Skip to main content
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:
key
string
required
The top-level key your array is sent under. Must be dot-free — see the rule below.
label
string
required
Human-readable label.
itemFields
VariableDefinition[]
required
The shape of each row (at least one field). Each field is a normal typed variable.
previewData
object[]
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 keySend the array asResult
areasdata.areas✅ binds
lineItemsdata.lineItems✅ binds
handover.areasdata["handover.areas"]⚠️ binds only at the literal dotted key
handover.areasdata.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" } ] } }