wkhtmltopdf alternative: migrating to a modern renderer
, 6 min read
wkhtmltopdf turned HTML into PDF for more than a decade, and plenty of apps still shell out to it. The project is now archived on GitHub, its last release is 0.12.6 from June 2020, and its own status page says the WebKit inside it hasn’t been updated since 2012. This guide moves a wkhtmltopdf call to a current Chromium renderer, flag by flag.
Why move
- Security. The project’s status page says: do not use wkhtmltopdf with any untrusted HTML, because it can lead to a complete takeover of the server it runs on.
- CSS. An engine frozen in 2012 doesn’t know much of today’s CSS. Layouts that need workarounds there (floats instead of flexbox or grid,
-webkit-boxhacks) just work in Chromium. - No fixes are coming. An archived repository takes no issues or pull requests.
Before
wkhtmltopdf --page-size A4 -O Portrait \
-T 20mm -B 20mm -L 16mm -R 16mm \
--print-media-type \
--footer-center "Page [page] of [topage]" --footer-font-size 8 \
invoice.html invoice.pdfAfter
curl https://api.pagenull.dev/v1/pdf \
-H "Authorization: Bearer $PAGENULL_API_KEY" \
-H "Content-Type: application/json" \
-o invoice.pdf \
--data-binary @- <<EOF
{
"html": $(jq -Rs . < invoice.html),
"format": "A4",
"margin": { "top": "20mm", "bottom": "20mm", "left": "16mm", "right": "16mm" },
"pageNumbers": "Page {page} of {pages}",
"pageNumbersPosition": "bottom-center"
}
EOFjq -Rs . turns the HTML file into a JSON string. In application code you’d build the JSON with your language’s own library instead; see the guides for Python, PHP and Laravel and Node.js.
Flag by flag
| wkhtmltopdf | pagenull |
|---|---|
--page-size A4 | format: "A4" (or width + height) |
-O Landscape | landscape: true |
-T -B -L -R | margin.top, .bottom, .left, .right |
--print-media-type | The default here. For screen styles: emulateMedia: "screen" |
--no-background | printBackground: false |
--zoom 0.9 | scale: 0.9 |
--header-html header.html | headerHtml: "<div>…</div>" (the markup, not a file) |
--footer-center "[page] / [topage]" | pageNumbers: "{page} / {pages}", pageNumbersPosition: "bottom-center" |
--disable-javascript | javascript: false |
--javascript-delay 2000 | waitFor: "#ready": wait for an element instead of a fixed delay |
--grayscale | No equivalent. Use grayscale CSS in a print stylesheet. |
CSS you can drop
page-break-inside: avoidon table rows anddisplay: table-header-grouponthead: the print reset sets both by default. See CSS for print.- Float- and table-based layouts kept for the old engine: flexbox and grid print correctly.
- Fixed pixel widths that matched wkhtmltopdf’s default DPI: size the page with
@pageand lengths in mm.
Render a few real documents with "test": true (free, watermarked) and compare them page by page before switching. Expect differences in line breaks, because the font rendering is newer.
Sources
Facts about other projects and rules, checked on 4 November 2026.