Configuration

Structure

yaml
open-command:
  name: menu
  menu: main

menus:
  main:
    pause-menu: true
    quick-actions: false
    title: "<green>Menu</green>"
    buttons:
      spawn:
        label: "<green>Spawn</green>"
        actions:
          - "player: spawn"

Menu and button ids use lowercase [a-z0-9_.-]. Input keys allow [a-zA-Z0-9_.-] and are case-sensitive.

Default config

yaml
# CustomMenu configuration
# ========================
#
# STRUCTURE
#   menus:
#     <menu-id>:
#       <menu settings>
#       inputs:   optional, only useful if a button reads %input_<key>%
#       buttons:  required, a menu with no valid buttons is skipped entirely
#
# IDS
#   Menu and button ids may only use [a-z0-9_.-] and are lowercased.
#   Input keys may use [a-zA-Z0-9_.-] and are CASE SENSITIVE:
#     "details:" -> %input_details%   (not %input_Details%)
#
# TEXT (MiniMessage: https://docs.advntr.dev/minimessage/format.html)
#   Hex:       <#ff5555>text</#ff5555>
#   Gradient:  <gradient:#ff5555:#ffaa00>text</gradient>
#   Named:     <red> <gold> <gray> <aqua> <bold> <italic> <underlined>
#
# SPRITES / CUSTOM GLYPHS
#   Native Minecraft sprite (Minecraft 1.21.9+ / Adventure 4.25+):
#     <sprite:blocks:block/stone>
#     <sprite:"minecraft:items":item/diamond>
#   ItemsAdder Font Image:
#     <ia:namespace:glyph_id>
#     <itemsadder:namespace:glyph_id>
#     Regular ItemsAdder :glyph_id: placeholders are also passed through when
#     the ItemsAdder API is available.
#   Nexo Glyph:
#     <glyph:glyph_id>
#     <nexo:glyph_id>
#
#   These work anywhere this config accepts MiniMessage: titles, button names,
#   tooltips, body text, input labels/options and message: actions.
#
# YAML WARNING
#   yes / no / on / off / true / false are BOOLEANS in YAML, not text.
#     on-true: yes     ->  the string "true"    (wrong)
#     on-true: "yes"   ->  the string "yes"     (right)
#   Quote any value that is meant to be text but looks like a boolean.
#
# PLACEHOLDERS (usable in player:, console: and message: actions)
#   %player%          player name
#   %uuid%            player UUID
#   %input_<key>%     value of the input with that key
#   Player-supplied values are sanitized server-side before they reach a
#   command, and tag-escaped before they reach a message.
#
# RELOAD
#   /custommenu reload  applies: button actions, messages, menu: targets, the
#   open-command TARGET menu, and the FULL layout of any menu opened inline
#   (menu: action, /custommenu open, the open command).
#   Pause menu entries and their layout are baked into the dialog registry at
#   startup and sent to clients on join, so those need a server RESTART. The
#   open-command NAME is registered at startup too and also needs one.
#
# PAUSE MENU (vanilla client behavior, not configurable server-side)
#   exactly one  pause-menu: true  ->  its button-name is a direct button
#   two or more                    ->  the client collapses them behind a
#                                      single "Custom Options..." button
#
# QUICK ACTIONS
#   quick-actions: true puts a menu behind the client's "Quick Actions" key
#   bind. That bind has NO default key: the player has to set one in
#   Options -> Controls -> Key Binds -> Miscellaneous -> Quick Actions.
#   pause-menu and quick-actions are separate tags; set both to appear in both.

# Player-facing command that opens one menu, separate from the admin
# /custommenu command. Permission: custommenu.use (default: everyone).
# Renaming the command needs a RESTART; the target menu reloads live.
open-command:
  # Command name without the slash. [a-z0-9_-], lowercased.
  # Set to '' to not register the command at all.
  name: menu
  # Menu id from 'menus' below. Falls back to the first menu if omitted.
  menu: main

