Why this comparison matters in 2026
PDF generation is one of those problems that looks simple from the outside ("just convert HTML to PDF") and reveals hidden complexity the moment you put it in production. Memory leaks. Fonts not loading. CSS that renders differently in the headless browser than it does in your dev machine's Chrome. Pages breaking in the wrong place. The 4-second render time that blocks your API response.
Choosing the wrong tool costs engineering time. This comparison is written from the perspective of a team that has used all three approaches in production and lived with the consequences.
The comparison at a glance
| Criteria | wkhtmltopdf | Puppeteer | Typsetter |
|---|---|---|---|
| Render speed (1-page doc) | 2,800ms | 4,200ms | 340ms |
| Template system | HTML/CSS | HTML/CSS | Typst + Tera |
| Hosting complexity | Medium (binary dep) | High (Chromium) | None (API) |
| Self-hosted cost | Server required | Large server required | $0 for 100/mo free |
| Maintenance burden | High (deprecated) | Medium (updates) | Zero (managed) |
| Batch processing | Manual loops | Manual loops | Native CSV batch |
| Scheduled generation | Manual cron setup | Manual cron setup | Built-in schedules |
| Typography quality | OK (browser rendering) | Good (Chrome) | Excellent (Typst) |
| Developer experience | Poor (CLI flags) | OK (Node.js API) | Excellent (REST API) |
| CSS support | Outdated (WebKit 2009) | Full (Chrome latest) | Typst (not CSS) |
wkhtmltopdf — the veteran that's showing its age
wkhtmltopdf converts HTML and CSS to PDF using a patched version of the Qt WebKit rendering engine — the same engine that powered Safari circa 2009. This is simultaneously its greatest strength and its greatest weakness.
What it does well: It is dead simple to get started. Install the binary, call it from a shell command or a language wrapper, pass an HTML file, get a PDF back. For simple documents that don't require modern CSS (flexbox works, CSS Grid is spotty), it produces acceptable output.
The problems: The project has been effectively unmaintained since 2020. The underlying WebKit version does not support CSS Grid, custom properties (variables), many modern pseudo-selectors, or web fonts loaded via Google Fonts without a network call. The CLI flag system for page numbering, headers, and footers is arcane. Most critically, it cannot run on headless servers without a virtual framebuffer (Xvfb) on Linux unless you use the special Qt-patched build.
In our benchmark, wkhtmltopdf rendered a single-page invoice in 2,800ms average. This includes the process spawn overhead. If you're spawning a new process per request (which most integrations do), the cold start cost dominates.
Verdict on wkhtmltopdf: Appropriate for legacy systems that already use it and where migrating is not worth the effort. Not recommended for new projects. The maintenance risk of a deprecated dependency is real.
Puppeteer (and Playwright) — powerful but heavy
Puppeteer launches a headless Chrome instance and uses the browser's built-in print-to-PDF functionality. Playwright is functionally equivalent for this purpose. The output quality is excellent — you get exactly what Chrome renders, with full CSS support including modern features, web fonts, and JavaScript-rendered content.
What it does well: If your document relies on JavaScript-rendered charts, dynamic CSS animations, or content fetched from APIs that populates via useEffect, Puppeteer is the only option that can capture that faithfully. Full Chrome CSS support means your web templates render exactly as designed.
The problems: Chrome is a huge binary (~300MB). Each Puppeteer instance consumes 100–300MB of RAM. Running it in a Docker container or serverless function is painful — you need to bundle Chromium, set the right sandbox flags, and manage browser instance pooling to avoid the 2–4 second cold start cost on every request. In our benchmark, Puppeteer averaged 4,200ms per single-page invoice. A browser pool can reduce this to ~800ms, but then you're managing a stateful pool in production.
Verdict on Puppeteer: The right choice when your document content requires a real browser to render (JavaScript-driven charts, heavily styled web components). Overkill for data-driven documents like invoices, contracts, and reports where the layout is static and only the data changes.
Typsetter — purpose-built for document APIs
Typsetter is a hosted PDF generation API built on Typst, a compiled typesetting language. You send a JSON body to a REST endpoint and get a PDF binary back. There is no browser involved, no binary to install, and no infrastructure to manage.
What it does well: Speed is the headline number — 340ms average for a single-page document, and this does not degrade under load because the Typst compiler is stateless and Rust-native. Template management is handled through a web UI with visual and code editors. Batch generation (CSV to ZIP), scheduled generation (cron), and public forms are all built-in features, not things you cobble together with cron jobs and S3 buckets.
Where it's different: You write templates in Typst, not HTML/CSS. This has a learning curve if you know CSS well, but Typst is significantly easier than LaTeX and produces better typographic output. If your existing templates are HTML/CSS and you have hundreds of them, migration has a cost. You also need to be comfortable sending document data to a third-party API.
Benchmarks explained
Benchmarks were run on a standard AWS t3.medium instance (2 vCPU, 4GB RAM) running Ubuntu 22.04. Each tool rendered the same single-page A4 invoice with a logo, table of line items, and a footer. Results are median of 100 runs with a warm server (browser pool pre-warmed for Puppeteer).
- wkhtmltopdf 0.12.6: 2,800ms median. Includes process spawn overhead. No warm pool possible with process-per-request model.
- Puppeteer 22.x (Chrome 122, warm pool, 3 browser instances): 4,200ms cold start, 820ms warm. The 4,200ms figure represents a realistic cold-container scenario (Lambda, Cloud Run, etc.).
- Typsetter API (Pro plan): 340ms median, 490ms p95. Network latency from Frankfurt data center (6ms RTT from test server) included.
If you run Puppeteer with a persistent pool on dedicated hardware and your invoices are simple, you can achieve ~300ms warm renders. But you then own the infrastructure, the Chromium updates, the memory management, and the failover — costs not reflected in the benchmark number.
Cost comparison
This is where the analysis gets nuanced. Raw hosting cost for self-hosted tools appears lower, but the total cost of ownership includes engineering time to maintain the stack.
- wkhtmltopdf: Binary is free. You need a server ($10–$50/mo EC2/DO). Engineering time to maintain, debug CSS issues, update the binary, handle Xvfb on Linux: estimated 2–4 hours/month for a healthy setup, more for a troubled one.
- Puppeteer: Package is free. You need a server with enough RAM for browser instances (t3.large minimum in practice, ~$70/mo). Engineering time for browser pool management, memory leak monitoring, Chromium updates: 4–8 hours/month.
- Typsetter: Free tier covers 100 PDFs/month. Growth plan ($29/month) covers 5,000. Pro ($99/month) covers 50,000. No server. No maintenance. Engineering time to integrate: ~1 hour initial setup.
For a team generating 5,000 PDFs/month, Typsetter Growth ($29) vs. a t3.large + 6 engineering hours of maintenance ($70 server + $600+ in developer time at a conservative $100/hr blended rate) is not a close comparison.
When to choose which tool
Choose wkhtmltopdf if...
You have a legacy system already using it, your templates are simple HTML, and the migration cost is higher than the maintenance burden. Don't start new projects with it.
Choose Puppeteer if...
Your documents require JavaScript-rendered content, complex CSS animations, or you are already generating the document as a web page that users also view in a browser and you want identical output.
Choose Typsetter if...
Your documents are data-driven (invoices, contracts, reports, certificates), you want fast renders without infrastructure, and you need batch processing, schedules, or webhooks without building them yourself.
Final verdict
There is no universally correct answer, but there is a common pattern: most teams that use Puppeteer for invoices and reports would be better served by a purpose-built PDF API. The browser overhead exists to support a rich interactive rendering environment that data-driven documents simply do not need. wkhtmltopdf served its purpose well for a decade, but its deprecation means you are accumulating technical debt with every month it stays in your stack.
Typsetter is not the right choice if your documents require a live browser to render. For everything else — especially at scale — the speed, DX, and zero-maintenance advantages are substantial. The 340ms render time is not a marketing claim; it reflects what happens when you use the right tool for the job.
Try Typsetter for free
100 PDFs/month on the free plan. API key in 30 seconds. No credit card required.