Features Full Page Screenshot Wait for Selector & Delay Block Cookie Banners Custom Viewport & Device Website to PDF HTML to Image Dark Mode Image Format & Quality MCP Server Webhook Pricing Docs Blog Log In Sign Up

wkhtmltopdf Alternative

Vitalii Holben Vitalii Holben

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 @page support 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.

Frequently asked questions

No. The GitHub repository was archived in January 2023 and the entire GitHub organization was marked unmaintained in July 2024. The last release (0.12.6) was in June 2020. No security patches or bug fixes will be released. Homebrew disabled the cask in December 2024, and other package managers are following.
For most projects, a managed HTML-to-PDF API like ScreenshotRun is the closest drop-in replacement. You remove the binary and system dependencies from your server and replace them with a single HTTP call. The API uses Chromium, so CSS Grid, Flexbox, and JavaScript all render correctly. For self-hosted alternatives, Puppeteer or Playwright are the most common choices, though they require managing Chromium binaries and browser processes.
No. The rendering engine is a fork of QtWebKit frozen around 2012. It does not support CSS Grid, CSS custom properties, modern Flexbox syntax, or calc() with mixed units. Pages built with Bootstrap 4+, Tailwind, or any modern CSS framework will render incorrectly or produce blank output.
It carries known unpatched vulnerabilities. CVE-2022-35583 is a critical SSRF vulnerability scored 9.8 out of 10 that allows attackers to read local files or access internal network endpoints through crafted HTML input. CVE-2020-21365 enables directory traversal. Neither will ever be patched. If your application processes untrusted HTML input, wkhtmltopdf is a security risk.
Remove the wkhtmltopdf binary and its system dependencies (xvfb, libfontconfig, libxrender) from your server or Dockerfile. Replace the CLI call or library wrapper (pdfkit for Python, Snappy for PHP, WickedPdf for Ruby) with an HTTP request to the API endpoint. The ScreenshotRun API accepts a URL or raw HTML and returns a PDF. Parameters like page size, margins, and orientation map directly to wkhtmltopdf flags.