DocuQueue Blog

Professional PDF generation — templates, APIs, and the art of looking legitimate.

TL;DR: Python has four main HTML-to-PDF options: Playwright (best for JavaScript-heavy pages), WeasyPrint (best for structured documents), pdfkit (simple wrapper), and xhtml2pdf (pure Python). For production use, an API like DocuQueue eliminates infrastructure overhead.

Written by Arun, Founder of DocuQueue


Generating a PDF from HTML shows up in almost every backend at some point — an invoice, a signed contract, a report a customer downloads. Python has no shortage of ways to do it, and picking the wrong one usually shows up later, as a slow request, a broken layout, or a dependency nobody wants to touch.

This guide walks through the main options — when to reach for a browser-based renderer, when a pure-Python library is enough, and when it’s worth skipping the infrastructure entirely and calling an API instead.

Why HTML as your PDF source

Most teams end up generating PDFs from HTML rather than a dedicated PDF library because:

  • You already know it. CSS handles layout, branding, and print styles without learning a separate document format.
  • It’s testable in a browser. You can preview the exact markup before it ever becomes a PDF.
  • It’s flexible. Tables, images, page breaks, and even charts render the same way they would on a webpage.

The tradeoff is that “HTML to PDF” isn’t one problem — a marketing site with client-side JavaScript and a static invoice template need very different renderers.

The Python options

Playwright — best for real, JavaScript-heavy pages

Playwright drives an actual browser engine, so if your source page depends on client-side rendering, this is usually the right tool. It’s heavier to set up (it installs a real browser binary) but the output matches what a user actually sees.

pip install playwright
playwright install chromium
from playwright.sync_api import sync_playwright

def html_to_pdf(html: str, output_path: str):
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        page.set_content(html, wait_until="networkidle")
        page.pdf(path=output_path, format="A4")
        browser.close()

WeasyPrint — best for structured documents

WeasyPrint doesn’t run a browser at all — it’s a dedicated HTML/CSS-to-PDF rendering engine, which makes it lighter to deploy and well suited to print-style layouts like invoices and reports, where you want predictable pagination rather than a snapshot of a live page.

pip install weasyprint
from weasyprint import HTML

def html_to_pdf(html: str, output_path: str):
    HTML(string=html).write_pdf(output_path)

xhtml2pdf — best for simple layouts

xhtml2pdf is the lightest dependency of the group, with no browser and no external rendering engine — but it also supports the least CSS, so it’s a better fit for simple, table-heavy documents than for anything visually complex.

pip install xhtml2pdf
from xhtml2pdf import pisa

def html_to_pdf(html: str, output_path: str):
    with open(output_path, "wb") as f:
        pisa.CreatePDF(html, dest=f)

python-pdfkit — a maintained wrapper, over an unmaintained engine

pdfkit is a thin Python wrapper around wkhtmltopdf. It’s still in a lot of production codebases, but worth flagging clearly: the wkhtmltopdf project itself is archived and no longer maintained, so this is generally a “keep it running” choice for existing systems rather than a pick for something new.

pip install pdfkit
import pdfkit

def html_to_pdf(html: str, output_path: str):
    pdfkit.from_string(html, output_path)

Comparing the four

  Playwright WeasyPrint xhtml2pdf pdfkit
Renders JavaScript Yes No No Limited
CSS support Strongest, browser-accurate Strong for print/page layouts Basic Depends on wkhtmltopdf
Setup weight Heavy (browser binary) Moderate (system libs) Light Requires wkhtmltopdf install
Best for Live web pages, dashboards Invoices, reports, contracts Simple table-based docs Legacy systems already using it
Maintenance outlook Actively maintained Actively maintained Actively maintained Underlying engine unmaintained

Rule of thumb: if the source has to run JavaScript to look right, use Playwright. If you’re generating a structured business document from data you already have, WeasyPrint is usually the cleaner fit. Reach for xhtml2pdf only when the layout is genuinely simple, and treat pdfkit as something to migrate away from rather than adopt fresh.

When a library isn’t the right layer

All four options above mean you’re now responsible for the renderer: browser binaries or system packages to install, memory usage to watch under load, and — for the browser-based options — the exact kind of infrastructure that tends to be the first thing to break in production (timeouts, zombie processes, container image bloat).

If PDF generation is a small part of what your app does, it’s often simpler to hand the rendering off entirely and just send HTML or a URL to an API:

import requests

response = requests.post(
    "https://api.docuqueue.com/v1/convert/html",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"html": "<h1>Invoice #1042</h1><p>Total: $480.00</p>"},
)

pdf_url = response.json()["download_url"]

Or straight from a URL:

response = requests.post(
    "https://api.docuqueue.com/v1/convert/url",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={"url": "https://example.com/invoice/1042"},
)

No browser install, no rendering engine to patch — you get a finished PDF back from one request.

If what you actually need is a document, not a page

There’s a related but different problem worth calling out: if you’re not converting an arbitrary page, but generating the same structured document repeatedly — an invoice, a certificate, a contract — with your own data dropped in, you don’t need to hand-build that HTML at all. DocuQueue’s template fill endpoint takes a template and your data directly:

response = requests.post(
    "https://api.docuqueue.com/v1/templates/invoice/fill",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "customer": {"name": "Acme Corp"},
        "invoice_number": "INV-1042",
        "line_items": [
            {"name": "Consulting", "quantity": 4, "unit_price": 120},
        ],
    },
)

And for generating many at once — end of month, end of cohort, end of a batch job — fill-batch takes an array of records and returns a PDF per entry in one call, rather than looping requests one at a time.

Which path to take

  • Prototyping or a one-off page snapshot: any of the four Python libraries will do; start with WeasyPrint if the source is your own HTML.
  • Rendering JavaScript-heavy pages in production, at scale: either Playwright with real infrastructure planning around it, or an API that runs the browser for you.
  • Recurring business documents (invoices, certificates, contracts) from structured data: skip HTML generation entirely and fill a template directly — it’s less code to maintain and there’s no rendering layer to keep patched.

DocuQueue converts URLs and HTML to PDF, and fills 6 professional templates (invoice, contract, certificate, letter, report, shipping label) with your own data via API. Try it free — 25 credits/month, no credit card required.

Frequently Asked Questions

What is the best Python library for HTML to PDF?

Playwright is best for JavaScript-heavy pages because it uses a real browser engine. WeasyPrint is best for structured documents like invoices and reports. For simple use cases, pdfkit or xhtml2pdf work fine.

How do I convert HTML to PDF in Python?

Use Playwright for complex pages: page.pdf(path="output.pdf", format="A4"). For simpler documents, use WeasyPrint: weasyprint.HTML(string=html).write_pdf("output.pdf"). For production use, consider an API like DocuQueue to avoid infrastructure overhead.

What's the difference between Playwright and WeasyPrint?

Playwright drives a real browser, so it handles JavaScript, dynamic content, and CSS exactly like a user sees it. WeasyPrint is a dedicated HTML/CSS-to-PDF engine — lighter to deploy, better for print-style layouts, but no JavaScript support.

Can I generate PDFs from HTML without a browser?

Yes. WeasyPrint, pdfkit, and xhtml2pdf are pure-Python libraries that don't require a browser. They're lighter to deploy but may not render JavaScript-heavy pages correctly.

When should I use an API instead of a Python library?

Use an API when you need to scale beyond a single server, want to avoid managing browser infrastructure, or need features like batch processing, webhooks, and template management. DocuQueue handles all of this with a simple REST API.