§ Django HTML to PDF
Django HTML to PDF: Convert a Django Template to a Real PDF
Turn a Django template into a searchable, pixel accurate PDF with one API call. Managed Chromium renders your real template with full CSS, web fonts and JavaScript charts, so the file matches the page, no WeasyPrint CSS limits and no headless browser to run and scale yourself. Try it on any URL or HTML below.
- Early access, launching soon
- No card required
- Your HTML stays yours
§ Live demo
Runs in your browser. Nothing is uploaded.
Your HTML
Live preview
Your PDF is downloading. Want this as one API call, with the page archived too?
§ Short answer
To convert HTML to PDF in Django, render your template to an HTML string with render_to_string, then hand that HTML to something that lays it out. WeasyPrint does this in pure Python and is the best free choice for styled templates, but it has no JavaScript engine, so client-side charts and maps come out blank. When the page depends on JavaScript, or you want output identical to a real browser and do not want to install and scale a renderer, post the HTML to a rendering API instead. Sitepdf runs that final step in managed Chromium and returns a true, selectable vector PDF, plus an optional timestamped archive of exactly what it rendered.
Last updated July 2026. Written and fact checked by the Sitepdf team.
§ 00
Three ways to make a PDF in Django, compared
Django ships no PDF export, so every project picks one of these three routes. They differ most in whether a real browser does the rendering, which decides your CSS fidelity, whether JavaScript runs, and what you have to install and run.
| Approach | How it works | CSS and JavaScript | What you run | Best for |
|---|---|---|---|---|
| WeasyPrint django-weasyprint |
Renders your template HTML and CSS to a PDF in pure Python, no browser | Strong CSS including @page paged media, but no JavaScript engine at all | A Python library plus its Pango and Cairo system libraries | Server-rendered, styled templates with no client-side JavaScript |
| xhtml2pdf, ReportLab code path |
xhtml2pdf converts simple HTML on top of ReportLab; ReportLab draws the page in code | Limited CSS in xhtml2pdf; ReportLab ignores HTML and CSS by design | A pip-only, binary-free Python install | Simple templated receipts, or documents you build in code |
| Server-side Chromium API Sitepdf |
Renders your real template or route in a managed browser | Full modern CSS and JavaScript, it is a real current browser | One HTTP call, no renderer or browser in your stack | Production documents, JavaScript charts, exact fidelity, a dated record |
The honest read: WeasyPrint is genuinely excellent and free, and if your invoices and reports are server-rendered HTML with CSS and no JavaScript, it may be all you ever need. A rendering API earns its place in three cases: the page draws content with JavaScript (Chart.js, a map, a live widget), you want the PDF to match a real browser pixel for pixel, or you do not want to install Pango and Cairo and then scale a renderer under load.
§ 01
The three real options in Django, and what separates them
Django deliberately leaves PDF generation to you, so the question is never "which Django feature" but "which rendering engine". There are really three families. The first is WeasyPrint, usually wired in with django-weasyprint: it reads your HTML and CSS and produces a print-quality PDF entirely in Python, with no browser involved. The second is the ReportLab family, where xhtml2pdf converts a limited subset of HTML on top of ReportLab, and ReportLab itself lets you draw the document in code. The third is a headless browser, either one you run yourself with Playwright, or a hosted rendering API, which is the only route that runs your JavaScript and matches a real browser exactly.
The line that matters runs between "a browser renders it" and "a library renders it". A library like WeasyPrint is fast, free and has no browser to babysit, but it will never run the JavaScript that draws a Chart.js graph, and its CSS support, while good, is a subset of what Chromium does. A browser runs everything your users see, at the cost of being a browser: memory, cold starts and scaling. Picking the right option is mostly about whether your document depends on JavaScript and how close to the on-screen page it has to look.
§ 02
WeasyPrint: the best free choice, until JavaScript enters
For a server-rendered invoice, statement or report, WeasyPrint is hard to beat on price and simplicity. You render the template to a string and hand it over, and you get clean PDF bytes back with real, selectable text.
from django.template.loader import render_to_string
from weasyprint import HTML
html = render_to_string("invoices/invoice.html", {"invoice": invoice})
pdf = HTML(string=html, base_url=request.build_absolute_uri()).write_pdf()
return HttpResponse(pdf, content_type="application/pdf")Two things trip people up. Pass base_url so WeasyPrint can resolve your static CSS, images and fonts, and remember that its layout comes from print CSS, so use the @page rule for margins and page size. The hard limit is JavaScript: WeasyPrint has no JavaScript engine, so a page that builds a chart, a table or a map in the browser will render blank or half-formed. The official advice is to render that data to static HTML or SVG on the server first, or move to a headless-Chrome pipeline. If your documents are pure server-side HTML and CSS, none of that applies and WeasyPrint is the right call. As of its February 2026 release it needs Python 3.10 or newer, plus the Pango and Cairo system libraries on every machine that runs it.
§ 03
The server-side path: render your real template in a browser
When the page has JavaScript, or you simply want the PDF to look exactly like the page in Chrome, let a real browser do the work. You do not have to change how Django builds the page. Render the template to a string as usual, then post that HTML to the render endpoint, or point it at a live route in your app and let it load the page, run the JavaScript and wait for the charts.
from django.template.loader import render_to_string
import requests
html = render_to_string("invoices/invoice.html", {"invoice": invoice})
resp = requests.post(
"https://api.sitepdf.com/v1/render",
headers={"Authorization": f"Bearer {settings.SITEPDF_KEY}"},
data={"html": html, "format": "Letter", "archive": "true"},
)
pdf_url = resp.json()["pdf_url"]Because a current browser does the layout, your CSS grid, web fonts, background colors and JavaScript-drawn charts all render the way they do on screen, and the text stays real and searchable. There is no Pango, Cairo or Chromium to install in your image and no browser process eating memory inside your Django workers. The same call works from Celery for background jobs and from a management command for batch runs. Our HTML to PDF in Python guide shows the plain requests version outside Django, and the PDF generator API page lists every parameter, including margins, page size, headers and the wait signal for charts.
§ 04
Keep a dated copy of every document you generated
A generated invoice, a signed quote or a compliance report leaves your app and nothing verifiable stays behind. Add archive=true to the render call and Sitepdf also stores a timestamped, immutable snapshot of exactly what it rendered, retrievable by id later, so you can prove what a given document said on a given date. That provenance is often the reason to render server side at all. The website archiving page explains how the record works, and the Laravel HTML to PDF page walks the same trade offs for the other big server framework. Renders are transient by default; leave archiving off and we return the PDF and store nothing.
curl https://api.sitepdf.com/v1/render \
-H "Authorization: Bearer $SITEPDF_KEY" \
-F url=https://app.example.com/invoices/8842 \
-F format=Letter \
-F wait_for="#invoice-total" \
-F archive=true
{
"pdf_url": "https://api.sitepdf.com/v1/documents/doc_dj42k.pdf",
"pages": 2,
"rendered_in_ms": 1642,
"archive": {
"id": "arc_dj7px",
"captured_at": "2026-07-19T21:04:12Z",
"retrieve_url": "https://api.sitepdf.com/v1/archives/arc_dj7px"
}
}
The API is in early access; this is the documented call shape it opens with. Full request and response walkthrough.
§ 05
Questions about this job
How do I convert HTML to PDF in Django?
Does WeasyPrint support JavaScript?
WeasyPrint, xhtml2pdf or ReportLab, which should I use in Django?
How do I render a Django template with charts to PDF?
How do I generate a PDF in Django without installing a browser?
§ Index
More PDF and archiving tools
- Convert HTML to PDF
- Webpage to PDF
- Save webpage as PDF
- URL to PDF API
- Website archiving
- Screenshot API
- React to PDF
- Laravel HTML to PDF
- Vue to PDF
- Next.js PDF generator
- Angular to PDF
- Markdown to PDF API
- Blazor HTML to PDF
- Spring Boot HTML to PDF
- Airtable to PDF
- Rails HTML to PDF
- PDF generator API
- Document generation API
- Bulk HTML to PDF
- Wayback Machine alternative
- Best HTML to PDF API
- DocRaptor alternative
- Puppeteer alternative
- Wkhtmltopdf alternative
- PDFShift alternative
- Urlbox alternative
- PDFCrowd alternative
- Api2Pdf alternative
- Browserless alternative
- APITemplate alternative
- CraftMyPDF alternative
- PDFMonkey alternative
- dompdf alternative
- Gotenberg alternative
- GrabzIt alternative
- How it works
- Features
- Pricing
§ Early access
Get on the early-access list
The API opens to the list first, in order. Early access locks the planned launch rates for 12 months. No card required, launching soon.