> ## Documentation Index
> Fetch the complete documentation index at: https://docs.widgentic.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Template DSL

> Every node form a widget template may use, the safety rules the validator enforces, and the structural bounds — derived from the live validators.

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
* **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", "template": \<node>, "empty"?: \<node> } repeats template with each item as scope
* **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). One transform per attr value: map OR prefix, never both
* **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"? }. 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

| Bound                              | Value   |
| ---------------------------------- | ------- |
| Maximum nesting depth              | `64`    |
| Interpreted node budget per render | `50000` |

Validation rejects deeper nesting; interpretation stops at the node budget and marks the render truncated.

Actions bound from templates are specified on [Action definition](/reference/action-definition); entry shapes on [Authoring contract](/reference/authoring-contract).
