HTML to PDF API: a developer's guide to the options
If you need to generate PDFs from HTML in code, the real decision is not which library renders best, most modern tools use headless Chromium and render identically, but who operates the rendering infrastructure. Your three paths: run a headless browser yourself (Playwright or Puppeteer), run a self-hosted HTTP service (Gotenberg), or call a managed API and let a provider handle the browser. Each trades operational complexity against control and cost. This guide lays out the trade-offs honestly, including one tool to avoid outright. (Note: PDFMoka is a browser-based converter, not an API, if you just need to convert a file by hand rather than in code, use the HTML to PDF tool; the rest of this guide is for programmatic generation.)
First, the decision that actually matters
Nearly every serious HTML-to-PDF tool in 2026 renders through headless Chromium, which means CSS and JavaScript support is essentially the same across them, your @media print rules, @page declarations, and break-inside: avoid behave identically. So stop comparing renderers on fidelity and ask instead: do you want to operate a browser in production, or pay someone else to?
That question splits the field cleanly.
Path 1: Self-hosted headless browser (Playwright / Puppeteer)
You run Chromium inside your own application or container.
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.setContent(htmlString, { waitUntil: 'networkidle' });
const pdf = await page.pdf({ format: 'A4', printBackground: true });
await browser.close();
Pros: full control, no per-render fee, no HTML leaves your infrastructure (matters for data residency), and you can run application JavaScript before export.
Cons, and they are real at scale:
- Memory: each Chromium process eats roughly 150-500MB; under concurrency you must pool browsers and cap parallelism.
- Docker bloat: Chromium plus fonts adds roughly 300MB+ to your image, slowing builds and deploys.
- Serverless pain: AWS Lambda's layer size limits and some edge runtimes make bundling Chromium awkward or impossible. This matters especially on Cloudflare Workers and similar edge platforms, where a full Chromium simply does not fit.
- Operations: crash recovery, page-leak memory growth, and Chromium version bumps several times a year.
Playwright vs Puppeteer: both are Chromium-based and render the same. Playwright (Microsoft) supports Node, Python, Java, .NET and ships native ARM64 Linux Chromium; Puppeteer (Chrome team) is Node-first. For PDF generation specifically, the operational burden is the same either way; pick on language and ecosystem fit.
verify against current docs: the Playwright/Puppeteer
page.pdf()APIs are current, but confirm the exact options against their docs before relying on syntax. Memory and image-size figures are typical ranges from 2026 benchmarks, not guarantees, measure on your own target platform, since containerized Linux can differ 30-70% from a dev laptop.
Path 2: Self-hosted HTTP service (Gotenberg)
Gotenberg wraps headless Chromium (and LibreOffice for Office formats) behind an HTTP API you host yourself. You still operate the infrastructure, but rendering is decoupled from your application, you POST HTML and get a PDF back over your own network.
Good when you want an API-shaped interface without sending data to a third party, and you are willing to run and scale the service. You still own the Chromium operational cost from Path 1, just isolated into a dedicated service.
Path 3: Managed API
You POST HTML over HTTPS and receive a PDF; the provider runs Chromium, handles scaling, security patches, and version upgrades. There are many providers in this space (Chromium-based ones for JS/modern CSS; a few use WeasyPrint or Prince for CSS Paged Media and PDF/A compliance).
Pros: near-zero infrastructure (your app holds an HTTP client, not a browser), no Docker bloat, works fine from serverless, provider absorbs Chromium upgrades.
Cons: per-render cost, a network round-trip, an external dependency, and, most importantly, your HTML leaves your infrastructure. If the HTML contains sensitive data and you have a data-residency or confidentiality policy, that can be a blocker; this is the main reason teams stay self-hosted.
The one to avoid: wkhtmltopdf
You will find countless older tutorials pointing to wkhtmltopdf (and wrappers like pdfkit, wicked_pdf, snappy). Do not start a new project on it. It was archived in January 2023, its rendering engine predates modern CSS, and it carries an unpatched critical SSRF vulnerability (CVE-2022-35583). If you have an existing integration, plan a migration to Playwright/Puppeteer (self-hosted) or a managed API, and remove the binary afterward, leaving it installed keeps the security exposure open.
verify: confirm wkhtmltopdf's archived status and the CVE reference yourself before publishing; security facts should be checked at the source (the project's GitHub and the CVE record).
Quick decision guide
- Need JavaScript execution or modern CSS, and can operate a browser: self-hosted Playwright/Puppeteer.
- Want an API interface but must keep data in-house: Gotenberg (self-hosted).
- Want zero infrastructure and are OK sending HTML out: a managed API.
- On serverless/edge (Lambda, Cloudflare Workers): a managed API is usually the pragmatic choice, since bundling Chromium there is painful or impossible.
- You were about to use wkhtmltopdf: don't; migrate to one of the above.
- Just converting a file by hand, no code: PDFMoka's browser HTML to PDF tool.
Related
- Converting a local file without code: how to convert an HTML file to PDF locally.
- Getting print CSS right (colors, page breaks): print code with syntax highlighting.
References
- Playwright
page.pdf()documentation: https://playwright.dev/docs/api/class-page#page-pdf - Puppeteer PDF generation documentation: https://pptr.dev/api/puppeteer.page.pdf
- Gotenberg documentation: https://gotenberg.dev/