Report templates

Build an HTML template that turns each submitted record into a finished PDF or a polished email — designed in the dashboard, with live preview.

Updated 2026-08-07

This guide builds one template end to end: an expense report. A crew member submits a record with a few details and a table of expenses; minutes later a finished PDF lands in an inbox — photos included. The form behind it is the one from form patterns, with fields report_title, inspection_date, expenses (a table of description + amount rows), and grand_total.

Templates are HTML, written in the dashboard

A template is HTML with merge fields where data should land. You author it under Reports in the dashboard: a Content tab for the markup, a Styles tab for the CSS, and a live preview beside them rendering against sample data as you type. A new template opens with empty <body> and <style> scaffolding showing where each part goes; the scaffolding is editor guidance only and is not part of the saved template.

HTML is a deliberate choice: it is the most capable design surface (layouts no word processor can produce), it prints deterministically, and it is the format AI design tools speak natively. A practical workflow: describe your report to your favorite AI tool, paste the result into the editor — a full pasted document splits into Content and Styles automatically — then swap the hardcoded values for merge fields.

Output format, and how email carries it

A template produces one document, in one format — PDF or HTML — set on the report itself. Every destination receives that same document, which is what lets one report reach several places at once.

Email destinations then choose how the document travels:

ModeWhat happensBest for
As an attachmentThe document is attached, with a message body you write alongside itReports, certificates, anything filed or printed
In the bodyThe rendered document is the messageSummaries, alerts, notification-grade content

Sending in the body only makes sense for an HTML report — there is no way to put a PDF inside a message body. See Reports for the full set of destinations.

One discipline note: print design and email design are different crafts. Email clients lag browsers by a decade (Outlook renders with Word's engine; Gmail clips large messages — the editor warns when an embedded template gets big), so keep embedded templates simple and table-based, and save the rich layouts for attached PDFs.

The data your template sees

A record event arrives wrapped in an envelope, and your form's answers sit at data.field_values:

{$data.field_values.report_title}
{$data.field_values.grand_total|number:2}

The preview's sample-data panel shows the exact shape — and it can generate realistic sample data straight from one of your forms, so you design against the same fields the crew fills in.

What each field type provides:

Field typeWhat is storedExample
Textthe text{$data.field_values.report_title}
Numberthe number{$data.field_values.grand_total|number:2}
Dropdownthe chosen option's label{$data.field_values.status}
Datean ISO timestamp{$data.field_values.inspection_date|date:'LL'}
Photo / signaturea media value{$data.field_values.photo|image:width=400}
GPSa point with coordinates{$data.field_values.location.coordinates|join:', '}
Lookupthe chosen row, one key per column{$data.field_values.site.site_name}
Table (subform)the list of rowsloop over it — below

A repeating expenses table

<table>
  <tr><th>Description</th><th>Amount</th></tr>
  {foreach $data.field_values.expenses as $row}
  <tr>
    <td>{$row.description}</td>
    <td>{$row.amount|number:2}</td>
  </tr>
  {/foreach}
</table>

The rendered report repeats the row once per expense. Conditional sections work the same way — the merge syntax reference covers {if} blocks, every modifier, and the safety rules.

Photos in the output

Photo and signature fields embed directly:

{$data.field_values.site_photo|image:width=400:alt='Site photo'}

The platform keeps the image links fresh at render time, so photos captured in the field appear in the PDF without any extra setup.

Brand colors, automatically

Your workspace's branding is available to every template as CSS variables — --brand-primary, --brand-secondary, --brand-accent, and --brand-logo — so templates can follow the workspace's colors without hardcoding them:

.masthead { background: var(--brand-primary, #1a1916); }

Test, then wire it up

  1. Build in the editor; the live preview re-renders as you type and flags unknown fields and syntax problems inline.
  2. For attached templates, Preview PDF renders the real file.
  3. Create a webhook subscription targeting the template, with record.created as its event.

From then on, every submission renders and delivers within about a minute. Failed sends retry automatically and stay visible in the template's delivery history — see reports for the operational details.

From code

When your workflow needs processing the platform does not do — a call to another API, a multi-step enrichment — run it in your own service and invoke the render yourself: the API merge endpoint queues the same pipeline, and the body you post is exactly what {$ paths read, with no envelope unless you send one. The template's page in the dashboard also shows a direct merge URL with a per-template secret key — reports covers when to use which.