Log In Sign Up

Blog

wkhtmltopdf is deprecated: 2026 migration

For a decade, wkhtmltopdf was the default answer to “HTML to PDF on Linux”: a single binary, no browser to manage, one command. If you’re reading this, some cron job or CI step in your life probably still calls it.

The project is over. The GitHub repository is archived (read-only, no issues, no fixes); the last commit landed in November 2022 and the last release, 0.12.6, in 2020. The engine inside is Qt WebKit, frozen around 2015. And yet apt install wkhtmltopdf still works on Ubuntu 24.04, which is exactly why so many systems still quietly depend on it. This post is what it actually renders in 2026 (we tested it, as with everything in our HTML-to-PDF series), why “it still mostly works” is the trap, and how to migrate with the least drama.

What 0.12.6 renders in 2026

We ran the same two documents used across this series: a modern invoice (Flexbox, CSS Grid, woff2 font, inline SVG) and a Chart.js report. Chromium reference renders are in the PHP round.

The invoice came out better than we expected, and worse than you need:

The invoice through wkhtmltopdf: header and address blocks correct, but the CSS Grid line items collapsed into stacked full-width rows

wkhtmltopdf’s invoice: close, until the line items. Download the PDF

The surprises on the good side: the Flexbox header and address blocks laid out correctly (old WebKit had an early flexbox implementation), the inline SVG logo rendered, and the woff2 web font loaded. Then the ceiling: the CSS Grid line items exploded into stacked full-width rows, the same failure dompdf and mpdf produce, because Grid postdates the engine. One page of HTML became two, in 2.9 seconds, and the output PDF weighs 212 KB where Chromium’s equivalent is 69 KB.

The chart report is the sharper lesson. wkhtmltopdf runs JavaScript, which was always its party trick over the pure libraries, so we gave it --javascript-delay 4000 and waited:

The chart report through wkhtmltopdf: stat tiles stacked vertically and both chart panels completely empty

Four seconds of JavaScript delay, zero charts. Download the PDF

Empty chart panels anyway. A 2015 JavaScript engine can’t parse the modern syntax that Chart.js 4, and increasingly any current library, ships. Having a JS engine isn’t the same as having one from this decade: your --javascript-delay is real, the script just fails to parse before the delay even starts mattering.

Why “it still mostly works” is the trap

Three separate clocks are running against a frozen engine:

  1. CSS keeps moving. Grid is already broken; every stylesheet your team writes drifts further from what 2015 WebKit understands, and each new template becomes a negotiation with a renderer nobody is fixing.
  2. JavaScript already moved. Current builds of the chart and framework libraries you’d reach for don’t parse at all, as demonstrated above.
  3. Security stopped. An archived project means no patches, and wkhtmltopdf is a full (old) browser engine that parses HTML, CSS, images, and scripts. If any of that content is user-influenced, invoices with customer-supplied names are, you’re feeding untrusted input to an unpatched browser from 2015. This alone justifies the migration for most teams.

The flag-by-flag migration map

The pleasant surprise of migrating off wkhtmltopdf is that its mental model, URL in, PDF out, options as flags, maps almost one-to-one onto both self-hosted headless Chromium and the capture API:

wkhtmltopdfPuppeteer / PlaywrightCapture API
wkhtmltopdf URL out.pdfpage.goto(URL) + page.pdf()POST /jobs with url
--page-size A4format: 'A4'page_size=A4
--orientation Landscapelandscape: truelandscape=true
--margin-top 20mm (etc.)margin: { top: '20mm' }margin_top=2cm (etc.)
--header-html / --footer-htmlheaderTemplate / footerTemplateheader_template / footer_template
[page] / [topage] in headers<span class="pageNumber"> / totalPagessame Chromium classes
--javascript-delay 4000waitUntil: 'networkidle0'wait_time=4 or wait_network=true
--window-status donepage.waitForSelector(...)wait_css_selector=...
--username / --passwordpage.authenticate(...)username / password
--print-media-type (opt-in)print media is the defaultdefault (screen=true to flip)

Two behaviour changes to expect, both improvements: Chromium uses print media by default (wkhtmltopdf made you ask with --print-media-type), and backgrounds need printBackground: true in Puppeteer (the API prints them by default; we verified).

Picking the destination

Where to land depends on what your documents need, and this series has tested each path with the documents above:

# The wkhtmltopdf muscle-memory version:
#   wkhtmltopdf --page-size A4 --javascript-delay 4000 URL out.pdf
curl -u "$CONVERTERER_API_KEY:" https://api.converterer.com/jobs \
  -d url="https://yourapp.example/invoices/1024" \
  -d page_size=A4 \
  -d wait_network=true \
  -d file_name="invoice-1024.pdf"

However you migrate, do it before the next template redesign rather than after: the gap between what your designers write and what 2015 WebKit renders only ever widens, and the project maintaining the bridge went read-only three years ago.