Template guide
A template is HTML with Handlebars in it. It is compiled when you store it, so a template that can never render is refused at POST /v1/templates rather than the first time you use it in anger.
Syntax
{{value}} inserts an escaped value. {{{value}}} inserts it unescaped. Only do that with content you produced yourself. A field that is missing renders as nothing rather than failing the whole document, because one absent optional value should not cost you an invoice.
<h1>Invoice {{number}}</h1>
{{#if buyer.tax_id}}
<p>VAT {{buyer.tax_id}}</p>
{{/if}}
<table>
<thead><tr><th>Item</th><th>Amount</th></tr></thead>
<tbody>
{{#each items}}
<tr>
<td>{{this.description}}</td>
<td>{{formatCurrency this.amount currency=../currency}}</td>
</tr>
{{/each}}
</tbody>
</table>Helpers
Five formatting helpers and four block helpers, and nothing else. An unknown helper is a compile error rather than a silent blank, and there is deliberately no helper that reads a file, opens a socket or evaluates a string, because every template we render arrived over the internet.
| Helper | What it does |
|---|---|
formatDate{{formatDate issued_on locale="en-GB" dateStyle="long"}} | Formats an ISO date or timestamp through Intl.DateTimeFormat. Options: locale, dateStyle, timeStyle, year, month, day, timeZone. Defaults to en-US and a medium date. |
formatCurrency{{formatCurrency total currency="EUR" locale="de-DE"}} | Formats a number as money. The value is in major units: 1250.5, not 125050. Options: currency, locale, minimumFractionDigits, maximumFractionDigits, useGrouping. |
formatNumber{{formatNumber weight maximumFractionDigits=2}} | Grouping and decimals through Intl.NumberFormat, without a currency symbol. |
uppercase{{uppercase status}} | Upper-cases a value. |
lowercase{{lowercase email}} | Lower-cases a value. |
Block helpers
if / else{{#if due_on}}Due {{formatDate due_on}}{{/if}}unless{{#unless paid}}Payment outstanding{{/unless}}each{{#each items}}<tr><td>{{this.description}}</td></tr>{{/each}}with{{#with buyer}}{{name}}, {{city}}{{/with}}
Page breaks
Chromium prints your CSS, so the standard fragmentation properties are what control pagination. These five rules cover almost every document.
/* Never split a table row across pages. */
tr { break-inside: avoid; }
/* Repeat the column headings on every page. */
thead { display: table-header-group; }
/* Keep the totals block together. */
.totals { break-inside: avoid; }
/* Start each section on a fresh page. */
.chapter { break-before: page; }
/* Do not leave a heading stranded at the foot of a page. */
h2 { break-after: avoid; }thead { display: table-header-group } is the one people miss. Without it a fifty-line invoice has column headings on page one and unlabelled numbers on pages two and three.
Fonts
The renderer ships Noto Sans, Noto Serif, Noto Sans Mono, Noto CJK, DejaVu and Liberation, covering Latin, Cyrillic, Greek, Arabic, Hebrew and CJK. Naming one of those costs no network request. Non-Latin coverage is where most HTML-to-PDF services quietly fall back to boxes.
/* Installed in the renderer, so no network request. */
body { font-family: 'Noto Sans', sans-serif; }
.figures { font-family: 'Noto Sans Mono', monospace; }
.heading { font-family: 'Noto Serif', serif; }
/* Or bring your own over HTTPS. */
@font-face {
font-family: 'Your Brand';
src: url('https://cdn.example.com/brand.woff2') format('woff2');
font-display: block;
}A @font-face over public HTTPS works too, subject to the same egress rules as any other subresource: private and loopback addresses are refused, and the whole document is capped at 20 MB across at most 100 subresources. Set "wait_for": "networkidle", which is the default, so the render does not capture the page before the font arrives.
Limits
- Template source and inline HTML: 5 MB. Reference large assets by URL rather than inlining them.
- Render time: 15 seconds by default, up to 60 with
timeout_ms. Past it you getrender_timeoutand no charge. - Data nesting: 32 levels. Prototype-polluting keys are dropped before your template ever sees them.
- Free-plan renders run with JavaScript disabled, so a document that builds itself with a script renders differently there than on a paid plan.
Complete templates
The recipes are whole documents you can store as they are: invoice, receipt, certificate, report and shipping label.