Enter at least two characters

KaMenu API Documentation

Docs version: v2.1.0

KaMenu provides a public API for other plugins to open file-based menus, render in-memory YAML menus, register custom action namespaces, and resolve native Sprite text components for vanilla or third-party items.

API Class#

org.katacr.kamenu.api.KaMenuAPI#

openMenu(Player player, String menuId, List<String> arguments = emptyList())#

Opens a menu loaded by KaMenu's MenuManager.

Parameters:

  • player — The target player
  • menuId — The menu ID, such as "main_menu" or "shop/weapons"
  • arguments — Positional arguments passed to the target menu; read them with {arg:0}, {arg:1}, and so on

Returns:

  • boolean — Whether the open request was submitted successfully
import org.katacr.kamenu.api.KaMenuAPI

val success = KaMenuAPI.openMenu(player, "main_menu")
KaMenuAPI.openMenu(player, "shop/weapons", listOf("weapons", "vip"))
import org.katacr.kamenu.api.KaMenuAPI;
import java.util.List;

boolean success = KaMenuAPI.openMenu(player, "main_menu");
KaMenuAPI.openMenu(player, "shop/weapons", List.of("weapons", "vip"));

openYaml(Player player, String yaml, String contextId = "external", List<String> arguments = emptyList())#

Parses a YAML string in memory and opens it as a KaMenu menu. The YAML is not written to the menus directory and does not require a menu reload.

Parameters:

  • player — The target player
  • yaml — Full KaMenu menu YAML content
  • contextId — A log identifier used to locate the source of the external menu
  • arguments — Positional arguments passed to the target menu

Returns:

  • boolean — Whether the YAML was parsed and the open request was submitted successfully
val yaml = """
Title: "&aGame Center"
Body:
  welcome:
    type: message
    text: "&7Welcome, %player_name%!"
Bottom:
  type: notice
  confirm:
    text: "&aJoin"
    actions:
      - "kgc:join lobby"
""".trimIndent()

KaMenuAPI.openYaml(player, yaml, "kagamecenter:main", listOf("lobby", "solo"))

If YAML parsing fails, KaMenu writes a warning containing the contextId.

openConfig(Player player, YamlConfiguration config, String contextId = "external", List<String> arguments = emptyList())#

Opens an in-memory YamlConfiguration as a KaMenu menu. The configuration does not need to come from MenuManager.

Parameters:

  • player — The target player
  • config — A complete KaMenu menu configuration
  • contextId — A log identifier used to locate the source of the external menu
  • arguments — Positional arguments passed to the target menu

Returns:

  • boolean — Whether the open request was submitted successfully
import org.bukkit.configuration.file.YamlConfiguration
import org.katacr.kamenu.api.KaMenuAPI

val config = YamlConfiguration()
config.loadFromString(yaml)

KaMenuAPI.openConfig(player, config, "myplugin:dynamic-shop", listOf("diamond", "buy"))

All three platforms run Events.Open before rendering. If the action chain reaches return, the menu is not opened. reset reopens the current in-memory YamlConfiguration, so external menus do not require a MenuManager file ID.

The target menu can declare fallback values and a minimum count under Settings.pass_arguments. Explicit values win by index; missing indexes are filled from default. Menu content can use {arg:0}, {arg:1}, {args}, and {arg_count}. If the final count after fallback is below must, the menu is not opened.

registerActionHandler(String namespace, KaMenuActionHandler handler)#

Registers a custom action namespace. Actions matching namespace:payload are offered to the registered handler before KaMenu's built-in action logic continues.

Parameters:

  • namespace — The action namespace without :, such as "kgc"
  • handler — The custom action handler

Returns:

  • boolean — Whether the handler was registered successfully
import org.katacr.kamenu.api.KaMenuAPI

KaMenuAPI.registerActionHandler("kgc") { player, action, variables, config ->
    val payload = action.removePrefix("kgc:").trim()

    when {
        payload == "open-main" -> {
            KaMenuAPI.openYaml(player, buildMainMenuYaml(player), "kagamecenter:main")
            true
        }
        payload.startsWith("join ") -> {
            val arenaId = payload.removePrefix("join ").trim()
            joinArena(player, arenaId)
            true
        }
        else -> false
    }
}

Menu YAML can then use the namespace anywhere an action list is supported:

