Skip to main content

Widget JSON reference

Lives at plugins/<name>/widgets/<widget-id>.json.

Top-level fields

FieldRequiredTypeNotes
idstringStable identifier. Must equal the filename (sans .json).
titlestringHuman-readable name. Shown in the gallery and on the dashboard tile.
descriptionstringOne sentence. Used for search and as a tooltip.
categorystringTop-level grouping (e.g. "finance", "hr").
tagsstring[]Free-form classification.
keywordsrecommendedstring[]Search synonyms — alternate phrases users might say.
connectorsUsedstring[]Slugs of MCP servers this widget needs. The widget is hidden from tenants without those connectors.
popularityrecommendednumberHint for the gallery sort. Start at 0.
sizing{ preferred, min, max }Each is { colSpan, rowSpan }.
dataProvider✅ when spec uses $state{ mcp, tool, params }The MCP call that runs on mount.
spec{ root, elements }The render tree (json-render schema).

sizing

"sizing": {
"preferred": { "colSpan": 5, "rowSpan": 4 },
"min": { "colSpan": 4, "rowSpan": 3 },
"max": { "colSpan": 10, "rowSpan": 8 }
}

The grid is 12 columns wide. Heights are uniform-row (each rowSpan: 1 ≈ ~80px on desktop).

dataProvider

"dataProvider": {
"mcp": "acme-billing",
"tool": "list_invoices",
"params": { "limit": 25, "status": "overdue" }
}
FieldNotes
mcpServer-id from .mcp.json's mcpServers keys.
toolTool name as exposed by tools/list.
paramsObject passed as the tool's input. Static values only — no $state here.

The response is written to the state store at /<mcp>/<tool>/....

spec

"spec": {
"root": "card",
"elements": {
"card": { "type": "Card", "children": ["..."] },
"...": { ... }
}
}
FieldNotes
rootKey into elements. The first thing the renderer mounts.
elementsMap of element id → element definition. Keys are arbitrary strings.

Each element has shape:

{
"type": "<ComponentName>",
"props": { ... }, // resolved from $state/$computed/$item/$prop/$template
"children": ["<id>", ...], // child ids, looked up in spec.elements
"watch": { ... } // optional: state-change reactions
}

See Spec primitives for the binding tokens you can use inside props values.

Validation

The marketplace validator does not deep-lint widget JSON — it only checks plugin-level structure. The strict widget linter lives in MyHub (apps/web/src/features/widgets-system/lint.ts) and runs at load time. Common rejections:

  • id doesn't match filename.
  • spec.root references an unknown element id.
  • An element references a child id missing from spec.elements.
  • connectorsUsed references a server-id not present in any plugin's .mcp.json.