Skip to main content
Templates are data: they select and arrange values, and the author supplies every literal. The forms below are the complete vocabulary.

Node forms

  • STRING — a text node: “Hello”
  • BIND — { “bind”: “path.to.value” } renders the value as text; ’.’ binds the scope itself. A text bind may carry “map”: { “<value>”: “<authored label>” } with an optional “default” — the value SELECTS a label (status → wording) — or a “format” (below), never both; “prefix” is an attribute-value transform and is ignored in a text position
  • PATHS — dot paths against the current scope (each item inside EACH). Escapes: ‘$meta.x’ reads payload.meta; ‘$root.x’ reads the top-level data from any depth; ‘$parent.x’ steps out of one enclosing EACH per token (‘$parent.$parent.x’); ‘$index’ is the zero-based position in the innermost EACH
  • EACH — { “each”: “path.to.array”, “key”?: “path”, “template”: <node>, “empty”?: <node> } repeats template with each item as scope; “key” names the item path that identifies it (e.g. “id”), so a list an action reorders keeps each item’s element and state
  • WHEN — { “when”: “path”, “template”: <node>, “else”?: <node> } renders template when the value is truthy
  • ELEMENT — { “tag”: “div”, “attrs”?: { “class”: “x”, “src”: { “bind”: “path” } }, “children”?: [<node>…] }
  • ATTR MAP — { “bind”: “status”, “map”: { “do-not-contact”: “wg-status wg-status-danger”, “active”: “wg-status wg-status-success” }, “default”: “wg-status” } — the bound value SELECTS one of your literals (semantic classes from data values); a miss emits default, or empty without one
  • ATTR PREFIX — { “bind”: “email”, “prefix”: “mailto:” } — emits prefix+value only when the value is non-empty (mailto:/tel: links; both schemes are allowed on href)
  • FORMAT — { “bind”: “ask”, “format”: { “type”: “currency”, “currency”: “COP”, “decimals”: 0 } } presents the value at render time while the payload keeps its typed value: a numeric STRING like “3206.9905920000” renders as $3,207. Types: number | currency | date. number/currency take decimals (integer 0-8) and an optional locale (default en-US); currency takes a three-letter uppercase ISO-4217 code and an optional currencyDisplay (narrowSymbol | symbol | code, default narrowSymbol). date takes a pattern of the tokens yyyy MM dd HH mm ss plus separators, at most 40 characters — { “bind”: “date”, “format”: { “type”: “date”, “pattern”: “dd-MM-yyyy HH:mm” } } turns “2026-09-01T02:04:47” into 01-09-2026 02:04 (an unzoned value is read as UTC and formatted in UTC, so every surface agrees). A value the format cannot parse renders raw — a format never hides data. Works on a text bind and on an attr value alike
  • ONE TRANSFORM PER VALUE — map, prefix and format are mutually exclusive on a single attr value; none of them may appear without bind
  • ACTION — an element may carry “action”: { “ref”: “<shared action name>” } or an inline { “definition”: { “kind”: “prompt”, “text”: [“Show the forecast for ”, { “bind”: “city” }] } } / { “definition”: { “kind”: “http”, “method”: “GET”, “url”: “https://…”, “input”: <schema>, “output”: <schema> } }, plus “input”: { “<field>”: “<path>” | { “const”: <value> } } and “output”: { “mode”?: “replace”|“merge”|“patch”, “path”?, “map”? }. An output map projects the response before the mode applies; when the response is an ARRAY the map entries resolve against EACH ITEM and the projection is the array of per-item results, so a list response can be reshaped without replacing it raw — a per-item projection is a LIST, so pair it with mode replace or patch (merge needs an object). A ”.” target SELECTS first: alone it is the whole projection (e.g. { ”.”: “0” }); beside other entries it names the value they map, so an enveloped list projects per item ({ ”.”: “data”, “ask”: “ask” }). Any source that starts with an index (“0.ask”) addresses the list itself, not its items. Bindings resolve at render time; buttons and links (never both href and action) become activatable in Apps hosts. A widget-level “load” (http GET only) runs once when the widget first renders

Safety rules

  • Attribute names matching on* and srcdoc are forbidden (FORBIDDEN_ATTRIBUTE).
  • script, iframe, frame, frameset, object, embed, style, link, meta, base, template and noscript are forbidden (FORBIDDEN_TAG) — a template is data, never active content.
  • On href, src, action, formaction, xlink:href, data, poster, ping: only http, https, mailto, tel schemes or relative references survive rendering.
  • Exception: an img element’s src additionally accepts base64 data:image/*;base64, URIs.
  • Bindings only ever produce text and attribute strings — bound values can never inject markup.

Bounds

Validation rejects deeper nesting; interpretation stops at the node budget and marks the render truncated. Actions bound from templates are specified on Action definition; entry shapes on Authoring contract.