menus:

  # =====================================================================
  # main: the pause menu entry. Demonstrates every action type.
  # =====================================================================
  main:
    # Show this menu in the pause screen. RESTART to change. Default: false
    pause-menu: true

    # Show this menu under the client's "Quick Actions" key bind (unbound by
    # default in Controls -> Miscellaneous). Independent of pause-menu, and
    # also baked in at startup, so RESTART to change. Default: false
    quick-actions: false

    # Label of the pause screen button. Falls back to 'title' if omitted.
    button-name: "<gradient:#00e5ff:#7c4dff><bold>Change Me</bold></gradient>"

    # Header inside the opened dialog. Falls back to the menu id if omitted.
    title: "<gradient:#00e5ff:#7c4dff><bold>Change Me</bold></gradient>"

    # What the client does after a button is clicked:
    #   close              close the dialog                    (default)
    #   none               keep it open        (see 'sticky' below)
    #   wait_for_response  show a waiting screen until the server acts
    #                                          (see 'forms' below)
    after-action: close

    # Allow closing with ESC. Default: true
    can-close-with-escape: true

    # Button grid width. Default: 2, minimum 1
    columns: 2

    # Bottom exit button. Remove this line entirely for no exit button.
    exit-button: "<red>Close</red>"

    # Lines shown above the buttons. Optional.
    #
    # A bare string is a text line. A map may carry an item icon, a text line
    # rendered next to that icon, or both:
    #
    #   body:
    #     - "<gray>A plain text line</gray>"
    #     - item: ENDER_PEARL         # Material name, with or without minecraft:
    #       text: "<gray>Shown beside the icon</gray>"   # optional
    #     - sprite: item/diamond       # native Minecraft 1.21.9+ sprite
    #       atlas: minecraft:items     # optional; omit to use the default atlas
    #       text: "<gray>Sprite beside text</gray>"       # optional
    #       show-decorations: false   # stack count / durability bar, default false
    #       show-tooltip: true        # hover tooltip on the item, default true
    #       width: 16                 # icon size, 1-256, default 16
    #       height: 16                # icon size, 1-256, default 16
    #       text-width: 200           # optional, 1-1024
    #
    # Unknown materials, and materials that have no item form, fall back to the
    # text line alone.
    body:
      - "<gray>Each button below is a different action type.</gray>"
      - "<dark_gray>Edit these in plugins/CustomMenu/config.yml</dark_gray>"

    buttons:

      # "player: <cmd>" runs the command AS the clicking player.
      # No leading slash needed. Runs with that player's permissions.
      spawn:
        label: "<#55ff55>Player command</#55ff55>"
        tooltip: "<gray>Runs /spawn as you</gray>"    # optional
        width: 150                                    # optional, 1-1024, default 150
        actions:
          - "player: spawn"

      # "console: <cmd>" runs the command from console.
      announce:
        label: "<#ffaa00>Console command</#ffaa00>"
        tooltip: "<gray>Runs a command as console</gray>"
        actions:
          - "console: say %player% (%uuid%) pressed a button"

      # "message: <text>" sends MiniMessage text to the player.
      discord:
        label: "<#5865f2>Message</#5865f2>"
        tooltip: "<gray>Just sends you text</gray>"
        actions:
          - "message: <gray>Join us at <aqua><underlined>discord.gg/changeme</underlined></aqua></gray>"

      # "menu: <menu-id>" opens another menu from this file.
      forms:
        label: "<gold>Open a submenu</gold>"
        tooltip: "<gray>Shows every input type</gray>"
        actions:
          - "menu: forms"

      sticky:
        label: "<light_purple>Open sticky menu</light_purple>"
        tooltip: "<gray>A menu that stays open</gray>"
        actions:
          - "menu: sticky"

      # Multiple actions run top to bottom.
      chained:
        label: "<#00e5ff>Chained actions</#00e5ff>"
        tooltip: "<gray>A message, then a console command</gray>"
        actions:
          - "message: <gray>Running two things...</gray>"
          - "console: say %player% used the chained button"

      # No actions = client-only button. Nothing reaches the server; the
      # dialog just does whatever after-action says (here: close).
      plain:
        label: "<gray>Do nothing</gray>"
        tooltip: "<dark_gray>No actions, closes only</dark_gray>"
        actions: []

  # =====================================================================
  # forms: every input type. Values are read back as %input_<key>%.
  # Not in the pause menu, so this whole menu reloads live.
  # Reached via "menu: forms" or /custommenu open forms
  # =====================================================================
  forms:
    pause-menu: false
    title: "<gold><bold>Every Input Type</bold></gold>"

    # The client parks on a waiting screen until the server acts. The plugin
    # closes it once the clicked button's actions have finished.
    after-action: wait_for_response

    can-close-with-escape: true
    columns: 1
    exit-button: "<red>Back</red>"
    body:
      - "<gray>Fill this in and press Submit.</gray>"

    inputs:

      # OPTION: single-choice picker. %input_category% = the chosen id.
      category:
        type: option              # alias: single_option
        label: "<white>Category</white>"
        label-visible: true       # optional, default true
        width: 200                # optional, 1-1024, default 200
        options:
          - id: bug
            display: "<yellow>Bug report</yellow>"   # optional, defaults to the id
            initial: true                            # optional, first entry wins if unset
          - id: player
            display: "<red>Player report</red>"
          - id: other
            display: "<gray>Other</gray>"
          # A bare value works too and uses the id as its own display:
          - question

      # TEXT: free typing. Include 'multiline' for a box, omit it for one line.
      details:
        type: text
        label: "<white>Details</white>"
        label-visible: true
        width: 300
        initial: ""               # optional, prefilled text
        max-length: 250           # optional, default 100
        multiline:                # remove this whole section for a single line
          max-lines: 5            # optional
          height: 60              # optional, 1-512

      # TEXT again, single line, showing the minimal form.
      contact:
        type: text
        label: "<white>Discord tag</white>"
        initial: "@"
        max-length: 32

      # NUMBER: a slider.
      urgency:
        type: number              # aliases: number_range, slider
        label: "<white>Urgency</white>"
        width: 200
        min: 1
        max: 5
        step: 1                   # must be > 0
        initial: 2                # clamped into min..max
        # Optional. Two %s: the label and the value. A translation key such as
        # "options.generic_value" also works. Omit for the vanilla default.
        label-format: "%s: %s"

      # BOOLEAN: tick box. on-true / on-false are the strings %input_anonymous%
      # resolves to. Quote them, see the YAML warning at the top.
      anonymous:
        type: boolean             # alias: bool
        label: "<white>Stay anonymous</white>"
        initial: false
        on-true: "yes"
        on-false: "no"
        # Boolean inputs ignore 'width' and 'label-visible'; Paper's builder
        # does not expose them.

    buttons:
      submit:
        label: "<green>Submit</green>"
        width: 200
        actions:
          # Each %input_<key>% matches an input key defined above.
          - "console: say [Support] %player% | %input_category% | urgency %input_urgency% | anon %input_anonymous% | %input_contact% | %input_details%"
          - "message: <green>Sent. Category <white>%input_category%</white>, urgency <white>%input_urgency%</white>.</green>"

      back:
        label: "<gray>Back</gray>"
        actions:
          - "menu: main"

  # =====================================================================
  # sticky: after-action "none" keeps the dialog open after every click,
  # which is what you want for toggles. ESC is disabled here, so the
  # "close" button below is the only way out. If you copy this menu, keep
  # one close button or set can-close-with-escape: true.
  # =====================================================================
  sticky:
    pause-menu: false
    title: "<light_purple><bold>Stays Open</bold></light_purple>"
    after-action: none
    can-close-with-escape: false
    columns: 1
    # No exit-button on purpose: the 'leave' button does that job.
    body:
      - "<gray>These buttons do not close the menu.</gray>"

    buttons:
      day:
        label: "<yellow>Set day</yellow>"
        tooltip: "<gray>Needs permission for /time</gray>"
        actions:
          - "player: time set day"

      ping:
        label: "<aqua>Ping me</aqua>"
        actions:
          - "message: <aqua>Still here, <white>%player%</white>.</aqua>"

      # "close" closes the dialog server-side.
      leave:
        label: "<red>Close</red>"
        width: 200
        actions:
          - "close"