メインコンテンツまでスキップ

Category Menu

Defines the menu layout for a quest category using declarative layout pools. Each menu is built entirely from the layout pool — no hardcoded positions. Dynamic content (categories, quests, sort buttons) is injected via placeholder slots.

One entry per category. Leave the category field empty to configure the main (root) menu.

Fields

FieldTypeDefaultDescription
categoryString""Category this menu applies to. Leave empty for the main menu (list of root categories).
titleString @Placeholder @Colored""Menu title shown to the player. Supports MiniMessage and PlaceholderAPI.
rowsInt4Number of inventory rows (3-6).
guiTypeGuiTypeCUSTOMGUI type. Must be CUSTOM for layout pool menus.
audioGuiAudioData(empty)Audio overrides for this specific menu. Falls back to global config.
layoutPoolList<LayoutData>[]The layout pool — a list of named layouts. The mainLayoutId selects which one renders.
mainLayoutIdString""ID of the layout within the pool to display.
sortDisplayList<SortDisplayConfig>[]Per-mode display overrides for the sort button.

Layout Pool System

The layout pool is the heart of menu design. It's a list of LayoutData entries, each with an id. The mainLayoutId selects one layout to render. Standard GUI items (decorative fills, static buttons) are placed at fixed (x, y) positions.

Placeholder Slots

Three special placeholder types are automatically detected and replaced with dynamic content:

PlaceholderWhereInjected Content
CATEGORY_SLOTMain menuCategory icons. One slot per visible root category.
QUEST_SLOTCategory menuQuest buttons. One slot per visible quest in the category.
SORT_SLOTCategory menuA dynamic sort button that cycles through sort modes. Only one per layout.

Important: Place an item with buttonType: "SORT_SLOT" (or CATEGORY_SLOT / QUEST_SLOT) in your layout. The extension replaces it with the dynamic content at render time. The placeholder item itself is never shown — markers without content (e.g. fewer quests than markers) render as empty slots.

How injection works (indexed markers)

