Content Index
- Why generate a PDF in Django with WeasyPrint?
- Common use cases
- Advantages of converting HTML templates to PDF
- Installing WeasyPrint and dependencies according to your operating system
- Installation on Windows
- Installation on macOS and Linux
- Installing WeasyPrint with pip
- Creating a view to generate a PDF in Django
- Basic example: converting a URL to PDF
- Generating PDF with a Django HTML template (render_to_string)
- Customizing page size and CSS styles
- Saving the generated PDF file on the server
- Recommendations for complex templates
- Alternatives to WeasyPrint for generating PDFs in Django
- Common errors when using WeasyPrint in Django and how to fix them
- OSError: no library called "cairo"
- Images or styles not appearing in the PDF
- CSS styles not applying correctly
- Conclusion
- Frequently asked questions about PDF generation in Django
Generating a PDF from an HTML template in Django is a very common need in any project that needs to dynamically export reports, invoices, or certificates. In this guide, I will show you how to do it step by step using WeasyPrint, a powerful, cross-platform, and well-documented library that directly converts HTML and CSS into PDF without relying on external tools like wkhtmltopdf or Puppeteer.
If you come from reading about how to write CSV files in Django, this is the natural next step: moving from flat exports to documents with professional formatting and design.
Why generate a PDF in Django with WeasyPrint?
Converting an HTML page to PDF directly from Django allows you to present information in a professional and exportable way without leaving your web application. The key advantage over other solutions is that you can leverage Django's template system —with all its context logic, variables, and filters— to build the document you want and then convert it to PDF with just a few lines of code.
Common use cases
- Automatic invoices and estimates.
- User, sales, or business metric reports.
- Downloadable certificates and vouchers.
- Exporting dynamic content like forms, listings, or contracts.
Advantages of converting HTML templates to PDF
- You can reuse your existing Django templates without rewriting anything.
- You maintain total control over the design using CSS, including print rules with
@pageand@media print. - The generated PDFs are compatible with any browser or operating system.
- Real CSS3 support, unlike alternatives like
xhtml2pdfwhich only understand CSS2.
Installing WeasyPrint and dependencies according to your operating system
WeasyPrint is a rendering engine that converts HTML and CSS into PDF following CSS Paged Media standards. Its installation varies slightly depending on the operating system, as it requires some external system libraries (mainly Pango, Cairo, and libffi) to process fonts and page layout.
Installation on Windows
On Windows, WeasyPrint needs the GTK+ environment to run. You must install the following program before installing the package with pip:
GTK for Windows Runtime Environment Installer (GitHub)
Once installed, make sure that the GTK bin directory is correctly added to the system PATH; otherwise, WeasyPrint will not be able to find the necessary libraries.
Installation on macOS and Linux
On macOS you can easily install the dependencies with Homebrew:
$ brew install python pango libffiOn Debian/Ubuntu-based Linux distributions:
$ sudo apt-get install python3-pip libpango-1.0-0 libpangoft2-1.0-0You can check the detailed instructions for your system in the official WeasyPrint documentation.
Installing WeasyPrint with pip
Once the system dependencies are installed, you can install the package as just another dependency of your Python project:
$ pip install weasyprintIf you use a dependencies file, remember to add it to your requirements.txt:
$ pip freeze > requirements.txtCreating a view to generate a PDF in Django
With the dependencies ready, let's create a view that converts an HTML template into a downloadable PDF. The core logic revolves around two WeasyPrint elements: the HTML() class, which accepts a URL or an HTML string, and the write_pdf() method, which writes the result to a file or an HTTP response object.
Basic example: converting a URL to PDF
The most direct use is to specify the URL you want to convert to PDF. In this example, useful as a functionality test, we convert the Google website:
pdfs/views.py
from weasyprint import HTML
def generate_pdf(request):
filename = "documents/archivo.pdf"
HTML("https://www.google.com/").write_pdf(filename)
return render(request, 'csv.html')This will generate an archivo.pdf file inside the project's documents/ directory with the content of the specified URL rendered as a PDF.
Generating PDF with a Django HTML template (render_to_string)
The most common workflow in Django is to render a template with dynamic data using render_to_string() and then pass the resulting HTML to WeasyPrint. This way, the PDF contains the exact same content that your normal view would display, but exported as a downloadable document.
Suppose you have a report.html template with dynamic data. You can adapt your view like this:
from django.template.loader import render_to_string
from django.http import HttpResponse
from weasyprint import HTML
def generar_pdf(request):
# Renderizamos la plantilla con el contexto necesario
html_string = render_to_string('report.html', {'usuario': 'María', 'total': 125})
html = HTML(string=html_string)
# Devolvemos el PDF directamente en la respuesta HTTP
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'inline; filename="reporte.pdf"'
html.write_pdf(target=response)
return responseThe inline value in Content-Disposition makes the browser display the PDF directly in the tab. If you prefer to force the download, change it to attachment.
Customizing page size and CSS styles
WeasyPrint allows passing additional stylesheets via the stylesheets parameter of the write_pdf() method. This is especially useful for defining page size and margins without modifying the base HTML template:
from weasyprint import HTML, CSS
HTML(string=html_string).write_pdf(
filename,
stylesheets=[CSS(string='@page { size: A3; margin: 1cm }')]
)You can also override global fonts without touching the template:
HTML('http://weasyprint.org/').write_pdf(
'/tmp/weasyprint-website.pdf',
stylesheets=[CSS(string='body { font-family: serif !important }')]
)In my case, I usually keep a CSS file dedicated only for printing (print.css), with adjusted fonts, margins, and page breaks. I pass it directly to stylesheets using CSS(filename='ruta/print.css') instead of CSS(string='...') when the stylesheet file is extensive. You can explore all available options in the official WeasyPrint documentation.
Saving the generated PDF file on the server
If instead of returning the PDF to the browser you prefer to save it on the server to serve it later or attach it to an email, simply pass the destination path to write_pdf():
html.write_pdf(target='media/reportes/reporte.pdf')Make sure the directory exists before writing. You can combine it with os.makedirs() or Django's storage system (default_storage) for more robust handling in production.
Recommendations for complex templates
If your design is visually complex (for example, an invoice with logos, tables, and custom styles), keep these considerations in mind before deploying:
Use absolute paths for images and stylesheets. WeasyPrint cannot resolve relative paths without a base URL context. Pass the
base_urlparameter:HTML(string=html_string, base_url=request.build_absolute_uri())- Avoid JavaScript dependencies in templates you plan to convert to PDF. WeasyPrint does not execute JS, so frameworks like React, Vue, or Alpine.js will not work. The PDF is generated from the static HTML resulting from Django's rendering.
- Test locally before deploying, especially to validate that external fonts and styles load correctly.
- If you use custom fonts (for example, from Google Fonts), download them and serve them locally. External requests from WeasyPrint can fail in production environments without outbound internet access.
Alternatives to WeasyPrint for generating PDFs in Django
Although WeasyPrint is my favorite option, it is useful to know the complete ecosystem to choose the right tool according to the project.
| Library | Advantages | Limitations |
|---|---|---|
| WeasyPrint | Precise rendering, CSS3 support, cross-platform, converts HTML directly | Requires external system dependencies (GTK, Pango, Cairo) |
| xhtml2pdf | Lightweight, easy to install, no external dependencies | Very limited CSS support (CSS2 only), results less faithful to design |
| ReportLab | Very powerful for custom PDFs with charts, tables, and precise coordinates | Does not convert HTML directly; requires building the PDF programmatically |
In summary: use xhtml2pdf if you only need something quick and without system dependencies for a simple document; opt for ReportLab if you need total layout control and pixel-perfect results (for example, tax documents or documents with exact coordinates); and use WeasyPrint for the majority of Django projects where you want to leverage your HTML templates with real CSS3 support.
Common errors when using WeasyPrint in Django and how to fix them
OSError: no library called "cairo"
This is the most frequent error on Windows. It appears when GTK is not correctly installed or its bin directory is not in the system PATH. Reinstall the GTK for Windows Runtime Environment Installer and restart the system.
Images or styles not appearing in the PDF
WeasyPrint needs absolute paths to access images and stylesheets. If the PDF generates blank or without styles, make sure to pass the base_url parameter:
HTML(string=html_string, base_url=request.build_absolute_uri())CSS styles not applying correctly
WeasyPrint only interprets valid CSS according to the standard. If a style is not reflected in the PDF, validate your stylesheets with the W3C CSS Validator and check that you are not using unsupported properties (such as proprietary browser properties or very recent CSS features not yet implemented in WeasyPrint).
Conclusion
Generating a PDF from an HTML template in Django is much simpler than it seems when you choose the right tools. In my experience, WeasyPrint is the most balanced option: it allows reusing your existing Django templates, natively supports CSS3, and produces stable and well-formatted PDFs on any platform with minimal configuration.
If you need absolute precision in layout (for example, tax documents or documents with exact coordinates), it is worth exploring ReportLab. But for the vast majority of projects —invoices, reports, certificates— WeasyPrint is more than enough and will save you a lot of development time.
Frequently asked questions about PDF generation in Django
- What is the best library to generate PDF in Django?
- It depends on the use case. WeasyPrint offers the best balance between power, CSS support, and simplicity of integration with Django's template system. It is the recommended choice for most projects.
- How to apply CSS styles to the PDF generated with WeasyPrint?
- By adding a stylesheet using the
stylesheetsparameter of thewrite_pdf()method. You can useCSS(string='...')for inline styles orCSS(filename='...')for an external CSS file.
- By adding a stylesheet using the
- Can you generate a PDF from an HTML template in Django?
- Yes, it is the most practical and recommended way: you render the template with
render_to_string()to get the HTML as a string and then convert it with WeasyPrint in a few lines.
- Yes, it is the most practical and recommended way: you render the template with
- What are the differences between WeasyPrint and xhtml2pdf?
xhtml2pdfis simpler and does not require external system dependencies, but its CSS support is very limited (CSS2). WeasyPrint supports full CSS3 and produces results much more faithful to the original design.
- Does WeasyPrint work in production with Django?
- Yes, but make sure to install system dependencies on your server (or Docker image) and to use absolute paths for static resources using the
base_urlparameter when instantiating theHTML()class.
- Yes, but make sure to install system dependencies on your server (or Docker image) and to use absolute paths for static resources using the
Next step: how to integrate CKEditor 5 in Django