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:
Scalar dataType rules are in Concepts. Note that image and url accept http(s) URLs only — see Images & signatures.

Select variables (fixed choices)

A select variable restricts a field to a fixed set of choices. It carries an options array of { value, label }value is what you send in data (and what is validated), label is display text. A select requires a non-empty options array at publish time (POST/PUT /v1/templates reject a select with no options); option values must be unique, and a defaultValue, if set, must be a string equal to one of them.
At render time the validator accepts only a declared value — anything else fails with invalid_input_data, and the generated JSON Schema emits the field as a string enum of the option values. options is optional and only meaningful for select, so clients that ignore it are unaffected.

Loops bind arrays of objects

A loop iterates an array of objects. Each loop declares:
string
required
The top-level key your array is sent under. Must be dot-free — see the rule below.
string
required
Human-readable label.
VariableDefinition[]
required
The shape of each row (at least one field). Each field is a normal typed variable.
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:

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.
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: