Generate PDFs in Django

This guide shows how to generate PDFs in Django using PDFCrowd's HTML to PDF API. The examples cover converting HTML, returning PDF downloads, and saving or emailing the result.

Before you start

Install the Python client and set up API credentials in your Django settings. For testing, set both PDFCROWD_USERNAME and PDFCROWD_API_KEY to "demo".

Generate a PDF

From rendered HTML

In your export view, render the existing template and pass its HTML to convertString():

import pdfcrowd
from django.conf import settings
from django.http import HttpResponse
from django.template.loader import render_to_string

html = render_to_string("invoices/detail.html", context, request=request)

client = pdfcrowd.HtmlToPdfClient(
    settings.PDFCROWD_USERNAME,
    settings.PDFCROWD_API_KEY,
)
pdf = client.convertString(html)

response = HttpResponse(pdf, content_type="application/pdf")
response["Content-Disposition"] = 'attachment; filename="invoice.pdf"'

Configure page size, margins, and other PDF options on the client before calling a conversion method.

Return response from your view. To open the PDF in the browser, change attachment to inline.

For PDFs that should not be stored in browser or shared caches, set response["Cache-Control"] = "no-store" before returning the response. max-age=0 still allows storage; see Cache-Control.

Conversion failures raise pdfcrowd.Error. Handle them through your application's error handling; see SDK error handling for the available error details.

From an existing page URL

For an existing public HTML page, create a client as above and use convertUrl():

url = request.build_absolute_uri("/reports/monthly-sales/")
pdf = client.convertUrl(url)

Here /reports/monthly-sales/ serves an existing monthly sales report as HTML. Use pdf to build the same response shown above.

PDFCrowd must be able to reach that URL. Its request does not inherit the user's browser session. For authenticated or personalized reports, check access in your Django view and convert the rendered HTML. PDFCrowd receives the content you pass to the conversion call.

Add a PDF download button

Post to the URL of your export view from an existing page. Here invoice_pdf is that URL's name:

<form method="post" action="{% url 'invoice_pdf' %}">
    {% csrf_token %}
    <button type="submit">Download PDF</button>
</form>

Restrict the export view to POST with Django's @require_POST decorator from django.views.decorators.http. The view generates and returns the PDF as shown above.

If generation runs on GET, crawlers, link-preview bots, and browser prefetching can trigger conversion API calls without a button click. Requiring POST prevents these GET requests from starting a conversion.

Generate a PDF for email or storage

convertString() returns bytes that you can use without creating an HTTP response. With HTML from your existing rendering code and a new client:

pdf = client.convertString(html)

Attach the bytes to an existing Django EmailMessage:

message.attach("invoice.pdf", pdf, "application/pdf")

Or save them through your configured Django storage:

from django.core.files.base import ContentFile
from django.core.files.storage import default_storage

name = default_storage.save("reports/invoice.pdf", ContentFile(pdf))

The same conversion can run in a background job. Render with the job's data; your application controls scheduling and delivery.

CSS and images

When converting an HTML string, use absolute URLs or a base URL for external CSS and images. Django's {% static %} tag may produce a path such as /static/css/report.css.

To set a base URL only for PDF rendering, pass it with a copy of the existing context. build_absolute_uri() supplies the request's scheme and host:

pdf_context = {
    **context,
    "pdf_base_url": request.build_absolute_uri("/"),
}
html = render_to_string(
    "invoices/detail.html", pdf_context, request=request
)

In your existing template's <head>, before stylesheet links:

{% if pdf_base_url %}
<base href="{{ pdf_base_url }}">
{% endif %}

When pdf_base_url is absent, the template omits the element. A <base> affects all relative URLs, including links. If the template already has a base element, update that element instead of adding another.

As an application-wide alternative, Django's standard static storage can use an absolute STATIC_URL, such as STATIC_URL = "https://www.example.com/static/". The {% static %} tag then generates absolute URLs throughout the application.

PDFCrowd must be able to fetch the referenced files. A base URL does not grant access to private assets or forward the user's browser session. URL conversion also requires access to CSS and images.

For background jobs, supply your configured public origin as pdf_base_url and render with the job's context. A request object is not required.

WebSave alternative

For a download button without a Django PDF view, see WebSave as PDF.

Related documentation

HTML to PDF API