Report templates pull record data into place with merge fields. The syntax is small and permanent: what you learn here will not change out from under your templates.
Fields
A field starts with {$ and gives a dot path into your data:
{$data.field_values.site_name}
{$data.field_values.expenses.0.amount}
For a record delivered through a webhook, your form's answers live
under data.field_values. The editor's preview shows the exact data
shape your template receives.
A missing path renders as empty text — and the preview lists it as a warning so typos surface while you build, not after delivery.
Modifiers
Format a value by appending modifiers with |. Arguments follow :
and can be quoted, so patterns containing commas are safe.
| Modifier | Example | Output |
|---|---|---|
number | {$total|number:2} | 1,240.50 |
currency | {$total|currency:'USD'} | $1,240.50 |
date | {$when|date:'LL'} | June 11, 2026 |
date | {$when|date:'YYYY-MM-DD'} | 2026-06-11 |
upper / lower / capitalize | {$name|capitalize} | Field Crew |
trim | {$notes|trim} | leading/trailing spaces removed |
default | {$region|default:'—'} | — when the value is empty |
join | {$tags|join:', '} | roof, electrical |
count | {$items|count} | 3 |
Modifiers chain left to right: {$name|trim|upper}. Dates display in
UTC so output never depends on where the rendering happens.
Conditionals
{if $score >= 90}A{elseif $score >= 80}B{else}Needs review{/if}
Conditions support ==, !=, >, >=, <, <=, combined with
&&, ||, !, and parentheses. Values can be fields (with
modifiers), quoted text, numbers, true, false, or null:
{if ($status == 'Failed' || $status == 'Flagged') && $items|count > 0}
<p class="alert">Review required.</p>
{/if}
Text comparisons are case-sensitive: a dropdown stores its option label
exactly, so compare against 'Other', not 'other'. An empty list,
empty text, and a missing field all count as false.
Loops
<table>
{foreach $data.field_values.expenses as $row}
<tr>
<td>{$row.description}</td>
<td>{$row.amount|number:2}</td>
</tr>
{/foreach}
</table>
Loops repeat over lists from your data — table rows from a subform are the everyday case. Loops nest, and inside a loop you can still read any top-level field by its full path.
Images
Photo and signature fields render with the image modifier:
{$data.field_values.site_photo|image:width=400:alt='Site photo'}
It accepts your media fields directly (and also plain URLs or base64
data). image_url gives just the address, for use inside an existing
<img src="...">.
What width does
width sets the size the image is drawn at, and it also sets how much
picture data goes into the file. The renderer shrinks each photo to
about 2.5 times the width you asked for — enough detail to stay sharp in
print — and never smaller than 320 pixels or larger than 1400 pixels
across. So width=400 embeds a photo about 1000 pixels wide, not the
full 1920-pixel phone capture.
This is a change from earlier releases, where width only affected
layout and the full-resolution original went into every document. PDFs
built from photo templates are now much smaller — a report with eight
phone photos drops from roughly 19 MB to under 200 KB — and the photos
in them carry less detail than they used to. If a photo needs to be
inspected closely, ask for a larger width.
Two cases to know:
- An image with no
widthgets the 1400-pixel ceiling. That includes any<img>tag you write yourself aroundimage_url, since there is no width for us to read there. - If the same photo appears twice at different widths, the larger one decides the quality for both.
Text safety
Everything a field prints is HTML-escaped automatically — a submission
containing <script> or stray angle brackets becomes harmless visible
text. When a field intentionally holds markup you trust, opt out with
{$content|raw}.
Special values
| Field | Value |
|---|---|
{$__timestamp__} | render time, ISO format (chain |date:'LL' to format) |
{$__unix_timestamp__} | render time, Unix seconds |
Where the syntax works
- Template content and styles tabs — the full syntax (content only; the styles tab is plain CSS).
- Email subject, recipient, and file name — fields and modifiers only; conditionals and loops are not available in these settings.
- Email body (attachment mode) — fields and modifiers only. For a fully templated email, send the document in the message body, where the template itself is the message.
- Box folder path, folder id, and file description — fields and modifiers only, the same as the settings above. A folder path is built segment by segment from the template, so a value holding a slash fails the delivery instead of adding a folder level. See reports.
Braces in CSS and text
Ordinary braces pass through untouched — CSS rules like
.card { margin: 0; } need no escaping. Only {$, {if, {foreach}
and their closing tags are treated as merge syntax.