GroupeZ Docs
Development

Custom Buttons

Creating custom button types for zMenu

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

On this page

GroupeZ MCP

Documentation in your AI

MCP lets your AI search GroupeZ documentation and read current examples to help you configure your plugins.

Read-only · No documentation API key needed

1. Copy the server URL

Transport: HTTP (Streamable HTTP). Authentication: none. Your tool uses its own model and subscription limits.

  1. Open Plugins in ChatGPT on the web, then + → Add custom MCP server.
  2. Name it ‘GroupeZ Documentation’, paste the URL above and choose ‘No authentication’. Create and install the plugin.
  3. In a conversation, type @ and select the plugin before asking. If needed, enable search, get_page and list_pages in its settings.
Official setup guide

3. Ask a question

Use GroupeZ MCP to help me create a zMenu inventory. Search relevant pages, read the examples and cite your sources.
Other clients and troubleshooting

For clients using mcpServers, adapt this example to their format:

{
  "mcpServers": {
    "groupez-docs": {
      "url": ""
    }
  }
}

Available tools are search, get_page and list_pages. Refresh the connector’s tools after updates. If your AI cannot access them, check that the connector is enabled in the conversation and your account allows custom MCP servers. Opening the URL in a browser is not an MCP test.