Add a PDF download to an existing Django view, or generate a PDF from a template. The examples use PDFCrowd's HTML to PDF API and work for invoices, reports, receipts, and other documents built from application data.
Set up the client
Install the Python client:
pip install pdfcrowd
You can run the examples below with the included demo credentials, or replace
them with your PDFCrowd username and API key.
Add a PDF download to an existing view
An existing view can display its usual HTML page and return a PDF when the user clicks a download button. Reuse the view's template, data preparation, and access checks.
Choose what to send to PDFCrowd:
- Rendered HTML uses content Django has already prepared, such as an invoice for the logged-in user.
- The page URL loads the page through its normal address.
To include changes made in the browser, such as filled-in form fields, consider WebSave as PDF in content mode.
Convert rendered HTML
This example extends an invoice view.
get_invoice_for_user(user, pk) represents your application's existing invoice lookup and
permission checks; substitute your own logic here.
import pdfcrowd from django.conf import settings from django.http import HttpResponse from django.shortcuts import render from django.template.loader import render_to_string from django.views.decorators.http import require_http_methods @require_http_methods(["GET", "POST"]) def invoice_detail_view(request, pk): invoice = get_invoice_for_user(request.user, pk) context = {"invoice": invoice} if request.method == "POST" and "download_pdf" in request.POST: context["pdf_base_url"] = getattr(settings, "PDF_BASE_URL", None) html = render_to_string("invoices/detail.html", context, request=request) try: client = pdfcrowd.HtmlToPdfClient("demo", "demo") # Username, API key client.setContentViewportWidth('balanced') pdf = client.convertString(html) except pdfcrowd.Error: # Add your application's error handling here. raise response = HttpResponse(pdf, content_type="application/pdf") response["Content-Disposition"] = ( f'attachment; filename="invoice-{invoice.pk}.pdf"' ) return response return render(request, "invoices/detail.html", context)
Add this form to the template, outside any existing form. Clicking its download button sends
a POST request to the current view. The pdfcrowd-remove class keeps the button visible on
the webpage but excludes it from the PDF.
<form method="post"> {% csrf_token %} <button type="submit" name="download_pdf" value="1" class="pdfcrowd-remove">Download PDF</button> </form>
GET requests continue to display the HTML page. A POST containing download_pdf renders the
same template to an HTML string, converts it, and returns a PDF download. The pdf_base_url
value supports the CSS and image setup below.
To display the PDF in the browser, change attachment to inline. Customize
page size,
margins, or other
PDF options on the client before convertString().
The except block provides a place for your application's error handling. As written, raise
propagates the original exception. See
SDK error handling for details.
Make CSS and images available
When you send rendered HTML to PDFCrowd, relative paths to CSS and images need a base URL so PDFCrowd can locate the files and include them in the PDF.
Check STATIC_URL and the asset URLs in the rendered HTML. If all asset URLs are complete,
such as https://cdn.your-site.example/static/style.css, or the template already provides a
correctly configured <base> element, no additional setup is needed.
For relative paths, define the site or directory URL in your Django settings:
PDF_BASE_URL = "https://your-site.example/"
Replace https://your-site.example/ with your website's public URL, where PDFCrowd can
access the assets. Then add this to the template's <head>, before stylesheet links:
{% if pdf_base_url %} <base href="{{ pdf_base_url }}"> {% endif %}
The view passes this setting to the template as pdf_base_url when generating a PDF.
The <base> element tells PDFCrowd where to resolve relative URLs. For example,
/static/style.css becomes https://your-site.example/static/style.css. This applies to
assets and hyperlinks.
You can also bundle the rendered HTML with local CSS, images, or fonts in a ZIP archive
and pass its path to convertFile().
Keep paths consistent with the archive's folders and omit the website <base> so those paths
resolve within the archive.
Convert the page URL instead
With URL conversion, PDFCrowd opens the page at the supplied address.
Keep the POST check, client initialization, error handling, and PDF response from the view
above. Replace its HTML rendering and convertString() call with
convertUrl():
pdf = client.convertUrl(request.build_absolute_uri())
This uses the current URL, including query parameters such as report filters. PDFCrowd makes a separate GET request, so the view runs its normal access checks and returns HTML. Only the original POST triggers PDF generation.
The server must be able to handle this GET while the original POST waits for conversion.
Django's development server supports concurrent requests by default; running it with
--nothreading can cause the conversion to time out.
PDFCrowd must be able to reach the URL. A localhost or 127.0.0.1 URL cannot be used to
reach your development machine from PDFCrowd's servers.
PDFCrowd's request does not inherit the user's Django session. For protected pages, configure cookies or HTTP authentication as appropriate, or use the rendered-HTML approach above.
Generate a PDF from a template
For scheduled reports or invoice emails, you can generate a PDF directly from application data without a browser request. Use an existing Django template or a separate template designed for the document.
The function below renders a template with the supplied data and returns the PDF as bytes. It uses the same asset configuration as the view example:
import pdfcrowd from django.conf import settings from django.template.loader import render_to_string def render_pdf(template_name, context): pdf_context = { **context, "pdf_base_url": getattr(settings, "PDF_BASE_URL", None), } html = render_to_string(template_name, pdf_context) client = pdfcrowd.HtmlToPdfClient("demo", "demo") # Username, API key client.setContentViewportWidth('balanced') return client.convertString(html)
Pass all data the template needs in context; this function runs without request context
processors. Conversion errors propagate to the calling job, which can use the same
try/except pattern as the view.
Save or email the PDF
For example, given an invoice object, render it with invoices/detail.html and save the
result through Django's storage API:
from django.core.files.base import ContentFile from django.core.files.storage import default_storage pdf = render_pdf("invoices/detail.html", {"invoice": invoice}) name = default_storage.save( f"invoices/{invoice.pk}.pdf", ContentFile(pdf) )
Django storage returns the actual saved name, which can differ if the requested name already
exists. For email delivery, attach the same bytes to an existing EmailMessage:
message.attach("invoice.pdf", pdf, "application/pdf")
Generate a batch of invoices
In a background job or management command, loop over the invoices selected by your
application:
from django.core.files.base import ContentFile from django.core.files.storage import default_storage for invoice in invoices: pdf = render_pdf("invoices/detail.html", {"invoice": invoice}) name = default_storage.save( f"invoices/{invoice.pk}.pdf", ContentFile(pdf) )
The same function can generate a single PDF when an invoice is issued.
WebSave alternative
For a download button without Django conversion code, see WebSave as PDF.