Since QuestCodex 0.3.0, markers are rewritten into indexed markers (QUEST_SLOT#0, QUEST_SLOT#1, …) before layout parsing. Each marker therefore lives inside the layout tree like any other slot and follows scrollable, paginated and frame viewports — quest slots placed inside a scrollable layout scroll with it.

  • The quest→marker mapping is stable: marker index = pool order → item order → the item's own repetition (row-major). Scrolling never reshuffles quests.
  • If a category has more quests than markers, the extras are not shown and a warning is logged (... has N quests but only M QUEST_SLOT markers). Add more marker rows or a taller scrollable area.
  • SORT_SLOT is the exception: it stays a fixed overlay at its declared position — it never scrolls away.

Multiple Placeholders

For CATEGORY_SLOT and QUEST_SLOT, a single placeholder item expands through its repetition fields — direction/count for a line, plus repeatY for a full grid:

{
"case": "simple",
"value": {
"items": [
{
"item": { ... },
"x": 1, "y": 1,
"buttonType": "QUEST_SLOT",
"direction": "right",
"count": 4,
"gap": 1
}
]
}
}

This creates 4 adjacent quest slots at x=1,2,3,4. The gap is a step multiplier (gap: 1 = adjacent, gap: 2 = every other slot → x=1,3,5,7). With repeatY: 3 the line is repeated on 3 rows (y=1,2,3), giving a 4×3 grid in one item.

direction is required. A marker (or any item) with count/gap but no direction stays a single slot — count and gap are ignored. If your category/quest area or your border "doesn't spread", add "direction": "right" (or down). Same rule for decorative panes — see the GUI border recipe.

Sort Display

The sortDisplay field configures how the sort button looks in each mode. Define one entry per sort mode:

FieldTypeDescription
modeSortModeConfigSort mode: ALL, NOT_STARTED, ACTIVE, COMPLETED.
itemItem @ContentEditorCustom item shown when this mode is active.
labelString @Placeholder @ColoredButton label (MiniMessage).
loreList<String> @Placeholder @ColoredAdditional lore lines.

The sort button cycles: ALL → NOT_STARTED → ACTIVE → COMPLETED → ALL. Each click rebuilds the menu with filtered quests and updates the button display.

Defaults & language: if you omit sortDisplay (or leave out a specific mode), that mode falls back to a built-in label and lore. Those built-in defaults are just a starting point — define a sortDisplay entry per mode to control the exact wording, colors and language of your sort button. Labels and lore render upright by default; add <italic> if you want italics.

Sort Mode Config

{
"sortDisplay": [
{
"mode": "ALL",
"label": "<yellow>Trier: Tout",
"lore": ["<gray>Clic: voir non commences"]
},
{
"mode": "NOT_STARTED",
"label": "<white>Trier: Non commences",
"lore": ["<gray>Clic: voir en cours"]
},
{
"mode": "ACTIVE",
"label": "<green>Trier: En cours",
"lore": ["<gray>Clic: voir termines"]
},
{
"mode": "COMPLETED",
"label": "<gray>Trier: Termines",
"lore": ["<gray>Clic: tout voir"]
}
]
}

Complete Example

A full category menu with layout pool, quest slots, sort button, and navigation:

{
"type": "category_menu",
"id": "menu_main_quests",
"name": "Menu main_quests",
"category": "main_quests",
"title": "<gold><bold>✦ Main Story ✦",
"rows": 5,
"guiType": "CUSTOM",
"layoutPool": [
{
"case": "simple",
"value": {
"id": "codex_layout_main_quests",
"items": [
{
"item": {
"case": "custom_item",
"value": {
"components": [
{ "case": "material", "value": { "material": "GRAY_STAINED_GLASS_PANE" } }
]
}
},
"x": 1, "y": 1,
"buttonType": "QUEST_SLOT",
"direction": "right",
"count": 4,
"gap": 1
},
{
"item": {
"case": "custom_item",
"value": {
"components": [
{ "case": "material", "value": { "material": "GRAY_STAINED_GLASS_PANE" } }
]
}
},
"x": 2, "y": 2,
"buttonType": "QUEST_SLOT",
"direction": "right",
"count": 4,
"gap": 1
},
{
"item": {
"case": "custom_item",
"value": {
"components": [
{ "case": "material", "value": { "material": "HOPPER" } }
]
}
},
"x": 4, "y": 4,
"buttonType": "SORT_SLOT"
},
{
"item": {
"case": "custom_item",
"value": {
"components": [
{ "case": "material", "value": { "material": "BARRIER" } },
{
"case": "custom_name",
"value": {
"name": "<red>Fermer"
}
}
]
}
},
"x": 8, "y": 4,
"buttonType": "CLOSE"
}
]
}
}
],
"mainLayoutId": "codex_layout_main_quests",
"sortDisplay": [
{ "mode": "ALL", "label": "<yellow>Trier: Tout", "lore": ["<gray>Clic: non commences"] },
{ "mode": "NOT_STARTED", "label": "<white>Trier: Non commences", "lore": ["<gray>Clic: en cours"] },
{ "mode": "ACTIVE", "label": "<green>Trier: En cours", "lore": ["<gray>Clic: termines"] },
{ "mode": "COMPLETED", "label": "<gray>Trier: Termines", "lore": ["<gray>Clic: tout voir"] }
]
}

What This Produces

  • Rows 1-2: Quest slots at x=1,3,5,7 (two rows, 8 quests max per page)
  • Row 4, center: Sort button (HOPPER icon) with per-mode label
  • Row 4, right: Close button showing the configured BARRIER icon and the <red>Fermer name you set — the tag only wires the close action, your visual is kept as-is

Tip: Use the GUI Extension's web editor to design your layout visually, then copy the layout pool into your category_menu entry.

Scrollable Quest Area (recipe)

To let players scroll through a long quest list, split the pool into four layouts: a decorative frame, a quest grid (content), a scrollable wrapper, and a frame that composes them:

"layoutPool": [
{ "case": "simple", "value": { "id": "deco", "items": [
{ "item": { "...": "border panes, back button (gui:back), SORT_SLOT, close button" }, "x": 0, "y": 0 }
] } },
{ "case": "simple", "value": { "id": "content", "items": [
{ "item": { "..." : "" }, "buttonType": "QUEST_SLOT", "buttonPrefix": "codex_button:",
"x": 0, "y": 0, "count": 7, "direction": "right", "gap": 1 },
{ "...": "repeat this marker row for y = 1..7 → a 7×8 virtual grid (56 quests)" }
] } },
{ "case": "scrollable", "value": {
"id": "quests_scroll", "innerId": "content",
"virtualWidth": 7, "virtualHeight": 8, "showDefaultButtons": false,
"buttons": [
{ "item": { "...": "ARROW item at frame-relative (1,4)" }, "direction": "UP", "step": 1 },
{ "item": { "...": "ARROW item at frame-relative (5,4)" }, "direction": "DOWN", "step": 1 }
]
} },
{ "case": "frame", "value": { "id": "root", "frames": [
{ "id": "f_deco", "x": 0, "y": 0, "width": 9, "height": 6, "layoutId": "deco" },
{ "id": "f_scroll", "x": 1, "y": 1, "width": 7, "height": 4, "layoutId": "quests_scroll" }
] } }
],
"mainLayoutId": "root"

Key points:

  • The visible window is the frame size (7×4 here); the virtual grid is the content size (7×8) — 56 quest slots, 28 visible, scrollable by row.
  • Scroll button coordinates are relative to the scroll frame (frame at x=1,y=1 → a button at (1,4) renders at absolute (2,5)). They are only shown when scrolling in that direction is possible — declare the frames with deco first and the scroll frame last so the arrows overlay the bottom border.
  • Scroll buttons generate gui:scroll 0 ±1 quests_scroll automatically — no manual commands needed.
  • Keep BACK/CLOSE/SORT_SLOT in the deco layout (outside the scroll region) so they never move.

Static navigation buttons use buttonType (with buttonPrefix: "codex_button:") to define their action:

ButtonTypeActionDescription
CLOSEClose the menuExits the codex entirely.
BACKMain menuAlways returns to the main menu: the empty-category category_menu if one exists, else the config's mainMenuTrigger, else the previous menu from history, else closes. (Since 0.3.0 — previously history-based only.)
PAGE_NEXTNext pageAdvances paginated layouts.
PAGE_PREVPrevious pageGoes back in paginated layouts.
SCROLL_UP / SCROLL_DOWN / SCROLL_LEFT / SCROLL_RIGHTScrollScrolls the focused scrollable layout by one step. Prefer the scrollable layout's own buttons when you have several scroll regions (they target their layout explicitly and hide automatically at the edges).
SORTSort (static)Static sort button — use SORT_SLOT instead for dynamic per-mode display.

Tagged navigation buttons keep the position of the tagged slot (since 0.3.0 — previously they were forced to slot 0). All navigation buttons use the global QuestCodexConfig sounds on click.

Your icon, name and lore are kept

A tagged navigation button displays exactly the item you configure on the slot — its icon, its custom_name / displayName, and its lore. The extension only attaches the button's behavior (the click action); it does not replace your visual.

The built-in default icon and label (a barrier for Close, a hopper for Sort, arrows for Back/scroll — with a short built-in label) is used only as a fallback — when the slot has no configured item at all. So to give your Close / Back / Sort / scroll button its own text (and your own language), just set its name like any other item:

{
"x": 8, "y": 5,
"buttonType": "CLOSE",
"buttonPrefix": "codex_button:",
"item": { "case": "custom_item", "value": { "components": [
{ "case": "material", "value": { "material": "BARRIER" } },
{ "case": "custom_name", "value": { "name": "<red>Close the Codex" } }
] } }
}

Names and lore render upright by default (no forced italics); add the <italic> MiniMessage tag on a line if you actually want it italic. This matches the GUI Extension rendering rules.

Alternative without tags: regular items with interactionList commands work in any menu and keep full control of the item's visual — gui:open <entryId> (guaranteed jump to a specific menu, e.g. your main hub), gui:back (session history), gui:close, gui:scroll <dx> <dy> <layoutId>. A close button is usually unnecessary: players can just press Escape.

Audio Overrides

The audio field overrides sounds for this specific menu:

{
"audio": {
"onOpen": { "soundId": "minecraft:block.note_block.chime", "volume": 0.5 },
"onClick": { "soundId": "minecraft:ui.button.click", "volume": 0.3 }
}
}

Unset fields fall back to the global quest_codex config.