Skip to main content

Custom Buttons

Learn how to create and register custom button types in zMenu.

Overview​

Custom buttons allow you to extend zMenu with new button behaviors. This involves:

  1. Creating a ButtonLoader to parse YAML configuration
  2. Creating a Button implementation for the behavior
  3. Registering the loader with the ButtonManager

ButtonManager​

Get the button manager to register custom buttons:

ButtonManager buttonManager = menuPlugin.getButtonManager();

Creating a Button Loader​

The ButtonLoader interface defines how your button is loaded from YAML:

import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.loader.ButtonLoader;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;

public class MyButtonLoader implements ButtonLoader {

@Override
public String getName() {
return "MY_CUSTOM_BUTTON"; // The type name used in YAML
}

@Override
public Button load(YamlConfiguration configuration, String path, Plugin plugin) {
// Read custom properties from configuration
String customProperty = configuration.getString(path + ".custom-property", "default");
int customNumber = configuration.getInt(path + ".custom-number", 0);
boolean customFlag = configuration.getBoolean(path + ".custom-flag", false);

// Create and return your button instance
return new MyCustomButton(customProperty, customNumber, customFlag);
}
}

Creating a Button Implementation​

Implement the Button interface (or extend a base class):

import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.button.DefaultButtonValue;
import fr.maxlego08.menu.inventory.inventories.InventoryDefault;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;

public class MyCustomButton extends DefaultButtonValue implements Button {

private final String customProperty;
private final int customNumber;
private final boolean customFlag;

public MyCustomButton(String customProperty, int customNumber, boolean customFlag) {
this.customProperty = customProperty;
this.customNumber = customNumber;
this.customFlag = customFlag;
}

@Override
public String getName() {
return "MY_CUSTOM_BUTTON";
}

@Override
public void onClick(Player player, InventoryClickEvent event,
InventoryDefault inventory, int slot) {
// Your custom click logic here
player.sendMessage("Custom button clicked!");
player.sendMessage("Property: " + customProperty);
player.sendMessage("Number: " + customNumber);
player.sendMessage("Flag: " + customFlag);

// Example: Execute different actions based on configuration
if (customFlag) {
player.sendMessage("Flag is enabled!");
}

// You can also interact with the inventory
// inventory.refresh(player);
}
}

Registering Your Button​

Register your button loader when your plugin enables:

@Override
public void onEnable() {
MenuPlugin menuPlugin = (MenuPlugin) Bukkit.getPluginManager().getPlugin("zMenu");

if (menuPlugin != null) {
// Register custom button
menuPlugin.getButtonManager().register(new MyButtonLoader());
getLogger().info("Registered custom button: MY_CUSTOM_BUTTON");
}
}

Using Your Custom Button​

In your inventory YAML configuration:

items:
my-custom-item:
type: MY_CUSTOM_BUTTON
slot: 13
custom-property: "Hello World"
custom-number: 42
custom-flag: true
item:
material: DIAMOND
name: "&b&lCustom Button"
lore:
- "&7This is a custom button!"

Complete Example: Teleport Button​

TeleportButtonLoader.java​

package com.example.buttons;

import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.loader.ButtonLoader;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;

public class TeleportButtonLoader implements ButtonLoader {

@Override
public String getName() {
return "TELEPORT";
}

@Override
public Button load(YamlConfiguration config, String path, Plugin plugin) {
String worldName = config.getString(path + ".world", "world");
double x = config.getDouble(path + ".x", 0);
double y = config.getDouble(path + ".y", 64);
double z = config.getDouble(path + ".z", 0);
float yaw = (float) config.getDouble(path + ".yaw", 0);
float pitch = (float) config.getDouble(path + ".pitch", 0);
String message = config.getString(path + ".message", "&aTeleported!");

World world = Bukkit.getWorld(worldName);
Location location = new Location(world, x, y, z, yaw, pitch);

return new TeleportButton(location, message);
}
}

TeleportButton.java​

package com.example.buttons;

