A template’s manifest has two parts: scalar variables and loops. The render endpoint
builds a validator from the manifest and checks your data against it. Anything not in the
manifest is stripped before the render runs — so a key that does not bind is silently dropped,
not rejected.
Scalar variables nest by dot-path
A scalar variable key with dots nests into an object. Declaring customer.name and
customer.email means you send:
{ "data": { "customer": { "name": "Ada", "email": "ada@example.com" } } }
Scalar dataType rules are in Concepts. Note that image and url accept
http(s) URLs only — see Images & signatures.
Loops bind arrays of objects
A loop iterates an array of objects. Each loop declares:
The top-level key your array is sent under. Must be dot-free — see the rule below.
itemFields
VariableDefinition[]
required
The shape of each row (at least one field). Each field is a normal typed variable.
Up to 10 sample rows for the builder preview.
Arrays are capped at 10,000 items per loop. You send the array at the top level of data,
keyed by the loop key:
{
"data": {
"areas": [
{ "areaKey": "hull", "condition": "damage", "severity": "cosmetic" },
{ "areaKey": "engine", "condition": "ok" }
]
}
}
The loop-key rule
A loop key must be a dot-free, top-level key. Scalar keys nest by dot-path; loop keys do
not — the validator uses a loop’s key verbatim as a single top-level property. Declare a loop
key with a dot and your array will never bind: it gets stripped and the loop defaults to an empty
array, so the section renders blank with no error.
Loop key | Send the array as | Result |
|---|
areas | data.areas | ✅ binds |
lineItems | data.lineItems | ✅ binds |
handover.areas | data["handover.areas"] | ⚠️ binds only at the literal dotted key |
handover.areas | data.handover.areas (nested) | ❌ stripped → empty array, blank section |
This rule applies to both the runtime validator and the JSON Schema returned by
GET /v1/templates/:slug. Recommendation: give every loop a
flat, dot-free key (areas, lineItems, passengers) and keep your scalars under their own
namespaces (booking.*, handover.*). Mixing a handover.* scalar namespace with a
handover.areas loop is the exact shape that silently fails.
Loops only render on the loop-capable pipeline
Declaring a loop in the manifest is necessary but not sufficient — the template must also be on a
pipeline that iterates. Document-mode templates have no loop primitive, so an array binds at
the API but renders nothing. Confirm the pipeline before you rely on a table:
Render pipelines.
Arrays of primitives
There is no “array of strings” variable type. Model a list of primitives as a loop with a single
item field:
{ "key": "tags", "label": "Tags", "itemFields": [ { "key": "value", "dataType": "text" } ] }
{ "data": { "tags": [ { "value": "vip" }, { "value": "repeat" } ] } }