Interactions
Every item in a layout can react to clicks. There are three levels, from simplest to most precise:
| Field | When it fires | Use for |
|---|---|---|
commands | Left or right click (when no specific interaction matches) | Simple buttons |
interactions | The exact click type it's bound to | Different actions per click type |
triggers | Alongside the above | Firing Typewriter entries (quests, dialogs, other menus) |
{
"x": 4, "y": 1,
"item": { "material": "EMERALD" },
"displayName": "<green>Shop",
"commands": ["shop open %player%"],
"triggers": ["<entry id of a sequence>"],
"interactions": [
{ "type": "RIGHT", "commands": ["shop sell-all %player%"] },
{ "type": "SHIFT_LEFT", "commands": [], "closeMenu": true }
]
}
Rules:
- A matching
interactionsentry takes precedence overcommandsfor that click type. %player%is replaced by the player name; commands run from console.closeMenu: trueandexecuteReturn: trueon an interaction append the internalgui:close/gui:backactions.- Slot-level
triggersfire on primary clicks and together with any matched interaction; interaction-leveltriggersfire only for their click type.
Click types
interactions[].type accepts:
| Keys | Type |
|---|---|
LEFT, RIGHT | Primary clicks |
SHIFT_LEFT, SHIFT_RIGHT | Shift-clicks |
MIDDLE, DOUBLE | Middle / double click |
KEY_1 … KEY_9 (or 1…9) | Hotbar number keys |
KEY_Q / Q (DROP), DROP_ALL | Drop keys |
KEY_F / F (SWAP_OFFHAND) | Swap-offhand key |
SCROLL_UP, SCROLL_DOWN | Mouse wheel (where supported) |
Internal gui: actions
Any command starting with gui: is handled by the menu engine instead of the console:
| Action | Effect |
|---|---|
gui:scroll <dx> <dy> [layoutId] | Scroll a viewport by slots. gui:scroll 0 1 my_list = one row down. Without layoutId, scrolls the focused/first scrollable |
gui:page \\<±n> [layoutId] | Change page of a paginated layout |
gui:back | Reopen the previous menu (session history) |
gui:close | Close the menu |
gui:open <entryId> / gui:action <entryId> | Fire another Typewriter entry (e.g. open a sub-menu) |
gui:input | Open the slot's input dialog |
Extensions can register their own prefixes (e.g. btcsky:) via MenuSessionService.registerCustomCommandHandler.
Cooldowns
Prevent click spam per slot:
{ "x": 4, "y": 1, "item": { "material": "CLOCK" }, "cooldownTicks": 40 }
Clicks during the cooldown window (here 2 s) are silently ignored.
Input dialogs
A slot can prompt the player for text input (Paper dialog API — the native 1.21+ dialog screen):
{
"x": 4, "y": 1,
"item": { "material": "NAME_TAG" },
"displayName": "<yellow>Rename island",
"input": {
"enabled": true,
"title": "<yellow>New name",
"placeholder": "Type the new island name…",
"onInputCommands": ["island rename %player% {input}"],
"onInputTriggers": []
}
}
- When
enabled, clicking opens the dialog immediately (takes precedence over commands/triggers). {input}is replaced by the typed text inonInputCommands;onInputTriggersfire after confirmation.- The menu re-renders automatically afterwards.
Ghost slots
"isGhost": true makes a click copy the item to the cursor instead of interacting — useful for creative-style palettes.
Ghost slots hand out a real ItemStack copy. Only use them with items players are allowed to obtain.
Item display options
| Field | Effect |
|---|---|
displayName, lore | MiniMessage, with PlaceholderAPI placeholders resolved per player |
criteria | Typewriter facts criteria — the slot only renders when they match |
allowPickup | Let the player take the item out (default false) |
animation | Tween the slot to targetX/targetY over duration ms (linear, ease_in, ease_out) |
buttonType / buttonPrefix | Tags the slot for extension-specific resolvers (e.g. dungeon buttons) |
Sounds
Menu-level audio is configured on the entry (audio.onOpen, onClose, onScroll, onClick) — see gui_settings for global defaults.
Keyboard & scroll navigation
While a menu is open, scroll-wheel and movement-key events (where captured) map to viewport scrolling of the focused scrollable — the one the player last clicked in. Multiple scroll areas each keep their own position.