wkhtmltopdf Alternative: What To Use In 2026

By · Last updated August 2026 · 12 min read

Migrating off wkhtmltopdf: pick an alternative by stack, migrate safely, and verify the new PDF rendered correctly.

wkhtmltopdf still works. That's the trap. It runs in your build, it makes a PDF, and nothing looks broken, so it sits in production for years after it stopped being safe.

The best wkhtmltopdf alternative depends on your stack: Puppeteer or Playwright for JavaScript and modern CSS, WeasyPrint for Python with static HTML, Gotenberg for a self-hosted service, a Prince engine like DocRaptor for heavy print, or a managed API to skip the browser entirely. The harder part is migrating without your invoices silently breaking, and proving they didn't. This guide covers both.

I build propzapi, a render API, so I'll show it where it fits. But the migration steps and the CSS traps apply whatever you pick, and I'll tell you where other tools are the better call.

Is wkhtmltopdf safe to use in 2026?

No. The wkhtmltopdf repository was archived on January 2, 2023, its last release was 0.12.6 in June 2020, and it carries CVE-2022-35583, an unpatched server-side request forgery flaw scored 9.8 (critical). An attacker injects an iframe pointing at an internal IP and reaches assets behind your firewall. It runs, but it's known-vulnerable, unmaintained code.

The security hole is the part that shows up in an audit.

CVE-2022-35583 was published in August 2022 and never fixed, because there's no one left to fix it. It's a straight path from a user-supplied HTML field to your cloud metadata endpoint. If you render any HTML you didn't fully author, you're exposed.

The engine is what your users see. wkhtmltopdf uses a fork of Qt WebKit from the Qt 4.8 era, which predates flexbox, CSS grid, custom properties, and calc(). Modern layout renders wrong or not at all, so teams end up hand-writing table-based HTML from 2012 to keep it happy.

Both problems have the same root: the project is abandonware. Doppio, DocRaptor, and Iron Software all reached the same call independently: treat it as end-of-life and move.

What should you replace wkhtmltopdf with?

Pick by what you need to render and how much infrastructure you want to own. Puppeteer or Playwright run real Chromium for JavaScript and modern CSS. WeasyPrint is pure Python with no browser but no JavaScript. Gotenberg is a self-hosted Docker service. A Prince engine like DocRaptor wins on multi-page print. A managed API removes the browser from your stack for a per-render fee.

Here's the field, honestly.

AlternativeEngineRuns JS?You run it?Best for
Puppeteer / PlaywrightChromiumYesYesJS-built pages, modern CSS, full control
WeasyPrintOwn (Python)NoYesStatic HTML in a Python app, low footprint
GotenbergChromiumYesYes (Docker)A self-hosted HTTP service for any language
DocRaptorPrinceLimitedNoMulti-page print, running headers/footers
propzapiChromiumYesNoSingle-page invoices, verify the output, PAYG

Most people land in one of two camps: run Chromium yourself (Puppeteer, Gotenberg) for control, or call an API (propzapi, DocRaptor, PDFShift) to skip the ops. The rest of this guide is about picking between them and migrating cleanly.

Which alternative fits your stack?

Your language wrapper is the tell. Rails wicked_pdf, PHP KnpLabs/snappy, and .NET DinkToPdf all shell out to the same dead wkhtmltopdf binary, so the fix is stack-wide, not a one-line swap. Node moves to Puppeteer or Playwright, Python to WeasyPrint or Playwright, and any stack can move to a hosted API by replacing the shell call with one HTTP request.

The uncomfortable truth: your framework's PDF gem is probably a wrapper.

Rails developers on wicked_pdf filed "long-term plans given the deprecation of wkhtmltopdf?" back when the archive happened. PHP's KnpLabs/snappy has the same open thread. .NET's DinkToPdf wraps the identical binary. None of them render the PDF. wkhtmltopdf does, and they just build its command line.

So the migration is the same shape everywhere. Node teams reach for Puppeteer or Playwright. Python teams pick WeasyPrint for static documents or Playwright when they need JavaScript.

Rails teams move wicked_pdf's views to Grover, a gem that drives Puppeteer, or render the same view through a hosted API. .NET teams on DinkToPdf move to a maintained Chromium library or an API call, since DinkToPdf ships the same abandoned binary underneath. PHP teams on snappy do the equivalent.

And any stack can drop the binary entirely by turning the render into an HTTP call.

That last option is why a hosted API is the least-effort path: you delete the wrapper and the binary, and your PDF code becomes one request you can write in any language.

Self-host Chromium or use a hosted PDF API?

