COMPARISON

WeasyPrint alternatives in 2026: when you need JavaScript, speed, or a different stack

The best WeasyPrint alternative depends on which of its deliberate limits you have hit. If your documents need JavaScript (charts, SPAs, client-rendered content), you need a browser engine: managed via an API like Transformy, or self-run with Playwright or Puppeteer. If renders have grown slow at volume, moving to headless Chrome usually helps. And if your documents are simple enough, ReportLab skips HTML rendering entirely.

Credit where due: WeasyPrint is one of the best-kept secrets in the PDF world, and we say so on every comparison page we publish. If your templates are JavaScript-free, server-rendered, and Python-adjacent, the correct alternative to WeasyPrint is usually WeasyPrint.

render.log
$ python render.py --template dashboard.html
WARNING: <script> ignored (WeasyPrint runs no JavaScript)
WARNING: <script> ignored (chart.umd.js)
.       #revenue-chart rendered empty

pages ················ 118
render time ·········· 41.3s
javascript ··········· not supported (by design)

$ 

KEY TAKEAWAYS

The four reasons teams outgrow WeasyPrint

1. The documents grew JavaScript. WeasyPrint intentionally does not execute scripts. The day a stakeholder wants the revenue chart from the dashboard in the PDF, or the template picks up a component that renders client-side, you have left WeasyPrint's design space. Warnings in the render log about ignored scripts are the usual first symptom.

2. Renders got slow at volume. WeasyPrint's layout engine is pure Python. For a 3-page invoice this is irrelevant; for a 150-page catalog or a nightly batch of thousands of statements, render times and memory use become the bottleneck, and there is no browser pool to scale out because there is no browser.

3. The stack is not Python. WeasyPrint is a Python library. Node, PHP, Java, and Go teams can only reach it through a sidecar service, at which point the "one pip install, no infrastructure" advantage has already been spent.

4. Fidelity expectations are set by a browser. WeasyPrint's CSS support is deep for print but it is not Chromium. Designers check templates in a browser, and modern layouts (or third-party HTML you do not control) can render differently. If "matches what Chrome shows" is the acceptance test, only a Chrome engine passes it by definition.

Tip

If none of the four apply, stop reading and keep WeasyPrint. It is the recommendation we make to browser-tool users in the reverse situation, and it cuts both ways.

What to match the alternative to

Need JavaScript or browser-exact output, want no ops

Transformymanaged Chrome API

Need JavaScript, have ops capacity, staying in Python

Playwright or PuppeteerPython bindings included

Want rendering as a service inside your cluster

Gotenbergin Docker

A script or cron job, not an application

Headless Chrome CLIone flag, one PDF

Documents simple enough to build with code

ReportLabPython, no HTML step

The shortlist at a glance

TOOL TYPE ENGINE JAVASCRIPT PRICING BEST FOR
Transformy Managed API Headless Chrome Yes Free 100 docs/mo, then $99/mo for 10,000 Browser-exact PDFs and JS content without ops
Playwright Library (Python, Node.js, Java, C#) Headless Chrome Yes Free (your infra) DIY Chrome rendering, straight from Python
Puppeteer Library (Node.js) Headless Chrome Yes Free (your infra) Same, for Node-first teams
Gotenberg Self-hosted service Chromium + LibreOffice Yes Free (your infra) Service-shaped rendering inside your cluster
Headless Chrome CLI Command-line tool Headless Chrome Yes Free One-off conversions and scheduled jobs
ReportLab Library (Python) None (constructs PDF directly) n/a Free (open-source core) Simple documents built programmatically in Python

The managed option: Transformy

Transformy covers the first, second, and fourth reasons at once: it renders with real headless Chromium, so JavaScript runs and settles before capture, output matches what Chrome shows, and scale is the vendor's problem rather than a Python process's. Your integration stays close to WeasyPrint's mental model (HTML in, PDF out); it just happens over one authenticated POST instead of an import.

For WeasyPrint users specifically, the notable details: your @page size and margin rules keep working (set prefer_css_page_size and Chrome honors them), webfonts load the way a browser loads them instead of through font-configuration plumbing, and the request is stack-neutral, so the Django service and the Node service call the same endpoint. Delivery options (straight to your own S3/GCS/Azure/R2 bucket, async jobs with signed webhooks, idempotency keys) replace the file-handling code around write_pdf.

curl -X POST https://api.transformy.io/v1/pdf/chrome \
  -H "Authorization: Bearer tfy_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<html>…rendered Django template…</html>",
    "prefer_css_page_size": true,
    "footer": { "html": "<span style=\"font-size:10px\">Page {{page}} of {{pages}}</span>" }
  }' \
  --output statement.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 losses relative to WeasyPrint: no PDF/A or PDF/UA output, so regulated archival workflows should stay put; rendering leaves your network boundary; byte-identical determinism is weaker, since Chrome evolves (upgrades are pinned and regression-tested, but "frozen forever" it is not); and a per-document fee replaces a free library.

Run it yourself: the open-source routes

Playwright and Puppeteer

