Skip to content

The interface as a JSON file (ui.json)

Everything declarative — translations, model, settings fields, commands, menus, toolbars, placements and panels — can live in a JSON file next to the plugin instead of in code. The plugin loads it explicitly from setup():

ctx.ui.define({ slice, icons, states, values, payloads, components });
await ctx.ui.load("ui.json"); // relative to the plugin's own folder
const settings = ctx.ui.settings(); // the handle for the declared fields

The manifest goes through the very same registry calls as code, so namespacing, i18n and cleanup on disable behave identically. ctx.ui.apply(object) does the same thing with an object you already have in memory.

Section What it declares
messages { en: {...}, fr: {...} } — the plugin’s own catalogs
models documentation of the plugin’s data shapes
settings { order, fields: [...] } — the settings fields
commands id, title, event, shortcut, icon, state, payload
menus id, title, order, standalone, parent
placements a command or a widget in a menu or a toolbar
panels a surface contribution: toolbar + tree/list/graph/form/forms/value/palette, or a named component
views a work-area view: { id, type, config } for a view engine, or a named component

Ids you declare are namespaced automatically ("new" becomes "notes.new"), and so are toolbars ("view:editor", "panel:documents"). References to something the shell owns are left untouched, so "menu": "file" places a command in the shell’s File menu. Titles and labels are i18n keys relative to the plugin.

Icons, live command state, tree rows, widget values, dynamic payloads and custom components are registered in code with ctx.ui.define() and referenced by name:

{ "id": "save", "title": "command.save", "event": "notes/document.save",
"icon": "save", "state": { "ref": "saveState" },
"payload": { "ref": "doc" } }

Simple cases need no code at all — small selectors read the plugin slice:

{ "state": { "from": "slice", "exists": "documents" } }
{ "value": { "from": "slice", "path": "activeDocId" } }
{ "items": { "from": "slice", "path": "documents",
"map": { "id": "id", "label": "title", "trailingWhen": "dirty",
"payload": { "docId": "id" } } } }
{ "payload": { "from": "context", "pick": { "docId": "params.docId" },
"fallback": { "docId": "activeDocId" } } }

Icon names come from the shared icon set (ctx.icons) or from ctx.ui.define({ icons }).

The Notes sample works this way: its whole interface lives in plugins/notes-tsx/ui.json. Only handlers, the editor view, the close prompt and undo/redo stay in code.

Documentation under CC BY 4.0 · © SCFlow contributors ·Back to supplychainflow.org