The four fields
Hints select among author-controlled options; they never inject content. A
fieldFormat pattern is escaped like any text, a links hint only emits an anchor when the composed URL passes the scheme guard, and an images hint cannot bypass isSafeImageSrc. Misaimed hints — a misspelled key, a column that does not exist — are reported by analyzeHints as never-fatal diagnostics; the render still succeeds. See Groups and hints.
Types and the validator
@widgentic/core and its @widgentic/core/contract subpath export WidgetPayload, WidgetKind, WidgetHints, WidgetMeta and WidgetContractError. hints and meta are typed optional, so a payload without them compiles.
validateWidgetPayload(input, options?) never throws. It returns a discriminated result:
knownKinds is optional. When it is provided and non-empty, a kind outside the set fails with UNKNOWN_KIND; when it is omitted, the format of kind is still checked but membership is not. catalog.render(payload) always passes its own registered kinds, so the catalog is the authority on what exists.
Error codes
WidgetContractError is { code, message, path? }, where path names the offending field.
Schema violations reuse the same vocabulary with a dotted path into the data, so an agent can correct a payload from the error alone. The MCP server keeps the codes but speaks the tool’s input language: its paths say
widget, not kind, and an unknown-widget error lists the available kinds so recovery needs no extra round trip.
Forward compatibility
Two rules keep old renderers and new producers compatible:- Renderers ignore fields they do not know. A payload carrying a field outside the current contract renders without a validation error.
- The validator preserves unknown top-level fields on the returned payload instead of stripping them.
validateWidgetPayload({ kind: "card", data: {}, futureField: 1 })returns a payload whosefutureFieldis still1, andtoWidgetResult/extractWidgetPayloadround-trip such fields through an MCP result.
theme rides in the widgentic payload block as a top-level field, so a natively mounting host can honour it while contract validation still passes.
Format selects transport, never content
render_widget accepts format (both, html, widget, page, app). The value decides which content blocks the tool result carries — an HTML fragment, the widgentic payload block, a self-contained page, an Apps resource — not what the widget shows. The render happens once, and structuredContent is identical whatever format was requested. The formats are described in Inline rendering.
The stable surface: classes and tokens
Built-in renderers emitwg- prefixed class names — wg-card, wg-card-title, wg-table, wg-tree-node, wg-img wg-img-avatar — so hosts and custom styles target classes, never markup structure; the base stylesheet adds utilities such as wg-status-danger for templates to use. Custom kinds may ship styles as data, but every selector must target a .wg- class.
Colour, spacing and type come from 32 --wg-* custom properties, each with a declared type, a documented use and a light default in TOKEN_SPECS. The base stylesheet defines every token at :root, so a custom style can reference var(--wg-spacing-lg) bare and always resolve; themes override those definitions per container. Authors may add x-* custom variables, emitted as --wg-x-*. The full registry is in Theme tokens.
The two prefixes are the whole public styling surface. Markup structure inside a widget may change between releases;
wg-* classes and --wg-* tokens are what stays stable.