Best wkhtmltopdf Alternatives for HTML to PDF Conversion
Compare Puppeteer, WeasyPrint, and Prince by rendering needs, runtime, and licensing. Includes migration checks, runnable examples, and security guidance.
Direct answer: start with Puppeteer when pages depend on dynamic JavaScript, WeasyPrint for controlled report HTML in a Python-oriented stack, and Prince when a commercial renderer fits your requirements. None is a universal drop-in replacement. Render representative documents with each finalist and check pagination, print styles, fonts, colors, assets, and deployment behavior before migrating.
The wkhtmltopdf project status page itself points to these tools for those respective use cases. This guide explains how to choose and try them, what to validate, and how to handle the security risks of converting untrusted HTML.
1. Why consider replacing wkhtmltopdf?
wkhtmltopdf describes itself as a headless command-line HTML-to-PDF converter built on Qt WebKit. Its downloads page lists version 0.12.6, released June 11, 2020, as the current stable series. The project status page describes the historical state of its underlying stack: Qt 4 support ended in 2015, the WebKit version used by wkhtmltopdf was last updated in 2012, and work on the planned 0.13 line stalled. These are statements from the project’s status and download pages, not a fresh independent audit.
That history matters when a workload needs modern page behavior, active maintenance, or a runtime your team can support. It does not prove that every existing wkhtmltopdf installation is broken. A migration should be driven by a concrete need and validated against the PDFs your users actually rely on.
Security warning for untrusted input
The wkhtmltopdf project warns: “Do not use wkhtmltopdf with any untrusted HTML – be sure to sanitize any user-supplied HTML/JS, otherwise it can lead to complete takeover of the server it is running on!” The project also recommends considering a Mandatory Access Control system such as AppArmor or SELinux. Treat user-controlled HTML, JavaScript, URLs, and referenced resources as hostile input; sanitization alone is not a substitute for isolation and restrictive network and filesystem access.
2. Choose an alternative by workload
| Tool | Good starting point | Rendering model to account for | Evaluate when |
|---|---|---|---|
| Puppeteer | Web pages whose output depends on JavaScript | Automates a browser; page.pdf() uses print CSS media by default |
You need browser behavior and can own the browser runtime |
| WeasyPrint | Controlled report HTML, especially in a Python stack | Purpose-built paginated HTML/CSS renderer, not a full browser engine such as WebKit or Gecko | You generate reports and want a Python library/command-line workflow |
| Prince | Report generation where a commercial renderer is appropriate | Commercial HTML/XML-to-PDF application with paged-media documentation | Commercial licensing and its feature set fit your requirements |
This is a shortlist, not a universal ranking. Compare the rendering requirements, print CSS and pagination, runtime and deployment fit, operational ownership, and commercial licensing needs. The cited sources do not provide a controlled head-to-head benchmark, so there is no substantiated universal speed winner or guarantee of identical output.
3. Try Puppeteer for dynamic JavaScript pages
Puppeteer automates a browser and can generate a PDF after page content has rendered. Its official Page.pdf() API uses the print CSS media type by default. If you want screen styles instead, call page.emulateMediaType('screen') before creating the PDF. Puppeteer notes that PDF generation modifies colors for printing by default; use -webkit-print-color-adjust when exact print colors are required.
Runnable Node.js example
Install Node.js, then create a project and install Puppeteer. The package downloads a compatible browser as part of installation.
npm init -y
npm install puppeteer
Save this as render.mjs. It accepts the target URL and output path as arguments, waits for the page load event, then writes a PDF using print media.
import puppeteer from 'puppeteer';
const [url, output = 'page.pdf'] = process.argv.slice(2);
if (!url) {
console.error('Usage: node render.mjs https://example.com [output.pdf]');
process.exit(2);
}
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle0', timeout: 60_000 });
await page.pdf({
path: output,
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
margin: { top: '15mm', right: '12mm', bottom: '15mm', left: '12mm' }
});
console.log(`Wrote ${output}`);
} finally {
await browser.close();
}
node render.mjs https://example.com example.pdf
networkidle0 waits for network activity to settle. Some sites keep connections open or continually fetch data, so this may time out; for those pages, wait for a known selector or an application-specific ready condition instead. The example does not guarantee that every asynchronous component has finished rendering.
Puppeteer PDF options worth checking
format,width, andheightcontrol paper dimensions; use a consistent paper size for predictable pagination.landscapechanges page orientation.marginsets top, right, bottom, and left margins.printBackgroundincludes background graphics and colors.preferCSSPageSizegives a document’s CSS@pagesize priority over the API paper size.displayHeaderFooter,headerTemplate, andfooterTemplateadd browser-generated header/footer regions.page.emulateMediaType('screen')selects screen styles when that is what the output needs. The default is print media.
Consult the official PDFOptions reference for the current option types and defaults. For controlled layout, define print rules such as @media print, @page, and page-break behavior in your stylesheet, then compare the result with the browser view.
4. Try WeasyPrint for controlled report HTML
WeasyPrint describes itself as a visual HTML/CSS rendering engine that exports to PDF. It is designed for pagination and is not based on a full browser engine such as WebKit or Gecko. It is a candidate for reports whose HTML and assets you control; do not assume it behaves like a browser for arbitrary websites or dynamic JavaScript.
At research time, the stable documentation identified WeasyPrint 70.0 and Python 3.10 or newer. Its installation also depends on system libraries, including Pango, so check the installation guide for your operating system and deployment image.
Runnable Python example
Install WeasyPrint in a virtual environment. On systems where the required native dependencies are missing, follow the official platform-specific installation instructions first.
python3 -m venv .venv
. .venv/bin/activate
python -m pip install weasyprint
Save as render.py. This example converts a local HTML file and resolves relative assets against that file’s directory.
from pathlib import Path
import sys
from weasyprint import HTML
if len(sys.argv) != 3:
raise SystemExit('Usage: python render.py input.html output.pdf')
source = Path(sys.argv[1]).resolve()
output = Path(sys.argv[2]).resolve()
HTML(filename=str(source), base_url=str(source.parent)).write_pdf(str(output))
print(f'Wrote {output}')
python render.py report.html report.pdf
For a URL, the library also supports using a URL as the HTML source. Before converting remote or user-provided content in a service, decide which schemes and hosts are allowed and restrict network access. Relative images, stylesheets, and fonts need resolvable URLs or a suitable base URL.
5. Evaluate Prince for commercial report generation
The wkhtmltopdf project names Prince as a commercial alternative for report generation. Prince’s documentation includes installation, user, and reference guides; its current materials should be consulted for installation and licensing details. Pricing, license terms, and suitability depend on your use case and were not established here, so check the vendor’s current purchasing information before selecting it.
Runnable command-line starting point
After installing Prince using the vendor’s platform-specific instructions, convert a local HTML document with the documented command-line pattern:
prince report.html -o report.pdf
Use the Prince user guide and reference to configure your stylesheet, JavaScript behavior, resources, and paged-media requirements. The command is a starting point; validate output and options against the installed version and your actual documents.
6. Migration plan: compare output before switching
- Inventory inputs. Identify whether HTML is trusted or user-controlled, whether content depends on JavaScript, and where assets, fonts, and stylesheets come from.
- Record requirements. Capture paper size, orientation, margins, headers/footers, colors, page-break rules, links, and expected handling of missing assets.
- Build a representative corpus. Include short and long documents, tables, images, unusual fonts, long unbreakable content, and documents near page boundaries.
- Render with finalists in the real deployment environment. Use the same fonts, OS/container, network rules, and resource locations intended for production.
- Inspect differences. Review pagination, clipping, font substitution, color behavior, headers/footers, asset loading, and failure modes. Keep expected PDFs or visual review records for regression checks.
- Exercise failure and security paths. Test missing or slow assets, rendering timeouts, malformed input, and blocked network destinations. Run untrusted conversions with least privilege, resource limits, and isolation.
- Roll out gradually. Compare output and operational failures on a limited workload before routing all production conversions to the new renderer.
This checklist is practical guidance inferred from the documented differences and the project’s security warning; it is not a benchmark or a claim that a conversion test has been run.
7. Security, reliability, performance, and cost
Security
Never run wkhtmltopdf against untrusted HTML/JavaScript without taking its project warning seriously. Apply input sanitization, run conversions under a restricted account or container, limit filesystem access, restrict outbound network requests, and set CPU, memory, and execution-time limits. The project specifically suggests considering AppArmor or SELinux. Apply the same threat-model review to any replacement that loads user-controlled markup, scripts, or URLs.
Reliability
Rendering can fail because a page never reaches the expected ready state, a remote font or image is unavailable, or a document contains layout the renderer handles differently. Set timeouts, log the input identifier and renderer version, capture useful diagnostics, and define whether a failed job is retried or returned as an error. Avoid treating a successful process exit as proof that the PDF is visually complete.
Performance and cost
The sources reviewed do not establish a comparative performance result. Measure throughput and resource use with your own documents and deployment. Puppeteer includes browser-runtime ownership; WeasyPrint needs its Python and native dependencies; Prince has commercial licensing considerations. Include installation, updates, isolation, concurrency, memory, and support needs in the total operational cost rather than comparing conversion time alone.
8. Or skip the browser setup
For website captures, ScreenshotNeo is the alternative to try first: it is a website screenshot API and MCP server for developers, and returns clean screenshots or PDFs from a GET request. See the API documentation for the PDF and capture parameters.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
The examples above use the documented screenshot request shape and save its image response. For PDF output, use the PDF options documented by ScreenshotNeo. Before capture, ScreenshotNeo accepts the cookie/consent banner like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000.
Sign up for 1,000 free screenshots a month with no card.
9. Common problems and fixes
| Symptom | Likely cause | What to do |
|---|---|---|
| PDF is missing content rendered by JavaScript | Capture ran before the page finished rendering or the readiness condition was wrong | With Puppeteer, wait for an application-specific selector or ready signal; avoid assuming a network-idle event means the app is finished. |
| PDF looks different from the browser | Print media rules or print color adjustment changed the page | Check print CSS; use screen media only if intended, and review Puppeteer’s print-color guidance. |
| Backgrounds or colors are absent | Background printing is disabled or print color rules suppress them | Set Puppeteer’s printBackground: true and review -webkit-print-color-adjust. |
| Fonts or images are missing | Resources are relative, inaccessible, blocked, or not loaded before conversion | Set a correct base URL for local WeasyPrint files, ensure resource access, and verify fonts are installed in the runtime. |
| WeasyPrint installation fails | A required native dependency such as Pango is absent or too old | Check Python and system-library requirements in the platform-specific stable installation guide. |
| Page dimensions or breaks differ | CSS @page, renderer defaults, or API paper dimensions conflict |
Choose one source of page size, inspect margins and break rules, and use preferCSSPageSize deliberately in Puppeteer. |
| Conversion hangs or times out | Open network activity, a slow resource, or non-terminating page behavior | Use a bounded timeout, wait for a specific readiness condition, and restrict or diagnose remote resources. |
| Unsafe local or remote resource access | HTML can direct the renderer to sensitive files or network destinations | Do not expose untrusted input to a privileged renderer; sanitize, sandbox, restrict network/filesystem access, and consider mandatory access controls. |
10. FAQ
Which alternative supports JavaScript-heavy websites?
Puppeteer is the clearest candidate in this shortlist: the wkhtmltopdf project recommends it for dynamic JavaScript sites. Wait for the site’s actual content readiness before generating the PDF.
Is WeasyPrint a browser replacement?
No. Its documentation describes a paginated HTML/CSS rendering engine, not a full browser engine. Evaluate it for controlled report HTML rather than assuming arbitrary sites will behave identically.
Is Prince free?
Prince is described by the wkhtmltopdf project as a commercial tool. Check Prince’s current vendor materials for pricing and license terms.
Will any alternative produce identical PDFs?
Do not assume so. Print CSS, fonts, pagination, resource loading, and renderer behavior differ; compare a representative document set before switching.
Can I safely convert HTML submitted by users?
Only with a deliberate security design. The wkhtmltopdf project explicitly warns against processing untrusted HTML/JavaScript and recommends considering mandatory access control. Isolate the renderer and restrict its privileges and network access.