Bottom:
  type: notice
  confirm:
    text: "&aJoin"
    actions:
      - "kgc:join lobby"

The handler receives the resolved action string, input variables such as $(name), and the current menu config when available. Returning true means the action was handled. Returning false lets KaMenu continue with its normal action handling. Handler exceptions are logged with the namespace and action.

unregisterActionHandler(String namespace)#

Unregisters a custom action namespace.

KaMenuAPI.unregisterActionHandler("kgc")

resolveItemSprite(String itemId)#

Resolves an item ID into a native Adventure Component Sprite that can be appended directly to text. It supports vanilla materials such as "diamond" and KaMenu external-item prefixes: ce: / craftengine:, ia: / itemsadder:, and oraxen:.

Parameters:

  • itemId - A vanilla material or provider-prefixed item ID, such as "ce:hfrpg:hefeng_shard"

Returns:

  • Component - A native Sprite Component with a fixed white color
  • null - KaMenu is not initialized, the server is older than Minecraft 1.21.9, or the item cannot be resolved

The return value is a native Component, not a legacy or MiniMessage string. Append it directly. Do not round-trip it through LegacyComponentSerializer, ItemMeta#setLore(List<String>), or another string API, because that degrades the Sprite into visible text.

import net.kyori.adventure.text.Component
import org.katacr.kamenu.api.KaMenuAPI

val icon = KaMenuAPI.resolveItemSprite("ce:hfrpg:hefeng_shard")
val line = Component.text("Upgrade item: ").append(icon ?: Component.empty()).append(Component.text(" Hefeng Shard"))
meta.lore(listOf(line))
import net.kyori.adventure.text.Component;
import org.katacr.kamenu.api.KaMenuAPI;
import java.util.List;

Component icon = KaMenuAPI.resolveItemSprite("ce:hfrpg:hefeng_shard");
Component line = Component.text("Upgrade item: ")
    .append(icon == null ? Component.empty() : icon)
    .append(Component.text(" Hefeng Shard"));
meta.lore(List.of(line));

This method reads third-party item APIs and CraftEngine's final resource pack. Call it only on the Bukkit main thread or the relevant Folia player/region thread.

isAvailable()#

Checks whether KaMenu is loaded and available.

if (KaMenuAPI.isAvailable()) {
    KaMenuAPI.openMenu(player, "main_menu")
}

getPlugin()#

Gets the KaMenu plugin instance, or null if KaMenu is not loaded.

Action Handler Interface#

package org.katacr.kamenu.api

import org.bukkit.configuration.file.YamlConfiguration
import org.bukkit.entity.Player

fun interface KaMenuActionHandler {
    fun execute(
        player: Player,
        action: String,
        variables: Map<String, String>,
        rawConfig: YamlConfiguration?
    ): Boolean
}

Choose a unique namespace for your plugin. Avoid using built-in action names such as tell, open, command, console, or js unless you intentionally want to intercept that namespace.

Using in Other Plugins#

Method 1: Reflection#

If your plugin does not need a direct compile-time dependency on KaMenu, you can call the API via reflection:

fun openKaMenu(player: Player, menuId: String): Boolean {
    return try {
        val kaMenuPlugin = player.server.pluginManager.getPlugin("KaMenu") ?: return false
        val apiClass = Class.forName("org.katacr.kamenu.api.KaMenuAPI")
        val openMenuMethod = apiClass.getMethod("openMenu", Player::class.java, String::class.java)
        openMenuMethod.invoke(null, player, menuId) == true
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }
}

Method 2: Plugin Dependency#

For type-safe API access, add KaMenu as a dependency in your plugin.yml:

name: YourPlugin
version: 1.0.0
main: com.yourname.yourplugin.Main
api-version: "1.21"
depend:
  - KaMenu

Then use the API directly:

import org.katacr.kamenu.api.KaMenuAPI

fun openShop(player: Player) {
    KaMenuAPI.openMenu(player, "shop/weapons")
}

Notes#

  1. Plugin dependency: Ensure KaMenu is installed and enabled before calling the API.
  2. Menu source: Use openMenu for file menus and openYaml or openConfig for dynamic in-memory menus.
  3. Threading: openConfig and openYaml can be called from async code; KaMenu schedules the actual dialog rendering back to the main server thread.
  4. Open events: In-memory menus still execute Events.Open before rendering.
  5. Error handling: Check the boolean return value and provide user feedback when an API call fails.

