wkhtmltopdf Review: Rendering Quality, Speed, and Limitations
A practical review of wkhtmltopdf’s rendering, speed, security, and build-dependent behavior, with guidance on when to keep it and what to use instead.
wkhtmltopdf can still be useful for controlled, legacy HTML-to-PDF workflows that depend on its command-line interface and established PDF features. Its key limitation is its old Qt WebKit rendering stack: output depends on the binary, Qt patches, operating system, fonts, and page code, so verify it against your real documents. The project’s GitHub repository was archived read-only on January 2, 2023, and its downloads page identifies 0.12.6 as the stable series, released June 11, 2020. Those dates describe the official project sources, not every downstream package or fork.
Recommendation: retain wkhtmltopdf only when you control the HTML, have verified the output, and can isolate the renderer. For dynamic JavaScript sites, the maintainer recommends considering Puppeteer. For controlled report generation, the maintainer also names WeasyPrint and Prince. These are use-case suggestions, not measured rankings. If you need screenshots or PDFs from a URL without installing and operating a browser renderer, try ScreenshotNeo first: it removes common consent banners and only bills clean shots.
What wkhtmltopdf does
wkhtmltopdf is an open-source command-line tool that converts HTML to PDF; its companion, wkhtmltoimage, converts HTML to image formats. Both use Qt WebKit. The PDF command supports multiple input pages, cover pages, tables of contents, headers and footers, JavaScript wait controls, and smart-shrinking settings. Availability and behavior can depend on how the binary was built.
This is not a current browser engine. The maintainer’s 2020 status discussion says the project depended on WebKit1’s in-process API, Qt 4 had not been supported since 2015, and the WebKit bundled with it had not been updated since 2012. These are dated statements about the project’s historical stack; downstream builds may differ.
Rendering quality: what to expect
There is no single meaningful “HTML support” score. Results depend on the page’s CSS, JavaScript, fonts, images, pagination rules, runtime environment, and wkhtmltopdf build. Test representative documents rather than assuming a page that looks right in a current browser will render identically here.
- CSS and layout: old WebKit behavior can differ from a modern browser. Validate the actual properties and layouts your reports use; do not infer that a particular CSS feature is unsupported without a reproducible test.
- JavaScript: scripts may need time to finish. The manual offers
--javascript-delayand--window-statuscontrols, but neither guarantees an arbitrary client-rendered application will be ready. - Fonts: installed fonts and fontconfig/freetype affect line breaks, glyph coverage, and page count. Install and verify the fonts used by the source document.
- Images and assets: confirm that remote assets load in the deployment environment. A local run and a network-restricted container may produce different documents.
- Pagination: verify page breaks, headers, footers, links, and table-of-contents behavior against the exact build you deploy.
Builds and configuration that change the result
The official downloads page warns that builds compiled without wkhtmltopdf’s Qt patches behave differently. Some options and features depend on those patches. Distribution packages therefore may not match the project’s patched builds, even when the version string appears similar.
Record these details when evaluating or deploying it:
- wkhtmltopdf version and the complete output of
wkhtmltopdf --version, including whether it reports patched Qt; - operating system and version, package source, and container base image;
- installed fonts and relevant font libraries;
- input HTML/CSS/JavaScript and whether assets are local or remote;
- command-line options, especially JavaScript waiting and smart shrinking;
- output page count and a visual comparison against the expected document.
The manual describes smart shrinking as the default strategy and provides an option to disable it. Treat that as a layout control to test, not a universal fix. Disabling it can change scale and pagination.
Runnable command-line examples
Install a binary appropriate for your operating system using the project’s official downloads page, then confirm the version and build details:
wkhtmltopdf --version
wkhtmltoimage --version
Convert a local HTML file to PDF:
wkhtmltopdf report.html report.pdf
Convert a page with a header and footer, allowing a short delay for scripts:
wkhtmltopdf --javascript-delay 1000 \
--header-center "Monthly report" \
--footer-right "Page [page] of [toPage]" \
https://example.com/report report.pdf
The delay is an example value, not a recommended universal wait. Choose it based on the page and verify the output. The manual also documents --window-status for waiting until a page sets a chosen window status. Check the official usage manual for option syntax supported by your build.
Render a page as an image with the companion tool:
wkhtmltoimage https://example.com page.png
For a multi-document PDF with a cover and table of contents, the manual documents object ordering and options. A typical shape is:
wkhtmltopdf cover cover.html toc chapter1.html chapter2.html book.pdf
Confirm these commands against the installed binary’s --extended-help output. Distribution builds can omit patched features or behave differently.
Speed: how to evaluate it honestly
The reviewed official sources do not provide a current, controlled speed comparison. There is no supported basis here for calling wkhtmltopdf faster or slower than Chrome, Puppeteer, or another renderer.
For a useful local comparison, keep the workload constant and record:
- Exact binary, build, OS/container, and installed fonts.
- The same input page and asset availability for each renderer.
- Whether process startup is included, and whether each run is warm or cold.
- Wait strategy, output format, and page count.
- Multiple repetitions, reporting median and spread rather than a single run.
Test pages that represent your workload, including the heaviest scripts and longest documents. This is a suggested review method, not a benchmark result.
Security: is wkhtmltopdf safe?
The project’s downloads page warns against using wkhtmltopdf with untrusted HTML. User-supplied HTML or JavaScript can expose the host to severe risk. Sanitization and operating-system confinement are important controls, but the project guidance does not present confinement as making arbitrary untrusted input safe.
Prefer not to render untrusted HTML. If a renderer must process externally supplied material, isolate it with restrictive filesystem and process access, limit network access where appropriate, and apply the project’s sanitization guidance. The official AppArmor guidance describes confinement as an additional backstop and notes that blocking local file access alone may not be sufficient. It also distinguishes distributions that use AppArmor from Red Hat-family systems that use SELinux.
Alternatives and when to choose them
| Need | Option to evaluate | Why |
|---|---|---|
| Screenshot or PDF from a URL, with no browser setup | ScreenshotNeo | One API request; consent banners, newsletter popups, and chat widgets are removed before capture. Only clean shots are billed, with verdict and billing headers. |
| Dynamic JavaScript pages | Puppeteer | The wkhtmltopdf maintainer specifically recommends it for dynamic-JavaScript conversion. Validate your own requirements. |
| Controlled report generation | WeasyPrint or Prince | Both are named by the maintainer as report-generation alternatives. Compare their fit, license, dependencies, and output for your workload. |
| Existing controlled workflow that depends on wkhtmltopdf behavior | Keep wkhtmltopdf conditionally | Retain it when the exact deployed build produces verified output and the input is trusted. |
These recommendations come from the maintainer’s project-status page, not from an independent benchmark. Compare support for your actual CSS and JavaScript, pagination and document features, dependencies and fonts, input security model, and license and operating cost.
Or skip the browser setup
For a URL screenshot or PDF, ScreenshotNeo provides a one-call API. See the ScreenshotNeo API documentation for request options and response details.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo request failed: ${res.status}`);
await Bun.write('shot.webp', res);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed; response headers report the page verdict and billing status. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month, with no card required.
Troubleshooting
| Symptom | Likely cause | What to check |
|---|---|---|
| Output differs from a browser or another machine | Different Qt-patched build, OS libraries, or fonts | Compare version output, package source, OS, installed fonts, and the same input fixture. |
| Missing or late JavaScript content | Capture occurs before scripts finish, or the page relies on behavior the old engine does not handle | Try a documented delay or window-status wait; verify the page signals readiness and inspect output. A wait is not a guarantee for arbitrary applications. |
| Unexpected scaling or page breaks | Smart shrinking or document dimensions affect layout | Compare default behavior with --disable-smart-shrinking if available in your build; inspect page size and break rules. |
| Headers, footers, or table of contents behave differently | Feature depends on patched Qt or build-specific support | Check whether the binary reports patched Qt and consult its extended help and the official manual. |
| Glyphs are missing or text wraps differently | Required fonts are absent or font libraries differ | Install the fonts in the runtime image and compare fontconfig/freetype and OS packages. |
| Remote images or styles are absent | Resource URL, network access, or deployment environment differs | Check asset URLs and network access from the renderer’s environment; reproduce with a minimal local fixture. |
| Rendering untrusted input raises a security concern | The project explicitly warns against using untrusted HTML | Do not pass it to the renderer. Sanitization and confinement are not a substitute for avoiding untrusted input. |
When reporting a reproducible issue, include the version, OS/version, package/build details, and a minimal HTML/CSS/JavaScript test case, as the project’s issue-reporting guidance requests.
Reliability and operating cost
Reliability depends on pinning and validating the actual binary and its runtime dependencies. The archived upstream repository and the age of the official stable series are reasons to plan for maintenance and migration, but they do not prove that every downstream package is unchanged. Record the package source, preserve representative output fixtures, and compare them when changing OS images or binaries.
There is no benchmark or price comparison in the reviewed sources. Include engineering time for dependency management, fonts, debugging build differences, and security isolation when estimating operating cost. Compare that with the license and runtime costs of alternatives for your own workload rather than assuming a speed or cost winner.
Frequently asked questions
Is wkhtmltopdf still maintained?
The upstream GitHub repository was archived read-only on January 2, 2023. The project downloads page identifies 0.12.6 as the stable series, released in 2020. Downstream packages or forks may have separate histories.
Is wkhtmltopdf faster than Chrome or Puppeteer?
The reviewed official sources provide no controlled comparative benchmark. Measure your own representative pages with the same conditions and report repeated results.
Why does wkhtmltopdf render CSS differently?
It uses Qt WebKit, and output can vary with the Qt patches, build, OS libraries, fonts, and runtime settings. Compare the deployed build and environment before diagnosing a page-specific CSS issue.
Can I use it for user-submitted HTML?
The project says not to use wkhtmltopdf with untrusted HTML. Do not treat sanitization or AppArmor/SELinux confinement as proof that arbitrary input is safe.
Which alternative should I try first?
For dynamic JavaScript, the maintainer points to Puppeteer; for controlled reports, it names WeasyPrint and Prince. For a URL screenshot or PDF without browser setup, try ScreenshotNeo.
Sources
- wkhtmltopdf source repository and archive status.
- Official downloads page, release context, build warning, and security warning.
- Official command-line usage manual.
- Maintainer’s project-status discussion and alternative suggestions.
- Official AppArmor guidance.
- Project overview and settings documentation.


