A report template turns platform events — most often a submitted record — into a finished deliverable. You design the template in the dashboard's built-in editor, and the platform fills it with each record's data and delivers it wherever you need it.
Templates and the editor
Templates are HTML, authored under Reports in the dashboard: a
Content tab for the markup, a Styles tab for the CSS, and a live
preview that re-renders against sample data as you type — including
sample data generated from one of your forms. Merge fields like
{$data.field_values.site_name} place the record's values; the
builder guide walks a full
template end to end, and the
merge syntax reference covers
every field, modifier, loop, and conditional. Photos and signatures
embed directly in the output.
A new template opens with empty <body> and <style> scaffolding so
it is clear where the markup and the CSS go. The scaffolding is editor
guidance only — the saved template stays a clean fragment.
Output, then destinations
A report has two separate parts, and keeping them separate is what lets one document reach several places.
The output is what the document is: the HTML template, and whether it is produced as a PDF or as HTML. Format lives on the report, not on each destination, because the sequence is merge the data, save the file, then send it. If two destinations genuinely need different formats, that is two reports.
The destinations are where each finished document goes. A report can have as many as it needs, and each one is delivered independently — a failed push to one does not hold up the others, and each retries on its own.
You add and remove destinations on the report's page in the dashboard. They can also be managed through the API, and listed with a report when you create one, so a system you build can provision a working report in a single call — see the API reference. Changing a destination's type is not an edit: delete it and add the replacement, because every setting on it belongs to the type it was created as.
Choose whether the document travels as an attachment or as the message body:
- As an attachment — the file is attached, and the message body is the text you write alongside it.
- In the body — the rendered document is the message itself. There is no separate body to write.
Recipient, subject, and body all accept merge fields, so a report can reach a different inbox for every submission. Separate several addresses with commas.
Webhook
The finished document is announced to an endpoint you run. This is the option to choose when the document needs to be archived, linked to a record in your own system, or routed by rules the platform does not know about.
Your endpoint receives a document.rendered event in the same envelope
every webhook uses, so one receiver can handle
both:
{
"id": "…",
"event": "document.rendered",
"timestamp": "2026-09-04T13:46:01Z",
"organization_id": "…",
"resource_type": "report_delivery",
"resource_id": "…",
"data": {
"file_url": "https://…",
"expires_at": "2026-09-05T13:46:01Z",
"filename": "Trial Results - Broccoli - 26CVA05.pdf",
"mime_type": "application/pdf",
"output_format": "pdf",
"template": { "id": "…" },
"merge_run_id": "…",
"organization_id": "…"
}
}id stays the same across retries, so a receiver can ignore a duplicate.
file_url is a short-lived link your service fetches to get the file —
the document is not sent in the payload. Fetch it promptly: the link
stops working at expires_at, and rendered documents are removed 24
hours after they are produced. If you need to keep a document, download
and store it on receipt.
Set a signing secret on the destination and each delivery is signed the same way record webhooks are, so you can verify it arrived from us using code you may already have. You can also add fixed headers — an API key, a tenant identifier — that we send with every request.
A webhook delivery counts as successful when your endpoint returns a 2xx response. That confirms the announcement reached you; it does not confirm you fetched the file.
Box
The finished document is uploaded into a folder in your Box account. Each Box destination is authorized separately: save the destination, then use Authorize Box on the report's page to sign in to Box and grant access. Box's own consent screen does the signing in; the platform never sees your Box password, and the tokens it receives stay on the server. A Box administrator may need to enable the FieldScroll app once before people in the company can authorize it.
Once authorized, pick the folder the documents should land in. From there, choose where each document goes:
- Selected folder — every document lands in the folder you chose.
- Subfolder from merge fields — a path under that folder built from
merge fields, for example
Accounts/{$data.record.field_values.account}/Locations/{$data.record.field_values.site}. Levels that do not exist yet are created; levels that do are reused, regardless of letter case. Each segment of the path comes from the template, never from a value: a field value that contains a slash fails the delivery instead of adding a level. - Folder id from a merge field — a merge field that holds the Box
folder id itself. The
id={$FolderId}form some tools use also works.
The file keeps the report's output filename. A file description is optional and accepts merge fields; Box allows 256 characters, and a longer resolved description fails the delivery rather than being cut off. The editor previews the folder path and the description against the sample data and names any field that has no value.
If a file with the same name already exists in the target folder, the destination either keeps both (the new file gets a short suffix) or replaces it by uploading a new version of the same Box file, so its id and sharing stay intact. Keeping both is the default.
Callback. Optionally, give the destination a URL to call after the
file is confirmed in Box. It receives a report.delivery.succeeded event
in the standard webhook envelope, signed with the secret you set:
{
"id": "…",
"event": "report.delivery.succeeded",
"timestamp": "2026-09-04T16:00:00Z",
"organization_id": "…",
"resource_type": "report_delivery",
"resource_id": "…",
"data": {
"report": { "id": "…", "name": "Designated UST Operator Visual Inspection" },
"merge_run_id": "…",
"delivery": { "id": "…", "destination_id": "…", "destination_type": "box", "destination_label": "…", "delivered_at": "…", "attempt": 1 },
"file": { "name": "2026-9-4_Inspection_SA-70024.pdf", "mime_type": "application/pdf", "size_bytes": 812345, "checksum": "sha256:…" },
"provider": { "type": "box", "file_id": "…", "version_id": "…", "folder_id": "…", "folder_path": "Accounts/…/Reports", "url": "https://app.box.com/file/…", "description": "…" },
"merge_data": { "…the data the document was merged from, unchanged…" }
}
}The callback is sent only after the upload and the description are
confirmed and the receipt is saved, so it can safely start whatever
comes next in your process. provider is the only part that differs by
destination type; a receiver reads provider.type before reading inside
it. merge_data is exactly what went into the merge, so the receiver
has every field without a second lookup. If the callback fails after its
retries, the Box delivery still stands; the callback shows separately in
the delivery history and can be sent again from there.
Error email. Optionally, list addresses to email when a Box delivery gives up: retries exhausted, the authorization no longer valid, or a result that needs a person to look. One email per failure, naming the report, the destination, the stage, the error, and the delivery id. A callback that gives up sends the same email, marked as the callback rather than the upload.
Google Drive
The finished document is uploaded into a folder in Google Drive, either in a person's My Drive or in a shared drive. Each Google Drive destination is authorized separately: save the destination, then use Authorize Google Drive on the report's page to sign in with Google and grant access. Google's own consent screen does the signing in; the platform never sees your Google password, and the tokens it receives stay on the server. A Google Workspace administrator may need to allow the FieldScroll app once if the company restricts third-party apps. Until Google finishes reviewing the app, the consent screen shows a notice that the app is unverified; choose Advanced to continue.
Once authorized, pick the folder the documents should land in. The picker starts in My Drive and lists every shared drive the account can see; switch between them at the top. From there, the routing choices are the same as for Box: the selected folder, a subfolder path built from merge fields, or a folder id held in a merge field. Levels that do not exist yet are created; levels that do are reused. Google Drive allows several folders with the same name in one place, so when that happens the oldest one is used, every time.
The file keeps the report's output filename. A file description is optional and accepts merge fields; it is set together with the upload, and a resolved description over 1000 characters fails the delivery rather than being cut off.
Files with the same name. Google Drive, unlike Box, allows several files with the same name in one folder. So the choice matters more here:
- Add a new file (the default) — a new file is added beside any existing file with that name.
- Replace the file with the same name — the most recently changed file with that name gets a new revision, keeping its id and sharing. Google keeps earlier revisions for a while and you can restore one from Drive's revision history.
Files uploaded into a My Drive folder belong to the account that authorized the destination and count against its storage. Files uploaded into a shared drive belong to the shared drive and count against the organization's storage.
The callback and error email work exactly as for Box. In the callback,
provider.type is google_drive and the block carries the Drive file
id, the revision id, the folder id and path, the shared drive id (null
for My Drive), a link, and the description:
"provider": {
"type": "google_drive",
"file_id": "…",
"revision_id": "…",
"replaced_revision_id": null,
"folder_id": "…",
"folder_path": "Accounts/…/Reports",
"drive_id": null,
"url": "https://drive.google.com/file/d/…/view",
"description": "…"
}replaced_revision_id is set only when the destination replaced an
existing file, and names the revision it replaced.
Signing secrets, for the webhook destination and for the Box and Google Drive callbacks, are write-only. Once saved, the value cannot be read back — not in the dashboard, not through the API, and not by any query a signed-in browser session can make. They show only that a secret is set. Leave the field blank to keep the stored secret, or enter a new value to replace it. If you lose your copy, set a new secret and update your receiver.
Wiring a template to events
The usual path is a webhook subscription. To generate a report
whenever something happens, create a
webhook subscription that targets the
template, and pick the events that should trigger it —
record.created is the usual choice. From then on, every matching
event renders and delivers, typically within a minute. Nothing else
to set up — the platform holds the wiring.
Generating a report from your own service
Some workflows need processing the platform does not do — a call to another API, data joined from several systems. For those, do the work in your own service and invoke the render yourself:
- A webhook subscription sends the event to your service.
- Your service does its processing.
- Your service POSTs the finished merge data to the template's merge URL — shown on the template's page in the dashboard, along with the secret key to send as the bearer token.
The call returns 202 with a merge_run_id; the render and delivery
run through the same queue and retry machinery as a platform-wired
subscription. The document is rendered once, then a delivery appears in
the template's history for each destination.
The secret key only unlocks that one template, which makes it the right credential to hand a third-party service. If you would rather authenticate with an organization-level API key, the API merge endpoint queues the same pipeline.
Retries and delivery history
Delivery is queued and retried. A failed send — a rejected address, a mail-server timeout — retries automatically up to 2 more times (3 attempts in total), with a growing delay between attempts. The template's detail page lists every delivery with its status (pending, delivering, delivered, failed, or needs attention), where it was going, and the next retry time when one is scheduled. A Box or Google Drive delivery that succeeded shows a link to the file, and its callback appears as its own row with its own status.
Each destination retries on its own, so a webhook endpoint that is down does not delay the email, and only the failed delivery is retried.
A delivery that exhausts its retries stays marked failed in the history, so nothing disappears silently, and any delivery marked failed or needs attention can be retried from the history. If a delivery keeps failing, check where it was going first — a rejected address, an endpoint returning an error, or a Box or Google Drive authorization that has expired is the most common cause.
Needs attention is reserved for one case: a Box destination set to replace an existing file found that the file changed in Box between being read and being written. Nothing was overwritten. Check the file, then retry. Google Drive has no equivalent check, so a Google Drive delivery never lands in this state: replacing a file there is last write wins, and the callback records which revision was replaced.
What does not exist yet
- Destinations are email, webhook, Box, and Google Drive. OneDrive and Dropbox are not built yet; a webhook destination is the way to move a document into other storage today.
- A Box or Google Drive authorization belongs to one destination. Two reports delivering to the same account are authorized twice.
- Delayed delivery and conditional delivery are not built. A report delivers on every merge that reaches it; to deliver only some events, filter what triggers the report.
- No Word or Excel template uploads — templates are HTML by design (the builder guide explains why, and how AI design tools fit the workflow).