Complete Example#

package com.example.plugin

import org.bukkit.command.Command
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
import org.bukkit.plugin.java.JavaPlugin
import org.katacr.kamenu.api.KaMenuAPI

class ExamplePlugin : JavaPlugin() {
    override fun onEnable() {
        if (!KaMenuAPI.isAvailable()) {
            logger.warning("KaMenu plugin not installed, some features will be unavailable")
        }

        KaMenuAPI.registerActionHandler("example") { player, action, variables, config ->
            val payload = action.removePrefix("example:").trim()
            if (payload == "reward") {
                player.sendMessage("§aReward action handled by ExamplePlugin")
                true
            } else {
                false
            }
        }
    }

    override fun onDisable() {
        KaMenuAPI.unregisterActionHandler("example")
    }

    override fun onCommand(
        sender: CommandSender,
        command: Command,
        label: String,
        args: Array<out String>
    ): Boolean {
        if (!command.name.equals("openmenu", ignoreCase = true)) {
            return false
        }
        if (sender !is Player) {
            sender.sendMessage("§cThis command can only be used by players")
            return true
        }

        val yaml = """
        Title: "&aExample Menu"
        Body:
          text:
            type: message
            text: "&7This menu is rendered from memory."
        Bottom:
          type: notice
          confirm:
            text: "&aReward"
            actions:
              - "example:reward"
        """.trimIndent()

        val success = KaMenuAPI.openYaml(sender, yaml, "example:command")
        if (!success) {
            sender.sendMessage("§cFailed to open menu")
        }
        return true
    }
}

Listening to Events#

KaMenu exposes two public Bukkit events for other plugins (such as EcoQuests) to listen to menu opens and data changes, e.g. to drive quests or statistics. Both event classes live in the org.katacr.kamenu.api.event package.

KaMenuOpenEvent#

Fired when a player opens a KaMenu menu. It is thrown synchronously just before the menu actually renders (both container and dialog menus), carrying the menu ID, the open source, and the arguments.

Field Type Description
player Player The player who opened the menu
menuId String Menu ID, e.g. "main/main_menu"
source KaMenuOpenEvent.Source Open source: COMMAND / BUTTON / API / OTHER
arguments List<String> The arguments passed to the menu
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.katacr.kamenu.api.event.KaMenuOpenEvent

class MyListener : Listener {
    @EventHandler
    fun onMenuOpen(event: KaMenuOpenEvent) {
        val player = event.player
        if (event.menuId == "main/main_menu") {
            // The player opened the main menu
        }
    }
}

KaMenuDataChangeEvent#

Fired when KaMenu storage (data / gdata / list / glist / meta / temp data) is written, modified, added, removed, or cleared.

Field Type Description
scope KaMenuDataChangeEvent.Scope Scope: PLAYER_DATA / GLOBAL_DATA / PLAYER_LIST / GLOBAL_LIST / META / PLAYER_TMP
action KaMenuDataChangeEvent.Action Operation: SET / MODIFY / ADD / REMOVE / CLEAR
player Player? The related player; null for global scopes or when the player is offline
key String The data key
value String? The written/appended value; null for removal operations
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.katacr.kamenu.api.event.KaMenuDataChangeEvent

class DataListener : Listener {
    @EventHandler
    fun onDataChange(event: KaMenuDataChangeEvent) {
        if (event.scope == KaMenuDataChangeEvent.Scope.PLAYER_DATA && event.key == "quest_progress") {
            val player = event.player ?: return
            // The player's data key quest_progress changed
        }
    }
}

Notes#

  1. Events are thrown synchronously: KaMenuOpenEvent fires on the main thread; KaMenuDataChangeEvent is triggered by async database writes and is rescheduled to the main thread before dispatch. Bukkit APIs are safe inside listeners.
  2. Online players only: KaMenuDataChangeEvent.player is non-null only while the player is online; cross-server or offline data changes yield null.
  3. List writes: list / glist methods reuse the data / gdata write path, so a single list write fires both the scalar event and the list event — filter by scope.
  4. Example integration: EcoQuests and similar plugins can add "open a menu" quests by listening to KaMenuOpenEvent.