> ## Documentation Index
> Fetch the complete documentation index at: https://docs.marioded.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom menu actions

> Register an action, bind a catalog item to it, and use the menu interaction context.

# Custom menu actions

Let's add a button that shows a recording's ID. We will register a callback in the real menu registry, then reference it from `gui.details.items`.

## Get the menu registry

On a lobby server:

```java theme={null}
ReverseLobby lobby = ReverseLobby.get().orElseThrow();
MenuActionRegistry registry = lobby.getActionRegistry();
```

On a standalone player server:

```java theme={null}
ReversePlayer player = ReversePlayer.get().orElseThrow();
MenuActionRegistry registry = player.getActionRegistry();
if (registry == null) {
    return; // The standalone GUI is not initialized, for example in network-worker mode.
}
```

These snippets belong in your main-thread integration setup. Keep the registry reference if you will unregister your action when disabling.

## Register the callback

```java theme={null}
registry.register("example:show_id", (context, parameters) -> {
    if (!context.isActive() || !context.hasReplay()) return;

    String label = parameters.getOrDefault("label", "Replay");
    context.player().sendMessage(label + ": " + context.requireReplay().getUniqueId());
});
```

Here is what each piece does:

* `example:show_id` is the action's namespaced ID.
* `context` describes the current menu interaction and selected replay, if one exists.
* `parameters` contains the string parameters configured on the clicked item.
* `hasReplay()` protects `requireReplay()` from being called on an item without a replay selection.

This example only shows data. If an action edits or deletes a replay, perform a fresh catalog operation with a trusted actor rather than trusting an old selection.

## Put the action on a button

Merge this item into the lobby or standalone player's `gui.yml`:

```yaml theme={null}
details:
  items:
    show_id:
      slot: 13
      entry: false
      visible-in: [ready]
      material: PAPER
      amount: 1
      data: 0
      name: '<aqua>Show replay ID'
      lore: ['<gray>Print the selected recording ID in chat.']
      flags: []
      action: example:show_id
      parameters:
        label: 'Recording'
```

Slot 13 is free in the default details menu; its Information item uses slot 15. Choose another free slot if you already added a custom button there, then reload the owning role. Menu slots are **zero-based**. Open a replay's details and click the button; our callback will read `label` and send the selected UUID.

## Use navigation from the context

The context also provides menu navigation:

```java theme={null}
registry.register("example:return_to_catalog", (context, parameters) -> {
    if (context.isActive()) context.openCatalog();
});
```

Other context operations include `openDetails(replayId)`, `back()`, `close()`, `refresh()`, and page navigation. Use them instead of reaching into Inventory Framework objects. `replay()` can be null; not every catalog background item belongs to a recording.

## Unregister when disabling

```java theme={null}
registry.unregister("example:show_id");
registry.unregister("example:return_to_catalog");
```

The registry is shared. If another integration intentionally replaced one of your IDs, coordinate cleanup instead of removing its callback accidentally. The namespace is not an automatic ownership token.

For asynchronous actions, see [Threading and failure handling](https://docs.marioded.com/api/threading-and-failures.md). For the complete player setup and cleanup example, see [Extending the UI](https://docs.marioded.com/examples/ui-extensions.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.