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:

VariableValue
{{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>

Next steps