Skip to main content
A widget template is JSON, not code. Five node forms — text, bind, element, each and when — compile into an ordinary renderer, and every value that reaches the output is either one of your literals or payload data rendered as escaped text. There are no expressions: data selects, and you supply every literal. This page is the human version of the rules; the generated Template DSL reference and Limits carry the exact lists and numbers.

Node forms

A string is a text node. { "bind": "path" } renders the value at that path as text; "." binds the current scope itself.

Attribute transforms

A bound attribute value may carry one transform, map or prefix, never both. map lets a data value select one of your literals. A hit emits that key’s literal; a miss emits default, or an empty value without one. Data never contributes output characters.
prefix emits your literal followed by the bound value, but only when that value is a non-empty string, so an absent email yields no dead mailto: link.

Paths

Paths are dot notation against payload.data: items.0.name walks objects and array indices. Inside each, the scope is the current element. Four escapes exist: Missing or non-traversable paths are blanks, not errors: empty text for bind, no repetitions for each, the else branch for when. Interpretation never throws, whatever shape the data has.
Bind properties your dataSchema declares. Schema-declared data is validated with dotted paths, so an agent gets a correctable error instead of blank output. $meta.* is outside schema validation; if the widget needs a heading, declare title as an optional property instead. See Data schemas.

What a template cannot do

  • Run code. There are no expressions, functions or scripts. Bindings only ever produce text and attribute strings; a bound value of <img onerror=x src=y> renders as those literal characters, not as an element.
  • Attach handlers. Attribute names matching on* fail validation with FORBIDDEN_ATTRIBUTE, as do srcdoc and the renderer-reserved data-wg-* names. They are skipped at render time as well.
  • Embed active content. script, iframe, frame, frameset, object, embed, style, link, meta, base, template and noscript fail with FORBIDDEN_TAG.
  • Smuggle URL schemes. On URL-bearing attributes (href, src, action, formaction, xlink:href, data, poster, ping) only http, https, mailto, tel and relative references survive; anything else, javascript: included, is dropped at render time. A prefix value runs the same guard, so "prefix": "javascript:" is dropped like a bound javascript: value.
  • Use data: URIs, with one exception: an img element’s src accepts base64 data:image/*;base64, URIs.
  • Nest without limit or render without bound. Validation rejects nesting past a fixed depth (TEMPLATE_TOO_DEEP); interpretation stops at a node budget and marks the render truncated, because each multiplies template nodes by agent-supplied data length. The numbers are in Limits.

Validation errors

validateTemplate returns a structured error with a code (INVALID_TEMPLATE_NODE, INVALID_PATH, FORBIDDEN_ATTRIBUTE, FORBIDDEN_TAG, TEMPLATE_TOO_DEEP, INVALID_ACTION or CONFLICTING_ATTRIBUTES), a message, and a dotted path locating the node, such as children.0. The designer shows it at the offending node; the store refuses the entry on write.

Roots and wrapping

When the root interprets to anything but exactly one node (an each at the root, for instance), the output is wrapped in a div with class wg-template. A single element root is emitted as is. Elements may also carry an action binding: a button or a bound to a prompt or http action, never together with href. That form is covered in Actions and secrets.