shire Docs
Docs/Plugin API/Declarative UI

Declarative UI

Plugins never receive the ImGui context. They send a JSON layout to the Host; the Plugin Manager renders it with the Shire ImGui context and sends interaction notifications back to the plugin.

Layout

ui.setLayout replaces the plugin's layout:

{
  "layout": [
    {"type": "text", "id": "title", "text": "Player Information"},
    {"type": "section", "id": "pos_section", "label": "Position"},
    {"type": "text", "id": "x", "text": "X 123.5"},
    {"type": "toggle", "id": "enabled", "label": "Enabled", "value": true},
    {"type": "slider", "id": "speed", "label": "Speed", "value": 5, "min": 0, "max": 10},
    {"type": "select", "id": "mode", "label": "Mode", "options": ["A", "B"], "value": "A"},
    {"type": "input", "id": "name", "label": "Name", "value": "text"},
    {"type": "progress", "id": "progress", "label": "Progress", "text": "50%", "value": 0.5},
    {"type": "button", "id": "refresh", "label": "Refresh"},
    {"type": "separator", "id": "sep"}
  ]
}

Components

Type Required fields Notes
text id, text wraps in the manager
section id, label separator + heading
separator none (id optional) thin divider
button id, label sends a UI event when clicked
toggle id, label, value (bool) sends the new boolean
slider id, label, value, min, max sends the new number while dragging
input id, label, value sends on edit completion
select id, label, options, value sends the selected string
progress id, label, value (0..1), optional text display only

Ids are limited to 64 characters from [A-Za-z0-9._-]; duplicate ids are rejected. Layouts are capped at 64 components and 16 KiB of label/text/value data. Text is rendered, never executed.

Events

When the user interacts, the Host sends:

{
  "jsonrpc": "2.0",
  "method": "shire.uiEvent",
  "params": {"component": "enabled", "type": "toggle", "value": true}
}

The SDKs expose on_ui_event (Python), onUiEvent (TypeScript/C#) or poll_event/run (C++/Rust). A handler can call ui.setLayout or ui.update to refresh the screen.

def on_ui(component, kind, value):
    if component == "enabled":
        settings["enabled"] = bool(value)
        plugin.set_ui(build_layout())

plugin.on_ui_event(on_ui)

Updating one component

ui.update avoids resending the whole layout:

{"id": "progress", "value": 0.75}

The Host updates the component in its copy. The manager reads the updated snapshot on the next frame.

Where it appears

Open the manager with F9 or Config → Plugins → Open Plugin Manager. Select a plugin; its layout appears under Plugin UI. The manager window uses the same ImGui context, fonts and theme as the rest of Shire. It is a normal ImGui window, so it is included in the input-capture rects like the main menu.

Limits and behavior

  • A plugin can have one layout at a time.
  • A layout update is validated and copied; a malformed component rejects the whole update with -32602.
  • A plugin that is stopped has its layout removed from the manager when the record is removed; disabled plugins keep their saved layout in memory.
  • The manager is the only renderer. A plugin cannot draw arbitrary ImGui, create windows, capture global input or access textures.
  • If a plugin needs a control that is not listed, it can combine existing components or request a new native operation through the Host roadmap.

Shire Plugin API 1.0 · Written in English. Documentation home · API status · Changelog

Independent project. Not affiliated with HoYoverse. Never put secrets in a plugin, manifest or package.