Migrating off wkhtmltopdf: a checklist that avoids the surprises

A practical sequence for replacing wkhtmltopdf in a production system: inventory, flag mapping, visual diff, cutover, rollback.

Updated 31 August 2026

wkhtmltopdf is archived and its WebKit is old, but it is also embedded in systems that have printed invoices correctly for years. Replacing it is a migration, not a swap. Here is the order that works.

1. Inventory the call sites

Grep for `wkhtmltopdf` and for the wrappers (`pdfkit`, `wicked_pdf`, `snappy`, `laravel-snappy`). List every flag in use. Most systems use six: page size, orientation, margins, print-media-type, header/footer, and zoom.

2. Map the flags

  • `--page-size A4` → `format: "A4"`
  • `--orientation Landscape` → `landscape: true`
  • `--margin-*` → `margin` (one value or four)
  • `--header-html file.html` → `header_html` inline; `[page]`/`[topage]` become `{{page}}`/`{{pages}}`
  • `--print-media-type` → always on in a modern renderer
  • `--zoom 0.8` → `scale: 0.8`
  • `--disable-smart-shrinking` → not needed; Chromium does not shrink

See the full mapping table.

3. Expect the CSS to look better — and different

Old WebKit ignored most of flexbox and grid; your templates may contain workarounds (tables for layout, fixed pixel widths, `float` everywhere) that were compensating. In a modern engine those workarounds still render, but any CSS that was silently dropped now applies. Diff visually, page by page, on your five most common documents.

4. Fonts

wkhtmltopdf used the fonts installed on the server. A hosted renderer has its own. If your templates rely on a system font, ship it as a web font (see the fonts guide) or accept the metric-compatible fallback.

5. Run both in parallel

For a week, generate every document with both engines and store the new one alongside the old. Compare a sample daily. This costs almost nothing and catches the template with the odd `

` nobody remembered.

6. Cut over behind a flag

A feature flag per document type lets you move invoices on Monday and contracts on Thursday, and flip back in seconds if a customer reports a problem.

7. Remove the binary

Only after the flag has been on for a full billing cycle. Then delete the Dockerfile lines, the apt packages and the fontconfig hacks — that is the payoff.

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

More guides

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.

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.