KaMenu API Documentation
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 playermenuId— 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 playeryaml— Full KaMenu menu YAML contentcontextId— A log identifier used to locate the source of the external menuarguments— 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 playerconfig— A complete KaMenu menu configurationcontextId— A log identifier used to locate the source of the external menuarguments— 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 colornull- KaMenu is not initialized, the server is older than Minecraft1.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#
- Plugin dependency: Ensure KaMenu is installed and enabled before calling the API.
- Menu source: Use
openMenufor file menus andopenYamloropenConfigfor dynamic in-memory menus. - Threading:
openConfigandopenYamlcan be called from async code; KaMenu schedules the actual dialog rendering back to the main server thread. - Open events: In-memory menus still execute
Events.Openbefore rendering. - 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#
- Events are thrown synchronously:
KaMenuOpenEventfires on the main thread;KaMenuDataChangeEventis triggered by async database writes and is rescheduled to the main thread before dispatch. Bukkit APIs are safe inside listeners. - Online players only:
KaMenuDataChangeEvent.playeris non-null only while the player is online; cross-server or offline data changes yieldnull. - List writes:
list/glistmethods reuse thedata/gdatawrite path, so a single list write fires both the scalar event and the list event — filter byscope. - Example integration: EcoQuests and similar plugins can add "open a menu" quests by listening to
KaMenuOpenEvent.