wkhtmltopdf Alternative
wkhtmltopdf was archived in January 2023. The rendering engine is frozen at QtWebKit circa 2012, which means no CSS Grid, no modern Flexbox, and no JavaScript framework support. An unpatched SSRF vulnerability (CVE-2022-35583, CVSS 9.8) affects every running instance. If you are looking for a wkhtmltopdf alternative, this page covers six real options: Puppeteer, Playwright, WeasyPrint, Gotenberg, Prince XML, and ScreenshotRun API. Self-hosted and managed, with honest trade-offs for each.
Why wkhtmltopdf needs replacing
The timeline tells the story. The last release (0.12.6) shipped in June 2020. The GitHub repository was archived in January 2023. The entire GitHub organization was marked as unmaintained in July 2024. Homebrew disabled the cask in December 2024. Package managers are dropping it one by one.
The rendering engine is a fork of QtWebKit frozen around 2012. That means no CSS Grid, no CSS custom properties, no modern Flexbox behavior, no calc() with mixed units, and limited JavaScript execution. React, Vue, and Angular applications render as blank pages. Chart libraries like Chart.js and D3 produce empty canvases. Bootstrap 4+ grid classes break completely because they rely on Flexbox features that the engine does not support.
The security situation is worse than the rendering. CVE-2022-35583 allows an attacker who controls the HTML input to read local files or hit internal network endpoints through SSRF. An iframe pointing to 169.254.169.254 can pull AWS EC2 metadata, including IAM credentials. CVE-2020-21365 enables directory traversal. Both vulnerabilities will never be patched because there is no maintainer.
If your compliance team runs a vulnerability scan and flags wkhtmltopdf, they are right. It is a known, unpatched, critical-severity risk sitting on your server.
The six real alternatives
Every wkhtmltopdf replacement falls into one of three categories: self-hosted browser libraries, managed APIs, or specialty rendering engines. The right choice depends on your stack, your scale, and how much infrastructure you want to manage.
| Tool | Type | CSS Grid / Flexbox | JavaScript | Setup | Best for |
|---|---|---|---|---|---|
| Puppeteer | Self-hosted (Node.js) | Full | Full (Chromium) | npm install + Chromium binary | Node.js apps, full control needed |
| Playwright | Self-hosted (multi-lang) | Full | Full (Chromium) | npm/pip/maven install + browser binary | Multi-language stacks, CI/CD pipelines |
| WeasyPrint | Self-hosted (Python) | Flexbox: yes, Grid: partial | None | pip install | Python apps with static HTML |
| Gotenberg | Self-hosted Docker API | Full | Full (Chromium) | Docker container | Kubernetes, microservices |
| Prince XML | Specialty engine | Full | Basic | License + binary | Print-quality PDFs, books, regulations |
| ScreenshotRun API | Managed API | Full | Full (Chromium) | HTTP call, no install | Teams wanting zero infrastructure |
Puppeteer and Playwright: the self-hosted path
Most developers migrating from wkhtmltopdf land on Puppeteer or Playwright first. They are free, open source, and use modern Chromium, so every CSS feature works. The migration feels natural: swap the wkhtmltopdf binary for a page.pdf() call and the output immediately looks better.
Sounds great until you look at the ops side. Chromium needs roughly 200 to 400 MB of RAM per browser instance. The Docker image adds 600 MB to 2 GB to your container. Scaling to handle concurrent PDF requests means managing a pool of browser instances, cleaning up orphaned browser tabs, and handling the occasional segfault when a page hits an out-of-memory condition. None of this is impossible, but it is real engineering work that has nothing to do with generating PDFs.
There is also the PDF file size problem. Chromium-based PDFs tend to run about five to ten times larger than wkhtmltopdf output for the same page. An invoice that was 80 KB in wkhtmltopdf might come out as 500 KB from Puppeteer. For email attachments and bulk storage, the difference adds up.
If you are already running Puppeteer or Playwright for testing and need occasional PDF output, adding page.pdf() is straightforward. If you need to generate hundreds or thousands of PDFs daily, you will spend time on infrastructure that a managed API handles for you. For a deeper comparison, see the Puppeteer vs screenshot API and Playwright vs screenshot API breakdowns.
WeasyPrint: the lightweight Python option
WeasyPrint takes a different approach entirely. It is a Python library that converts HTML and CSS to PDF without running a browser. No Chromium, no headless process, no zombie tabs. Memory usage stays low, and the output PDFs are compact.
The limitation is clear: no JavaScript at all. If your HTML template is static, generated server-side, and styled with CSS, WeasyPrint handles it well. Flexbox works. CSS Grid has partial support. But anything that relies on client-side rendering, a React component, a Chart.js graph, a dynamically loaded table, produces blank or broken output.
For invoice templates and simple reports built in server-side frameworks like Django or Flask, WeasyPrint is a solid choice. For anything that needs a real browser to render correctly, it is not the right tool.
Gotenberg: the Docker-first approach
Gotenberg wraps Chromium in a stateless Docker container and exposes it as an HTTP API. You send an HTML file or URL, you get a PDF back. It also handles Office document conversions (Word, Excel, PowerPoint to PDF) through LibreOffice, which makes it appealing for document processing pipelines.
The trade-off is that you still manage infrastructure. The container needs 512 MB to 1 GB of RAM per Chromium instance. Scaling means running multiple containers, configuring health checks, and monitoring for memory leaks. It is a good fit for teams running Kubernetes who already have container orchestration in place. For teams without that infrastructure, it adds complexity.
Prince XML: the expensive specialist
Prince XML has been around since 2003 and excels at one thing: print-quality PDFs with proper CSS Paged Media support. Page numbers, running headers, table of contents generation, footnotes, and cross-references all work natively through CSS. No other tool matches it for book-quality or regulatory document output.
The cost reflects that specialty. Server licenses start at $3,800 or $2,500 per year. DocRaptor offers Prince as a managed API starting at $15 per month for 125 documents, which gets expensive fast at scale. For teams producing formal publications or compliance documents where print layout precision matters, Prince earns its price. For standard web-to-PDF conversion, it is overkill.
Why a managed API replaces wkhtmltopdf with the least friction
Look at the pattern: every self-hosted option adds something to your server. A binary, a Docker image, a browser process, system libraries. The whole reason wkhtmltopdf became painful was the binary dependency chain. Installing it on Alpine Linux required pulling in X11 libraries, Qt, fontconfig, and a dozen transitive packages. Multi-stage Docker builds broke when upstream packages changed.
A managed URL to PDF API eliminates that entire category of problems. You send an HTTP request with a URL or raw HTML. You get back a PDF. No binary on your server, no Chromium to manage, no memory tuning, no security patches to track.
ScreenshotRun's API uses Chromium under the hood, so the rendering matches what you see in Chrome. CSS Grid, Flexbox, web fonts, JavaScript execution, all of it works. The PDF-specific parameters map directly to concepts wkhtmltopdf developers already know:
| wkhtmltopdf flag | ScreenshotRun parameter |
|---|---|
--page-size A4 |
pdf_page_format=A4 |
--orientation Landscape |
pdf_landscape=true |
--margin-top 10mm |
pdf_margin_top=10 |
--margin-bottom 10mm |
pdf_margin_bottom=10 |
--javascript-delay 3000 |
delay=3000 |
--custom-header Cookie "session=abc" |
cookies=session%3Dabc |
Migration: the old way vs the new way
A typical wkhtmltopdf setup in a Dockerfile and application code looks something like this:
# Dockerfile — installing wkhtmltopdf
RUN apt-get update && apt-get install -y \
wkhtmltopdf \
xvfb \
libfontconfig1 \
libxrender1 \
libxtst6
# Application code — generating a PDF
xvfb-run wkhtmltopdf \
--page-size A4 \
--margin-top 10mm \
--margin-bottom 10mm \
https://example.com/invoice/123 \
/tmp/invoice.pdf
Five packages to install, a virtual framebuffer wrapper because wkhtmltopdf needs X11, and a CLI command that fails silently when dependencies are missing. Took me an embarrassing amount of time to figure out that last part.
The API equivalent in cURL:
curl "https://api.screenshotrun.com/v1/screenshots/capture?url=https://example.com/invoice/123&format=pdf&pdf_page_format=A4&pdf_margin_top=10&pdf_margin_bottom=10" \
-H "Authorization: Bearer YOUR_API_KEY" \
-o /tmp/invoice.pdf
One line. No binary, no xvfb, no apt-get. The Dockerfile shrinks because you no longer need X11 libraries, Qt packages, or the wkhtmltopdf binary itself.
For Node.js applications migrating from a wkhtmltopdf wrapper:
const fs = require('fs');
const params = new URLSearchParams({
url: 'https://example.com/invoice/123',
format: 'pdf',
pdf_page_format: 'A4',
pdf_margin_top: '10',
pdf_margin_bottom: '10'
});
const response = await fetch(
`https://api.screenshotrun.com/v1/screenshots/capture?${params}`,
{ headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const buffer = Buffer.from(await response.arrayBuffer());
fs.writeFileSync('/tmp/invoice.pdf', buffer);
For Python applications replacing the pdfkit wrapper:
import requests
response = requests.get(
'https://api.screenshotrun.com/v1/screenshots/capture',
params={
'url': 'https://example.com/invoice/123',
'format': 'pdf',
'pdf_page_format': 'A4',
'pdf_margin_top': '10',
'pdf_margin_bottom': '10'
},
headers={'Authorization': 'Bearer YOUR_API_KEY'}
)
with open('/tmp/invoice.pdf', 'wb') as f:
f.write(response.content)
For PHP applications replacing Snappy or Laravel Snappy, the migration follows the same pattern. See the full PHP integration guide for Guzzle and native cURL examples. Ruby developers moving from WickedPdf can find equivalent code in the Ruby integration.
When the API approach is not the right fit
Honest disclaimer: a managed API is the simplest wkhtmltopdf replacement for most use cases, but not for all of them.
- Air-gapped or offline environments. If your servers cannot make outbound HTTP calls, you need a self-hosted solution. Gotenberg or Puppeteer running locally are better choices.
- Sub-50ms latency requirements. An API call adds network round-trip time, typically 1 to 3 seconds for a PDF depending on page complexity. A local wkhtmltopdf or Puppeteer call can be faster for single documents. For batch workloads, the difference disappears because you can run requests in parallel.
- Print-quality typographic PDFs. If you need precise control over page breaks, widows, orphans, running headers, and footnotes through CSS Paged Media, Prince XML is the right tool. Chromium's
@pagesupport is functional but not publication-grade. - Zero-cost at any scale. wkhtmltopdf was free regardless of volume. An API has a free tier (200 requests per month on ScreenshotRun) but costs money beyond that. If budget is the constraint and you can tolerate the security and rendering limitations, a self-hosted Chromium setup eliminates per-request costs.
I spent too long picking the wrong tool for PDF generation early on. Knowing these trade-offs upfront would have saved me a few weekends.
How ScreenshotRun compares to wkhtmltopdf directly
| Capability | wkhtmltopdf | ScreenshotRun |
|---|---|---|
| Rendering engine | QtWebKit (frozen ~2012) | Chromium (latest) |
| CSS Grid | Not supported | Full support |
| Flexbox | Partial, broken with newer syntax | Full support |
| JavaScript execution | Limited, no ES6+ | Full (with delay and wait_for_selector) |
| Project status | Archived January 2023 | Active, maintained |
| Security vulnerabilities | CVE-2022-35583 (CVSS 9.8), unpatched | Sandboxed Chromium, no local binary |
| Installation | Binary + X11 + Qt + fontconfig | HTTP API, nothing to install |
| Docker image impact | ~200-400 MB added | 0 MB (external call) |
| Web fonts | Unreliable, manual install needed | Native Chromium support |
| Output formats | PDF, image (basic) | PDF, PNG, JPEG, WebP, AVIF, TIFF |
| Cost | Free (self-hosted) | Free tier: 200/month, paid plans after |
The gap between these two tools reflects a decade of web platform evolution. wkhtmltopdf was a good solution when most websites were built with floats and jQuery. The web moved on. The rendering engine did not.
For generating PDF invoices and reports from modern HTML templates, or converting any URL to a clean PDF, the managed API approach removes the most friction for the least ongoing maintenance.
Start with the free tier, run your existing templates through it, and compare the output to what wkhtmltopdf produces. The rendering difference on any page using modern CSS will speak for itself.
Vitalii Holben