> ## 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.

# Extending the UI

> Register a catalog action and a playback hotbar item using the public UI contracts.

# Extending the UI

**Runtime:** player · **Use it for:** a custom catalog action and restart control

Follow the [UI extension guide](https://docs.marioded.com/api/ui-extensions.md) for registration, slot validation, and cleanup rules.

Declare `depend: [reverse-player]`. This example adds a menu action that shows the replay ID and a hotbar button that restarts playback. Reserve a free hotbar slot before registering: slot 8 is free in the defaults, but may be used by customized layouts.

The player menu registry is nullable in network-worker mode. The hotbar remains a separate service. Add `action: example:show_id` to an appropriate replay-bearing menu item if you want to expose the menu action.

The restart action passes `context.session().getSessionId()` to both timeline controls. If you add viewer defaults, use `layout.addDefaultState(viewer.getUniqueId(), key, value)` and `clearViewerState(viewer.getUniqueId())`; rendering still uses `layout.refresh(viewer)` or `layout.refresh(session)` with actual objects. Leave/stop through the playback service so its HUD and session-keyed sidebar cleanup run too.

```java theme={null}
package example;

import com.marioded.reverse.api.gui.action.MenuActionRegistry;
import com.marioded.reverse.api.player.ReversePlayer;
import com.marioded.reverse.api.player.layout.LayoutItem;
import com.marioded.reverse.api.player.service.LayoutService;
import java.util.Map;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;

/** Example player extension. Declare depend: [reverse-player] and reserve a free slot. */
public final class UiExtensions extends JavaPlugin {
  private LayoutService layout;
  private MenuActionRegistry menus;
  private boolean installed;

  @Override
  public void onEnable() {
    ReversePlayer api = ReversePlayer.get().orElseThrow();
    layout = api.getLayoutService();
    menus = api.getActionRegistry();
    if (menus != null) {
      menus.register("example:show_id", (context, parameters) -> {
        if (context.isActive() && context.hasReplay()) {
          context.player().sendMessage(context.requireReplay().getUniqueId().toString());
        }
      });
    }
    layout.registerAction("example:restart", (context, parameters) -> {
      context.playbackService().seekTo(context.session().getSessionId(), 0);
      context.playbackService().resume(context.session().getSessionId());
    });
    layout.setItem(new LayoutItem(
        "example:restart_item", 8,
        context -> new ItemStack(Material.ARROW),
        Map.of(LayoutItem.Click.LEFT,
            new LayoutItem.Binding("example:restart", Map.of()))));
    installed = true;
  }

  @Override
  public void onDisable() {
    if (menus != null) menus.unregister("example:show_id");
    if (layout != null) {
      if (installed) layout.removeItem("example:restart_item");
      layout.unregisterAction("example:restart");
    }
  }
}
```

Copy this class into your integration. Programmatic registrations require explicit cleanup; they are not automatically owned by the registering plugin.


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