Invoice PDFs that match the invoice page

Your billing page already computes tax, currency and line items correctly. Render that page. Stop maintaining a second, divergent PDF template.

A rendered invoice PDF: Acme, Inc. letterhead, line items and a $1,240.00 total, page 1 of 1.

Two templates, one truth

The HTML invoice gets a VAT fix. The PDF template does not. Six weeks later an auditor finds both versions.

Totals split across pages

A long line-item table puts the total block alone on page three, with no header. Customers ask whether the invoice is complete.

The first of the month

Forty thousand invoices in one window. Your own renderer is sized for the average day, not for that hour.

Currency and script coverage

A Japanese company name renders as boxes because the container has no CJK font. Nobody notices until the customer replies.

01

Sign a URL for the invoice so the renderer can reach it without making it public.

02

Wait for the total with a selector, so a slow tax call never produces a half-rendered invoice.

03

Repeat the header so page two still says which invoice it belongs to.

04

Store it with long retention and keep the returned checksum next to the invoice row.

const pdf = await client.render({ url: signedInvoiceUrl(invoice.id), format: "A4", margin: { top: "18mm", bottom: "18mm" }, wait_for: "#invoice-total", header_html: `<div>Acme · Invoice ${invoice.number}</div>`, footer_html: "<div>{page} / {total}</div>", pdf_metadata: { title: `Invoice ${invoice.number}` }, store: true, retention: "7y" }); await db.invoices.update(invoice.id, { pdf_url: pdf.url, pdf_sha256: pdf.checksum });

wait_for

Print only once the total is on screen. No more invoices ending in “—”.

header_html

The invoice number on every page, without touching your CSS.

POST /api/batch

The monthly run in chunks of 500, with per-job results.

retention

Keep the file as long as your accounting rules require, then let it expire.