Skip to content

How to convert HTML to PDF in Python

Three ways that work, with code: WeasyPrint (pure Python, no browser), Playwright (real Chrome), or an API call that returns the PDF.

By Saurav N, senior software engineer · Updated

· 6 min read

WeasyPrint: no browser needed

WeasyPrint lays out HTML and CSS itself, with strong support for print CSS: @page, running headers and footers, and page counters. It needs Pango and its system libraries installed.

It does not run JavaScript, and some newer layout CSS behaves differently from Chrome, so check documents that use flexbox or grid heavily.

weasy.py
from weasyprint import HTML

html = "<h1>Invoice INV-2026-0142</h1><p>Total ₹49,402.50</p>"
HTML(string=html).write_pdf("invoice.pdf")

Playwright: the same output as Chrome

Playwright prints with Chromium, so the PDF matches what you see in the browser, JavaScript included. You carry the cost of running a browser.

playwright_pdf.py
from playwright.sync_api import sync_playwright

html = "<h1>Invoice INV-2026-0142</h1><p>Total ₹49,402.50</p>"

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.set_content(html)
    page.pdf(path="invoice.pdf", format="A4", print_background=True)
    browser.close()

An HTML to PDF API from Python

With Templater your code sends data to a saved template and gets the PDF back; there is nothing to install.

render_api.py
import os
import requests

res = requests.post(
    "https://templater.nestutils.com/v1/pdfs/render",
    params={"wait": 20},
    headers={"Authorization": f"Bearer {os.environ['TEMPLATER_KEY']}"},
    json={
        "template": "gst-invoice",
        "returnAs": "binary",
        "data": {"invoice": {"number": "INV-2026-0142", "total": 49402.5}},
    },
    timeout=30,
)

if res.status_code == 202:
    print("still rendering:", res.json()["data"]["id"])  # poll GET /v1/pdfs/{id}
else:
    res.raise_for_status()
    with open("invoice.pdf", "wb") as f:
        f.write(res.content)

Which one should you use?

WeasyPrint suits reports and documents with lots of print CSS and no JavaScript. Playwright suits pages that must match Chrome exactly.

An API suits you when: You want the same template edited by your team in a browser, previewed on real data, versioned, and rendered without running Chrome yourself. You pay per render instead of per server.

Questions

Is wkhtmltopdf still an option?

It still runs, but the project is no longer maintained and it uses an old WebKit, so modern CSS often breaks. Prefer WeasyPrint or Chrome.

How do I use Django or Jinja templates?

Render the template to an HTML string first (render_to_string in Django), then pass that string to WeasyPrint or Playwright.

Why is the PDF blank or missing images?

Relative image paths have nothing to resolve against. Use absolute URLs, or pass base_url to WeasyPrint’s HTML().

Try it on your own template

Start Free