WeasyPrint alternative
WeasyPrint alternative: WeasyPrint has great print CSS but no JavaScript and partial flexbox and grid. See when Chrome rendering fits better.
Quick answer. If your HTML needs JavaScript, modern flexbox or grid, use a Chromium renderer. With html-pdf.com it is one call.
curl https://html-pdf.com/v1/pdf \
-H "Authorization: Bearer $HTML_PDF_KEY" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Hello</h1>", "format": "A4"}' \
-o report.pdf
WeasyPrint is a BSD-licensed Python library that turns HTML and CSS into PDF with its own layout engine. It is one of the best tools for print-focused CSS. It is also not a browser, which matters when your HTML depends on JavaScript or modern layout.
This page is a fair look at both sides.
What WeasyPrint does well
These are the areas where WeasyPrint is strong.
- Paged media CSS.
@pagerules, named pages, margin boxes for running headers and footers,counter(page)andcounter(pages), page-based cross references, and footnotes. - Typesetting features aimed at books, reports, and documentation.
- No browser to install. Pure Python plus system libraries (Pango), so it runs offline and in locked-down environments.
- Permissive license. BSD, free for commercial use.
@page {
size: A4;
margin: 20mm;
@bottom-center { content: "Page " counter(page) " of " counter(pages); }
}
Where WeasyPrint falls short
These are the limits you are most likely to hit.
- No JavaScript. Scripts don't run, so charts (Chart.js, D3, ECharts), client-rendered tables, and SPA pages come out empty. You would have to pre-render charts to SVG or images.
- Partial flexbox. The docs describe flex support as working for simple cases but not deeply tested.
- Basic grid.
display: grid, grid areas, andfrwork. Features likeauto-fill/auto-fit, subgrid, and intrinsic sizing aren't supported. - Different from the browser. Because it is a separate engine, a page that looks right in Chrome can lay out differently. You debug in the PDF rather than DevTools.
- System dependencies. Pango and related libraries need to be installed in Docker images and on some platforms.
Comparison
This table compares WeasyPrint, self-hosted Chromium and the html-pdf.com API side by side.
| WeasyPrint | Playwright/Puppeteer (self-hosted) | html-pdf.com API | |
|---|---|---|---|
| Engine | Own layout engine | Chromium | Chromium (hosted) |
| JavaScript | No | Yes | Yes |
| Flexbox / grid | Partial / basic | Full | Full |
Margin boxes (@top-center etc.) |
Yes | Yes (Chrome 131+) | Yes, or header_html / footer_html |
| Running headers, page counters | Yes, in CSS | Header/footer templates | header_html / footer_html |
| Footnotes, cross references | Yes | No | No |
| Works offline | Yes | Yes | No |
| License / cost | BSD, free | Free + servers | 1 credit per page |
When WeasyPrint is the better choice
WeasyPrint is the right pick in these cases.
- Print-CSS-heavy documents like books, manuals, and reports that rely on footnotes and page-based cross references. Chrome handles margin boxes and page counters, but not these.
- Offline or air-gapped systems, or when content must never leave your servers.
- Static HTML with no JavaScript and simple layouts.
If that is your case, WeasyPrint is likely a better fit than any Chrome-based tool.
When Chrome rendering fits better
Chrome is the better choice when your PDF should match what the browser shows.
- The HTML is the same page your app shows in the browser, built with flexbox, grid, or Tailwind.
- You need charts or other JavaScript to run before printing.
- You want to convert a live URL as it looks in Chrome.
- You don't want to maintain Pango or Chrome in your images.
Chrome supports @page size and margins, break-before, break-after, and break-inside. Since Chrome 131 it also renders @page margin boxes such as @bottom-center { content: counter(page) }. That means most WeasyPrint running headers and page numbers work as-is, and header and footer templates are an alternative.
Python example
This example renders a Chart.js report with a CSS grid and page numbers.
import os
import requests
html = """
<!doctype html>
<html><head>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body { font-family: system-ui, sans-serif; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
</style>
</head><body>
<h1>Monthly report</h1>
<div class="grid"><div>Revenue: $12,400</div><div>Orders: 318</div></div>
<canvas id="c" width="600" height="300"></canvas>
<script>
new Chart(document.getElementById("c"), {
type: "bar",
data: { labels: ["Jan", "Feb", "Mar"], datasets: [{ label: "Sales", data: [12, 19, 7] }] },
options: { animation: false }
});
window.chartReady = true;
</script>
</body></html>
"""
resp = requests.post(
"https://html-pdf.com/v1/pdf",
headers={"Authorization": f"Bearer {os.environ['HTML_PDF_KEY']}"},
json={
"html": html,
"format": "A4",
"margin": {"top": "15mm", "right": "12mm", "bottom": "20mm", "left": "12mm"},
"footer_html": '<div style="font-size:9px;width:100%;text-align:center">'
'Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
"wait_for_expression": "window.chartReady === true",
"max_pages": 20,
},
timeout=40,
)
resp.raise_for_status()
with open("report.pdf", "wb") as f:
f.write(resp.content)
wait_for_expression holds the print until the chart has drawn. Turning off Chart.js animation avoids capturing a half-drawn chart. The bottom margin leaves room for footer_html.
Mapping WeasyPrint habits
Use this table to translate common WeasyPrint calls to html-pdf.com options.
| WeasyPrint | html-pdf.com |
|---|---|
HTML(string=html).write_pdf("out.pdf") |
{"html": html} and save resp.content |
HTML(url).write_pdf(...) |
{"url": "https://..."} (public pages) |
@page { size: A4 landscape } |
Keep it with prefer_css_page_size, or format + landscape |
@bottom-center { content: counter(page) } |
works as-is (Chrome 131+), or footer_html with pageNumber / totalPages |
stylesheets=[CSS(...)] |
"css": "..." |
media_type="print" |
"media": "print" |
Or skip the server
If you would rather not run any renderer, use the hosted API. html-pdf.com is an HTML/URL to PDF API on headless Chromium. POST https://html-pdf.com/v1/pdf with a Bearer key and {"html": "..."} or {"url": "https://..."} returns a PDF with selectable text, as binary, base64, or a 24-hour link.
1 credit = 1 PDF page. You get 100 free credits once, then packs from $10 for 1,000 pages down to $0.005 per page, and purchased credits never expire. AI assistants can also use it through the remote MCP server.
Get a free API key · API docs · HTML to PDF in Python · Page breaks · Templates
FAQ
What is the best alternative to WeasyPrint?
If your HTML uses JavaScript, charts, or modern flexbox and grid, a Chromium renderer like Playwright, Puppeteer or the html-pdf.com API is the closest fit. For footnote-heavy print documents, WeasyPrint is still a strong choice.
Does WeasyPrint support JavaScript?
No. WeasyPrint does not run scripts, so charts and client-rendered content come out empty unless you pre-render them to SVG or images.
Can Chrome do CSS page numbers like WeasyPrint?
Yes. Since Chrome 131, @page margin boxes such as @bottom-center { content: "Page " counter(page) " of " counter(pages) } work, and you can use prefer_css_page_size to keep your CSS page size. You can also use footer_html instead.
Is WeasyPrint free?
Yes, it is BSD-licensed and free for commercial use. html-pdf.com charges 1 credit per PDF page, with 100 free credits to start and no card needed.
