WeasyPrint alternative

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.

@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.

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.

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.

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.

This page as markdown · text