Playwright is the natural DIY move for a WeasyPrint team because its first-class Python bindings mean no language change: page.pdf() from the same codebase, rendered by real Chrome, JavaScript included. Puppeteer is the Node-first equivalent. Both produce browser-exact output and both hand you the browser-operations bill: pools, memory recycling, crash recovery, and Chrome upgrades are now yours, which is a genuine step up in ops from a pip-installed library. Our Playwright HTML to PDF guide and Puppeteer guide cover the setup; our Puppeteer alternatives page covers where that road leads at scale.

Gotenberg

If you want browser rendering as a service but inside your own infrastructure, Gotenberg wraps Chromium (and LibreOffice) in a Docker container behind an HTTP API. It is the shape of a managed API with the ops of self-hosting: your services POST HTML and receive PDFs, and your cluster carries the container, its memory, and its upgrades. A common WeasyPrint-outgrowth pattern is exactly this: keep templates unchanged, stand up Gotenberg, and swap the render call. We weigh its own trade-offs in our Gotenberg alternatives page.

Headless Chrome from the command line

For scripts and scheduled jobs, Chrome itself is a converter:

chrome --headless --no-pdf-header-footer --print-to-pdf=report.pdf https://example.com/report

No library, no service; also no proper page-number headers and only coarse control. Right for a nightly report, wrong for an application feature.

ReportLab

The alternative in the opposite direction: if your documents are simple and structured (labels, receipts, certificates, data-driven statements), ReportLab builds the PDF programmatically in Python, with no HTML or CSS step at all. It is fast, dependency-light, and gives absolute placement control; the cost is that layout is code, so every wrap, break, and alignment is yours to manage. Teams sometimes discover their WeasyPrint templates were reimplementing what a ReportLab table flowable does natively, and sometimes the reverse. The dividing line is styling complexity: rich brand styling favors HTML/CSS rendering; rigid data layout favors construction.

Migrating: what carries over to Chrome, and what does not

Moving from WeasyPrint to any Chrome-based renderer (Transformy, Playwright, Gotenberg alike) is mostly smooth because your input is already clean, server-rendered HTML:

migrate.diff -6 +6
- HTML(string=html).write_pdf("out.pdf")
+ POST /v1/pdf/chrome  {"html": "..."}

  @page { size: A4 landscape; margin: 2cm }
+ keeps working  ("prefer_css_page_size": true)

- @bottom-center { content: counter(page) " / " counter(pages) }
+ "footer": {"html": "… {{page}} of {{pages}} …"}  # Chrome has no @page margin boxes

- FontConfiguration() and font plumbing
+ webfonts load like a browser

- WARNING: <script> ignored
+ JavaScript runs and settles before capture

- write_pdf(pdf_variant="pdf/a-3b")
# no PDF/A in Chrome-based tools; keep WeasyPrint for archival docs

The margin-box row is the one that costs real work: WeasyPrint's elegant pure-CSS running headers and page numbers have no Chrome equivalent, and the replacement (self-contained header/footer HTML fragments with inline styles) is clunkier. Budget template time for it, and test page-break behavior on your longest real documents while you are at it.

A practical pattern for mixed estates: route by document. JavaScript-bearing and browser-checked documents go to the Chrome renderer; PDF/A archival documents stay on WeasyPrint. Nothing forces a single engine for everything.

Frequently asked questions

Is WeasyPrint still maintained?+

Yes, actively, and it keeps improving (flexbox and grid support have both landed in recent years). This page exists for teams whose requirements crossed WeasyPrint's deliberate limits, not because of project health.

Can WeasyPrint run JavaScript?+

No, by design, and there is no plugin or flag that changes it. If your documents need charts or client-rendered content, you need a browser engine: Transformy, Playwright, Puppeteer, or Gotenberg.

Is wkhtmltopdf a good WeasyPrint alternative?+

No. wkhtmltopdf is archived with unpatched critical CVEs and a 2012-era engine; moving there from WeasyPrint would be a downgrade on every axis except JavaScript, where it is also outdated. See our wkhtmltopdf alternatives page for that migration.

WeasyPrint vs Puppeteer: which should I pick?+

Pick by JavaScript and stack. JS-free templates in Python: WeasyPrint stays lighter and more deterministic. JS-bearing documents or browser-exact fidelity: Puppeteer (or Playwright, which speaks Python too). If neither of you wants to run the browser, that is the managed-API case.

Why is WeasyPrint slow on my large documents?+

Its layout engine is pure Python, so very long or deeply nested documents pay a real CPU cost. Mitigations: simplify DOM depth, split documents and merge afterward, or move those particular documents to a Chrome-based renderer where the engine is native code.

The bottom line

Keep WeasyPrint for what it is best at; leave it only for the reason you actually have. JavaScript or browser-exact output with no ops appetite: Transformy, where the free tier (100 documents a month, no credit card) is enough to test whether your @page CSS survives the trip using the table above. The same needs with ops capacity: Playwright from Python, or Gotenberg for a service shape. Documents simpler than HTML: ReportLab. And documents that need PDF/A: stay put, with our blessing.

WeasyPrint proved you do not always need a browser to make a good PDF. The alternatives above are for the documents where you do.

Generate your first PDF

Free for your first 100 documents a month. No credit card required.