import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.button.DefaultButtonValue;
import fr.maxlego08.menu.inventory.inventories.InventoryDefault;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;

public class TeleportButton extends DefaultButtonValue implements Button {

private final Location location;
private final String message;

public TeleportButton(Location location, String message) {
this.location = location;
this.message = message;
}

@Override
public String getName() {
return "TELEPORT";
}

@Override
public void onClick(Player player, InventoryClickEvent event,
InventoryDefault inventory, int slot) {
// Close the inventory first
player.closeInventory();

// Teleport the player
player.teleport(location);

// Send message with color codes
player.sendMessage(ChatColor.translateAlternateColorCodes('&', message));
}
}

Usage in YAML​

items:
spawn-teleport:
type: TELEPORT
slot: 13
world: "world"
x: 0
y: 100
z: 0
yaw: 90
pitch: 0
message: "&aYou have been teleported to spawn!"
item:
material: ENDER_PEARL
name: "&5&lTeleport to Spawn"
lore:
- "&7Click to teleport"

Complete Example: Counter Button​

A button that tracks clicks per player:

CounterButtonLoader.java​

package com.example.buttons;

import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.loader.ButtonLoader;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class CounterButtonLoader implements ButtonLoader {

// Shared counter storage across all counter buttons
private static final Map<UUID, Integer> counters = new HashMap<>();

@Override
public String getName() {
return "COUNTER";
}

@Override
public Button load(YamlConfiguration config, String path, Plugin plugin) {
int maxCount = config.getInt(path + ".max-count", 10);
String rewardCommand = config.getString(path + ".reward-command", "");

return new CounterButton(counters, maxCount, rewardCommand);
}
}

CounterButton.java​

package com.example.buttons;

import fr.maxlego08.menu.api.button.Button;
import fr.maxlego08.menu.api.button.DefaultButtonValue;
import fr.maxlego08.menu.inventory.inventories.InventoryDefault;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;

import java.util.Map;
import java.util.UUID;

public class CounterButton extends DefaultButtonValue implements Button {

private final Map<UUID, Integer> counters;
private final int maxCount;
private final String rewardCommand;

public CounterButton(Map<UUID, Integer> counters, int maxCount, String rewardCommand) {
this.counters = counters;
this.maxCount = maxCount;
this.rewardCommand = rewardCommand;
}

@Override
public String getName() {
return "COUNTER";
}

@Override
public void onClick(Player player, InventoryClickEvent event,
InventoryDefault inventory, int slot) {
UUID uuid = player.getUniqueId();

// Get current count
int currentCount = counters.getOrDefault(uuid, 0);
currentCount++;

// Update counter
counters.put(uuid, currentCount);

player.sendMessage("§aClick count: " + currentCount + "/" + maxCount);

// Check if max reached
if (currentCount >= maxCount) {
player.sendMessage("§6You reached the maximum clicks!");

// Execute reward command
if (!rewardCommand.isEmpty()) {
String command = rewardCommand.replace("%player%", player.getName());
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
}

// Reset counter
counters.put(uuid, 0);
}

// Refresh the inventory to update display
inventory.refresh(player);
}
}

Advanced: Button with Custom Item Display​

Override the item display based on player state:

@Override
public ItemStack getCustomItemStack(Player player) {
// Return a custom item based on player state
int count = counters.getOrDefault(player.getUniqueId(), 0);

ItemStack item = new ItemStack(Material.PAPER);
ItemMeta meta = item.getItemMeta();
meta.setDisplayName("§6Counter: " + count + "/" + maxCount);
item.setItemMeta(meta);

return item;
}

Best Practices​

  1. Use meaningful type names - Make them unique and descriptive
  2. Validate configuration - Check for missing or invalid values
  3. Handle null values - Use defaults for optional properties
  4. Keep buttons focused - One button type = one purpose
  5. Document your buttons - Explain required configuration options
  6. Test edge cases - Empty configs, missing properties, etc.

Next Steps​

Copyright © 2026 GroupeZ|Build #loading...|-