Guide
Generate a PDF from HTML in Django
You have a Django app, an HTML template, and you need it as a downloadable PDF: an invoice, a report, a ticket. This guide shows a working view that posts your HTML to the PDFPipe API and streams the result back as an application/pdf response. It also covers when a pure Python library like WeasyPrint or xhtml2pdf is the better fit.
The view
The pattern is simple: render an existing Django template to an HTML string with render_to_string, post that string to POST /v1/pdf, and return the raw bytes. The API responds with the PDF body directly, so you assign response.content to an HttpResponse.
# views.py
import requests
from django.conf import settings
from django.http import HttpResponse
from django.template.loader import render_to_string
def invoice_pdf(request, invoice_id):
invoice = get_invoice_or_404(invoice_id)
# Render an ordinary Django template to an HTML string.
html = render_to_string("invoices/invoice.html", {"invoice": invoice})
resp = requests.post(
"https://api.pdfpipe.xyz/v1/pdf",
headers={
"Authorization": f"Bearer {settings.PDFPIPE_API_KEY}",
"Content-Type": "application/json",
},
json={
"html": html,
"options": {"format": "A4"},
},
timeout=30,
)
resp.raise_for_status()
pdf = HttpResponse(resp.content, content_type="application/pdf")
pdf["Content-Disposition"] = f'attachment; filename="invoice-{invoice_id}.pdf"'
return pdfKeep the key out of your code. Read it from an environment variable in settings.py:
# settings.py
import os
PDFPIPE_API_KEY = os.environ["PDFPIPE_API_KEY"] # pp_live_your_keyWire up the URL
Nothing unusual here. Point a route at the view and you have a download endpoint.
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path("invoices/<int:invoice_id>/pdf/", views.invoice_pdf, name="invoice_pdf"),
]Inline display instead of download
To show the PDF in the browser tab rather than trigger a download, switch the disposition to inline. Everything else stays the same.
pdf["Content-Disposition"] = f'inline; filename="invoice-{invoice_id}.pdf"'How this compares to WeasyPrint and xhtml2pdf
WeasyPrint and xhtml2pdf render entirely in your Python process. That is genuinely useful: no network call, no external dependency, and data never leaves your server. The tradeoff is rendering fidelity. Neither runs a browser engine, so modern CSS (flexbox in older WeasyPrint versions, CSS grid, web fonts, and especially anything that needs JavaScript such as charts) renders poorly or not at all. WeasyPrint also pulls in native libraries like Pango and Cairo, which can be awkward to install in slim Docker images and on serverless platforms.
PDFPipe renders with a full browser engine, so the PDF matches what users see on screen. The cost is an outbound HTTP request and a dependency on a third party. Pick based on what your documents need.
| Aspect | WeasyPrint / xhtml2pdf | PDFPipe API |
|---|---|---|
| Runs where | In your Python process | Hosted, over HTTP |
| Rendering engine | Custom Python renderer | Headless Chrome |
| JavaScript and charts | Not supported | Supported |
| Modern CSS fidelity | Partial, version dependent | Full, matches Chrome |
| Native deps to install | Pango, Cairo, GDK (WeasyPrint) | None, just requests |
| Data leaves server | No | Yes, sent to the API |
| Offline / air-gapped | Works | Needs network |
Handling errors and timeouts
Since this is a network call, treat it like one. The timeout argument prevents a hung request from blocking your worker, and raise_for_status() surfaces a bad API key or malformed payload as an exception you can log or convert into a clean error page. For high traffic, generate the PDF in a Celery task and store it, rather than rendering on every request.
from django.http import HttpResponseServerError
try:
resp = requests.post(
"https://api.pdfpipe.xyz/v1/pdf",
headers={"Authorization": f"Bearer {settings.PDFPIPE_API_KEY}"},
json={"html": html, "options": {"format": "A4"}},
timeout=30,
)
resp.raise_for_status()
except requests.RequestException:
return HttpResponseServerError("Could not generate the PDF.")Paste your own HTML into the live playground on the home page to see the output instantly, or read the full endpoint reference in the docs.
See pricing →