Handlebars cheat sheet
Transactional uses Handlebars.js to interpolate your variables into the template's HTML before rendering to PDF. This page is the short, opinionated subset that actually works in our sandbox.
Variables
{{ name }} — escaped (safe by default)
{{{ raw }}} — unescaped (only when you really want HTML)
{{ customer.name }} — nested path
{{ ../parent }} — go up one scope
{{ [key with spaces] }} — bracketed key
{{ this }} — current context (useful inside #each)
Escape rules: {{ x }} HTML-escapes <, >, &, ", '. Use the triple-stash form {{{ x }}} only when the variable already contains HTML you trust.
Conditionals
{{#if paid}}
<span class="text-emerald-600">PAID</span>
{{else}}
<span class="text-rose-600">DUE</span>
{{/if}}
{{#unless trial}}
<p>Billing starts next cycle.</p>
{{/unless}}
Falsy values (the else branch runs): false, null, undefined, 0, "", [].
Loops
{{#each items}}
<div class="flex justify-between">
<span>{{label}}</span>
<span>{{amount}} €</span>
</div>
{{else}}
<p>No items.</p>
{{/each}}
Inside #each, special variables are available:
| Variable | Value |
|---|---|
{{this}} | The current item |
{{@index}} | 0-based position |
{{@key}} | The key, when iterating over an object |
{{@first}} | true for the first iteration |
{{@last}} | true for the last iteration |
{{../x}} | Access the parent scope |
{{#each lineItems}}
<tr class="{{#if @last}}border-b-2{{else}}border-b{{/if}}">
<td>{{label}}</td>
<td>{{../currency}} {{amount}}</td>
</tr>
{{/each}}
With (change scope)
{{#with customer}}
<p>{{name}}</p>
<p>{{address.line1}}</p>
{{/with}}
Equivalent to writing {{customer.name}} and {{customer.address.line1}}, but cleaner when you're inside one nested object for a while.
Lookup
{{lookup data key}}
Used to access a property whose key is itself a variable. Rare in print templates — usually you can restructure the data instead.
Comments
{{!-- this is a comment, won't render --}}
Whitespace control
{{~ trim }} — strip whitespace before
{{ trim ~}} — strip whitespace after
Mostly useful inside <pre> or where leading/trailing whitespace would matter. PDFs rarely care.
What Transactional adds on top
Nothing. We do not ship custom helpers (no date formatters, no math, no currency). If you find yourself wanting {{formatDate created "DD/MM/YYYY"}}, pre-format on your side and pass "createdDisplay": "23/05/2026" in variables.
This is deliberate: it keeps templates portable and your variable schema explicit. The AI assistant follows the same rule.
Common patterns
Optional sections
{{#if logoUrl}}
<img src="{{logoUrl}}" class="h-12" alt="Logo" />
{{/if}}
Default text via the data layer
Send a default in variables rather than wishing Handlebars had a default helper:
<h1>{{title}}</h1> {{!-- always renders --}}
{ "title": "Welcome" } — pass a default from your code
Multi-language
Pre-compute the localized strings on your side:
{
"labels": {
"invoice": "Facture",
"total": "Total"
},
"invoice": { "number": "INV-2026-0142" }
}
<h1>{{labels.invoice}} {{invoice.number}}</h1>