Writing HTML templates for PDF: a style guide (Jinja, Django, Handlebars)

Templates that render well as PDFs follow a few rules: fixed page geometry, no external state, defensive filters, print CSS. Examples in Jinja and Django syntax.

Updated 31 August 2026

Web templates assume a scrolling viewport and a user who can resize. PDF templates assume a fixed page and no user. Different rules.

Geometry in millimetres

Set `@page { size: A4; margin: 12mm }` and design inside 186 × 273 mm. Pixels are fine for text sizes; use mm for anything that must line up with paper — margins, columns, signature lines.

No external state

The template gets everything from the context. No fetching, no `now()` calls that differ between preview and render, no relative URLs. Dates arrive formatted; money arrives formatted; the template only places them.

Defensive filters

Every variable that might be empty gets a default: `{{ client_vat|default:"—" }}` (Django) or `{{ client_vat or "—" }}` (Jinja). An empty cell is a data problem; a missing cell that shifts the layout is a template problem.

Loops for line items

{% for line in lines %}
<tr><td>{{ line.description }}</td><td class="num">{{ line.qty }}</td><td class="num">{{ line.total }}</td></tr>
{% endfor %}

Compute totals in code, pass them in. Templates that do arithmetic are templates that round differently from your ledger.

Print CSS

`break-inside: avoid` on rows and cards, `thead { display: table-header-group }`, and a visible footer with `{{page}} / {{pages}}` through the renderer's header/footer option rather than CSS tricks.

Preview at scale

Render the longest real document you have, not a fixture. Page breaks, repeated headers and overflowing addresses only show up with real data.

Try it: convert HTML to PDF in the browser, or get a free API key — 50 documents a month.

More guides

Issuing 5,000 course certificates in one afternoon

A spreadsheet of names, one landscape template, batch requests of 100, and signed links per student. The full workflow including naming, verification codes and delivery.

Generating receipts at checkout without slowing the checkout

The receipt PDF should never sit between the customer and the order confirmation. A pattern: confirm first, render asynchronously, attach when ready.

URL-to-PDF and SSRF: how a PDF renderer becomes an attack surface

A renderer that fetches URLs can be pointed at your internal network. The attacks, the defences, and what to demand from a PDF API vendor.

Why the PDF from headless Chromium differs from Print to PDF in the browser

Same engine, different output: viewport, print media, backgrounds, default margins, headless font rendering. A list of the differences and the option that fixes each.