Self-hosting Puppeteer or Gotenberg has no license fee, but Chromium uses roughly 6 to 11 times the CPU and RAM of a lightweight PDF library (per Iron Software's 2026 tool comparison), and you own patching, cold starts, font management, and the AWS Lambda 250MB unzipped limit. A hosted API trades all of that for a per-render fee. Low, steady volume favors self-host; a serverless or spiky app usually favors the API.

The license being free is not the same as it being cheap.

A headless-Chromium render is real work: a few hundred milliseconds of CPU and a chunk of RAM per document. Run a few of those concurrently on a small box and you're tuning memory limits and browser recycling instead of shipping features. On AWS Lambda, the full puppeteer package's bundled Chromium (~170MB) blows past the 250MB unzipped limit, so you end up on puppeteer-core plus a slimmed Chromium layer, minding cold starts.

Gotenberg softens this by giving you one Docker service to point at, which is a real improvement over a binary per app. You still run and scale it.

A hosted API removes that surface: no Chromium in your function, no CVE to track, no cold-start tuning. You pay per render instead. The math is volume against ops time, and for most teams shipping a product, ops time is the more expensive number.

Choosing a wkhtmltopdf alternative: self-host Chromium (Puppeteer, Gotenberg) for control versus a hosted API (propzapi, DocRaptor) to skip the ops.
The real fork is whether you run the browser yourself or pay someone else to.

How do you migrate without breaking your PDFs?

Do it one template at a time: replace the shell command with the new call, map the flags (--page-size A4 becomes the tool's A4 option), then visual-regression test the output. The engine changes from Qt WebKit to Chromium, so margins, page breaks, and table layout shift, and a document that looked right can move. Confirm each renders correctly, then delete the binary. Budget a focused day or two per template.

The one-line command becomes a one-line call.

# The old way: shell out to an abandoned binary with an unpatched 9.8 CVE.
wkhtmltopdf --page-size A4 --print-media-type invoice.html invoice.pdf
# wkhtmltopdf 0.12.6 (last release June 2020, repo archived Jan 2023)

Turns into this, a request you can make from any language, with no binary in your image:

# The replacement: one HTTP call, no binary to patch, real Chromium rendering.
curl https://api.propzapi.com/v1/screenshot \
  -H "X-API-Key: pk_live_…" -H "Content-Type: application/json" \
  -d '{"url":"https://yourapp.com/invoice/1042","format":"pdf","paper":"A4"}'
# → { "url": "https://images.propzapi.com/shot_2ef7….pdf", "format": "pdf" }

Or, if you want to keep the render in your own process, the self-hosted Puppeteer version does the same job. You trade the API fee for owning the browser:

// If you'd rather run it yourself: the Puppeteer (Node) replacement.
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
await page.goto("https://yourapp.com/invoice/1042", { waitUntil: "networkidle0" });
const pdf = await page.pdf({ format: "A4", printBackground: true });
await browser.close();
// You now own the browser: the RAM, the cold starts, and the patching.

The flag mapping is mechanical. Most wkhtmltopdf options have a direct equivalent in a Chromium-based tool or a hosted API.

wkhtmltopdf flagWhat it didModern equivalent
--page-size A4Paper sizeA4 paper option / format: "A4"
--orientation LandscapeRotate the pagelandscape flag
--print-media-typeUse print CSSprint media emulation (Chromium defaults to it)
--margin-top 10mmPage marginsa margin object per side
--javascript-delay 1000Wait for JSwait-for-network-idle or an explicit wait
--header-htmlRunning headera header template (or CSS @page in print engines)

The one flag with no clean equivalent is --disable-local-file-access. You set it to blunt the SSRF risk. On a maintained tool that guard is built in, which is the point of leaving the old engine behind.

The layout shift is the part that bites. Because Chromium honors CSS that Qt WebKit ignored, a table that fit on one page in wkhtmltopdf may reflow, and a margin you tuned for the old engine may now be wrong. This is why every honest migration ends in visual-regression testing, not a find-and-replace. Once each template checks out, remove the wkhtmltopdf binary so CVE-2022-35583 leaves your attack surface for good.

How do you prove the new PDF actually renders right?

This is where every alternative guide stops, at "re-test the CSS," meaning eyeball it. With propzapi you assert it instead: pass verify with the exact strings you expect, like the invoice number and total, and the response returns verified plus a per-string check for each one found and un-clipped. It confirms the values rendered as visible pixels. On a document where a shifted total is expensive, that replaces a manual spot-check with an automated assertion.

Think about what actually goes wrong in a migration.

You switch engines, the layout moves, and a total that used to sit inside its box now clips at the page edge. You render a thousand invoices overnight. Nobody eyeballs a thousand PDFs, so the broken ones ship, and you find out when a customer emails a screenshot.

Verify closes that gap in the same call that makes the PDF.

# The part no alternative list covers: prove the output rendered right.
curl https://api.propzapi.com/v1/screenshot \
  -H "X-API-Key: pk_live_…" -H "Content-Type: application/json" \
  -d '{"url":"https://yourapp.com/invoice/1042","format":"pdf","paper":"A4",
       "verify":["INV-1042","$4,200.00"]}'
# → { "url":"….pdf", "verified":true,
#     "checks":[ {"text":"INV-1042","found":true,"clipped":false},
#                {"text":"$4,200.00","found":true,"clipped":false} ] }

It's a check inside the render pass, not OCR, and no other tool in this space offers it. Wire it into your invoice job and a clipped total becomes a caught error before the email goes out. That's the one reason to reach for propzapi on billing documents specifically, and I'd rather tell you that than pretend it wins every row above.

wkhtmltopdf alternative pricing in 2026

Self-hosting is free to license but costs you infrastructure and ops. Among hosted APIs, PDFShift starts at $9/month, PDFMonkey at €5, and DocRaptor at $15 (its $75 tier runs the Prince print engine). propzapi is pay-as-you-go from a $5 pack that never expires, with 50 renders free and no card. Commercial print engines like Prince and PDFreactor run $1,900 to $7,000 a year.

OptionModelFree tierEntry price
Puppeteer / Gotenberg (self-host)Open sourceFree$0 license + your infra
PDFMonkeySubscription20/mo€5/mo
PDFShiftSubscriptiontrial$9/mo
DocRaptorSubscription5/mo$15/mo ($75 for Prince)
propzapiPay-as-you-go50 (one-time)$5 PAYG · $29/mo

Prices verified August 2026 from each tool's pricing page. Self-host looks free until you price the RAM and the hour you spend patching Chromium; the API fees are what you pay to make that someone else's job.

Which should you choose?

Choose Puppeteer or Gotenberg if you want to run the engine yourself and have the ops capacity. Choose DocRaptor for heavy multi-page print where pagination has to be flawless. Choose propzapi when you want a hosted render with pay-as-you-go pricing and the ability to verify the output, the case where a wrong value is expensive. All of them beat staying on an abandoned binary with a critical CVE.

If you take one thing from this: the wkhtmltopdf alternatives are easy to name, and the migration is where the risk lives. Pick the tool that matches your stack, migrate one template at a time, and verify each one before you delete the wkhtmltopdf binary.

For a hosted path with verify built in, grab a free key, point it at an invoice URL, and render with verify on. Fifty renders on the house, no card, then a $5 pack or a $29 plan.

Get a free key Read the docs

Frequently asked questions

Is wkhtmltopdf safe to use in 2026?
No. The wkhtmltopdf repo was archived on January 2, 2023, the last release was 0.12.6 in June 2020, and it carries CVE-2022-35583, an unpatched server-side request forgery flaw scored 9.8 (critical) that lets an attacker reach internal assets by injecting an iframe with an internal IP. It still runs, but you're shipping known-vulnerable, unmaintained code on an end-of-life engine.
What is the best wkhtmltopdf alternative?
There isn't one best; it depends on your stack. Puppeteer or Playwright if you need JavaScript and modern CSS, WeasyPrint for Python apps with static HTML, Gotenberg for a self-hosted HTTP service, a Prince-based tool like DocRaptor for heavy multi-page print, or a managed API if you'd rather not run a browser at all. Match the tool to the job, not the hype.
How do I migrate from wkhtmltopdf without breaking my PDFs?
Swap the shell command for the new call, map the page flags (--page-size A4 becomes the tool's A4 option), then visual-regression test every template. The engine changes from Qt WebKit to Chromium, so margins, page breaks, and table layout shift, and a document that looked right can move. Confirm each one renders correctly, then delete the wkhtmltopdf binary to remove the CVE.
Does wkhtmltopdf support modern CSS like flexbox and grid?
No. wkhtmltopdf renders with a fork of Qt WebKit from the Qt 4.8 era that predates modern layout, so flexbox, CSS grid, custom properties, and calc() render wrong or not at all. Any Chromium-based replacement (Puppeteer, Gotenberg, or a hosted API) renders them the way Chrome does, which is usually why the output looks better the moment you switch.
Is it cheaper to self-host Chromium or use a hosted PDF API?
It depends on volume and ops appetite. Self-hosting Puppeteer or Gotenberg has no license fee, but Chromium uses roughly 6 to 11 times the CPU and RAM of a lightweight PDF library, and you own patching, cold starts, and the AWS Lambda 250MB limit. A hosted API trades that per-render for a fee. Low volume favors self-host; a serverless app usually favors the API.
How do I confirm my PDFs still render correctly after switching?
Most guides stop at 're-test the CSS,' which means eyeballing it. With propzapi you pass the exact strings you expect back (an invoice total, a customer name) and the response confirms each one rendered as visible pixels and wasn't clipped. On a billing document, where a shifted total is expensive, that turns a manual spot-check into an automated assertion in the render call.