Puppeteer alternatives for HTML to PDF in 2026: six ways out of browser ops
The best Puppeteer alternative for HTML-to-PDF work depends on what is actually hurting. If it is browser operations (memory, crashes, serverless packaging), a managed API like Transformy removes the browser from your stack while keeping Chrome-quality output. If it is Puppeteer's Node.js-only reach, Playwright brings the same rendering to Python, Java, and C#. And if your documents are simple, deterministic templates, WeasyPrint or a PDF construction library like pdf-lib may mean you never needed a browser at all.
Scope note: Puppeteer is a general browser-automation tool, and this comparison covers only its HTML-to-PDF job. If you use Puppeteer for scraping or end-to-end testing, none of what follows argues for changing that.
$ ps aux | grep -c "[c]hrome"
47
$ pm2 show pdf-worker | grep restarts
restarts ············· 23 (last: 41m ago)
$ tail -1 pdf-worker.log
FATAL ERROR: Reached heap limit
Allocation failed - JavaScript heap out of memory
$
KEY TAKEAWAYS
- Diagnose before you switch. Browser-ops pain, language mismatch, and document overkill are three different problems with three different best answers.
- A managed API removes the browser, not the quality. Transformy renders with the same headless Chromium; the pool management, upgrades, and scaling move to the vendor.
- Playwright is a sibling, not an escape. Better cross-language bindings and a nicer API, but you still run every browser yourself.
- Serverless and browsers remain an awkward fit. Chromium's size and cold-start cost in Lambda-style environments is a packaging tax you pay on every deploy.
- Simple documents may not need rendering at all. WeasyPrint (HTML/CSS, no JS) and pdf-lib or PDFKit (construct the PDF directly) skip the browser entirely.
Why teams look past Puppeteer for PDFs
To be fair to the incumbent: Puppeteer is excellent. It is free, actively maintained, and page.pdf() produces exactly what Chrome's print dialog would. Teams go looking for alternatives not because the output is wrong but because of what surrounds it.
Production browsers are an ops workload. The Puppeteer part of a PDF feature is 20 lines; the production-hardening part is the project. Browsers leak memory and need recycling; crashed renders leave zombie Chromium processes; realistic throughput needs a browser pool with a queue; and every Chrome release joins your regression matrix. None of this is hard once, but all of it is forever.
Serverless makes it worse. Chromium does not enjoy being zipped. Lambda-compatible Chromium builds, layer size limits, and multi-second cold starts are a whole genre of workaround documentation, and the workarounds need maintenance as runtimes and Chrome versions move.
Your stack may not be Node.js. Puppeteer is a Node library. Python, PHP, Ruby, and Java teams either run a Node sidecar service for PDFs or pick a tool native to their stack.
Your documents may be simpler than your renderer. A receipt with a logo and a table does not need JavaScript execution, font settling, or a 300 MB browser. Plenty of Puppeteer PDF setups are overkill that nobody re-evaluated after the prototype worked.
Tip
If Puppeteer is producing correct PDFs and the ops load is invisible to you, you are not the audience for this page. Alternatives earn their place when browser operations start appearing in your incident channel or your sprint planning.
What to match the alternative to
Three questions sort the field:
Is the pain operational?
You like the output; you dislike running browsers. Move rendering behind an API you do not operate: managed (Transformy) or self-hosted service shape (Gotenberg).
Is the pain the language?
You want Chrome output from a non-Node stack. Playwright has first-class Python, Java, and C# bindings; a REST API is language-neutral by definition.
Is the renderer overkill?
Your documents are static templates. Consider WeasyPrint (browser-free HTML/CSS) or constructing the PDF directly with pdf-lib or PDFKit.
The shortlist at a glance
| TOOL | TYPE | ENGINE | PRICING | BEST FOR |
|---|---|---|---|---|
| Transformy | Managed API | Headless Chrome | Free 100 docs/mo, then $99/mo for 10,000 | Chrome-quality PDFs without operating browsers |
| Playwright | Library (Node.js, Python, Java, C#) | Headless Chrome | Free (your infra) | Puppeteer's job from non-Node stacks |
| Gotenberg | Self-hosted service | Chromium + LibreOffice | Free (your infra) | Service-shaped rendering inside your cluster |
| Headless Chrome CLI | Command-line tool | Headless Chrome | Free | Scripts and scheduled jobs |
| WeasyPrint | Library (Python) | Own CSS paged-media engine | Free | JS-free templates, no browser in the stack |
| pdf-lib / PDFKit | Library (JavaScript) | None (constructs PDF directly) | Free | Simple documents built programmatically |
The managed option: Transformy
Transformy is the "keep the output, lose the browser" move. It renders with real headless Chromium, so everything you rely on Puppeteer for (flexbox, grid, webfonts, JavaScript that settles before capture) carries over. What changes is who operates it: the browser pool, memory recycling, Chrome upgrades, and burst scaling live on the vendor side, behind one authenticated POST.
The migration is unusually pleasant because it is mostly deletion. The launch/newPage/goto/close lifecycle, the retry wrapper, the pool, and the Lambda layer all disappear; page.pdf() options map one-to-one onto request parameters (cheat sheet below). You also pick up plumbing Puppeteer setups rarely build: async jobs with signed webhooks, delivery straight to your own S3/GCS/Azure/R2 bucket, idempotency keys, and a free watermarked test mode.
curl -X POST https://api.transformy.io/v1/pdf/chrome \
-H "Authorization: Bearer tfy_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://app.example.com/reports/q2",
"page_size": "a4",
"footer": { "html": "<span style=\"font-size:10px\">Page {{page}} of {{pages}}</span>" }
}' \
--output report.pdf
Pricing starts free (100 documents a month, no credit card); the Pro plan is $99/month for 10,000 documents, then $0.01 per additional document. The honest trade-offs: at very large volume, self-hosting is cheaper on paper if you have the team to own it; rendering leaves your network boundary, which some compliance postures forbid; and per-document pricing means a runaway loop costs money instead of memory. If any of those bite, the self-hosted options below are the better call.
Run it yourself: the open-source routes
Playwright
Playwright is the closest thing to Puppeteer without being Puppeteer: same headless Chrome rendering, a broadly similar API, and official bindings for Python, Java, and C# alongside Node.js. If your reason for leaving Puppeteer is language reach or API ergonomics, Playwright solves it cleanly, and our Playwright HTML to PDF guide covers the setup. (Its own exits are mapped in our Playwright alternatives comparison.)
Be clear-eyed about what it does not solve: you still launch, pool, recycle, and upgrade browsers yourself. Swapping Puppeteer for Playwright to fix production browser pain is redecorating the room that is on fire.
Gotenberg
If you want rendering out of your application processes but inside your infrastructure, Gotenberg wraps Chromium (and LibreOffice) in a Docker container with an HTTP API. Your services call it the way they would call a managed API; the container, scaling, and upgrades stay yours. It is the natural middle step between embedding Puppeteer everywhere and buying rendering as a service. We compare its own trade-offs in our Gotenberg alternatives page.
Headless Chrome from the command line
When the Puppeteer script was only ever a cron job, the library may be more machinery than the task needs:
chrome --headless --no-pdf-header-footer --print-to-pdf=report.pdf https://example.com/report
No dependencies, no browser lifecycle code, same engine. The flags run out fast (no proper headers/footers with page numbers, coarse wait control), but for a nightly report, it is honest infrastructure.
WeasyPrint
If your PDFs come from server-rendered templates and never execute JavaScript, WeasyPrint renders HTML and CSS to PDF with its own paged-media engine: no browser anywhere, unusually good @page and page-numbering support, deterministic output, and PDF/A and PDF/UA variants when compliance asks. It is Python-only and runs no JS, which is exactly why it is lighter than everything else in this list. Our WeasyPrint guide covers the common pitfalls, and our WeasyPrint alternatives comparison maps where its limits sit.
pdf-lib and PDFKit: skip rendering entirely
The contrarian option: for genuinely simple documents (receipts, tickets, labels, one-page statements), you can construct the PDF directly in JavaScript with pdf-lib or PDFKit, placing text, images, and tables with code instead of rendering HTML. No engine, no fonts-not-loading mysteries, microsecond-fast, and trivially serverless. The cost is that layout is now manual: every alignment and wrap is your code, so complexity that a stylesheet would absorb becomes arithmetic. It is the right tool for a barcode label and the wrong tool for a styled 12-page report. If you find yourself reimplementing line wrapping, go back to HTML rendering.
Migrating: mapping page.pdf() options to Transformy
The core of most migrations is deleting the browser lifecycle and renaming options:
- const browser = await puppeteer.launch();
- const page = await browser.newPage();
- await page.goto(url, { waitUntil: "networkidle0" });
- await browser.close(); // plus the pool, the retries, the Lambda layer
+ one authenticated POST; JavaScript settles automatically
- format: "A4"
+ "page_size": "a4"
- landscape: true
+ "orientation": "landscape"
- margin: { top: "2cm" }
+ "margin": {"top": "2cm"}
- printBackground: true
+ "print_background": true # already the default
- pageRanges: "1-5" / scale: 1.3
+ "page_ranges": "1-5" / "zoom": 1.3
- preferCSSPageSize: true
+ "prefer_css_page_size": true
- headerTemplate with <span class="pageNumber">
+ "header": {"html": "… {{page}} of {{pages}} …"}
Two behavioral notes. Header and footer HTML stays an isolated fragment in both worlds, so keep styles inline there. And where Puppeteer setups tune waitUntil and manual delays per page, the API waits for JavaScript to settle by default; ports usually delete wait logic rather than translate it.
Frequently asked questions
Is Playwright better than Puppeteer for PDF generation?+
The PDF output is equivalent (both drive headless Chrome). Playwright wins on cross-language bindings (Python, Java, C#) and some API ergonomics. Neither changes the operational cost of running browsers in production.
Can Puppeteer run in AWS Lambda?+
Yes, with community Chromium builds and careful packaging, and it remains one of Puppeteer's most persistent friction points: size limits, cold starts, and version drift between the build and the library. If Lambda PDFs are your pain, an HTTP API (managed or self-hosted) sidesteps the packaging entirely.
Do I need a browser at all for invoices and receipts?+
Often not. If the template is static HTML/CSS with no JavaScript, WeasyPrint renders it without a browser; if it is simple enough to lay out programmatically, pdf-lib or PDFKit skips HTML too. Browsers earn their weight when pages have JS, complex layouts, or live URLs.
What is the difference between Puppeteer and a managed API like Transformy?+
Same rendering engine, different operator. With Puppeteer you own the browser lifecycle, pooling, scaling, and upgrades; with a managed API you send one authenticated POST and those concerns are the vendor's. You trade per-document fees for deleted ops work.
Is Puppeteer still maintained?+
Yes, very actively. This comparison exists because of the operational cost of self-hosting browsers and Puppeteer's Node-only reach, not because of any project-health concern.
The bottom line
Name your actual problem and the field narrows fast. Browser ops eating your sprints: move rendering behind an API, managed with Transformy (the free tier covers 100 documents a month, no credit card, enough to port your page.pdf() options against the table above) or self-hosted with Gotenberg. Wrong language: Playwright brings the same engine to Python, Java, and C#. Renderer overkill: WeasyPrint for JS-free templates, pdf-lib or PDFKit for documents simple enough to construct directly. And if part of your estate still runs the abandoned wkhtmltopdf, our wkhtmltopdf alternatives comparison handles that migration in the same spirit.
Puppeteer made "a browser you can script" free. What it cannot make free is the browser itself; every alternative above is a different answer to who should carry it.