Converting HTML to PDF is a common requirement in Django applications - generating invoices, reports, certificates, or any printable document. This tutorial shows how to integrate PDFCrowd's HTML to PDF API into your Django views.
If you prefer a client-only solution without server-side code, see WebSave as PDF.
Installation
Install the PDFCrowd Python client:
pip install pdfcrowd
Converting a URL to PDF
The simplest approach is to create a Django view that serves content at a URL, then convert that URL to PDF. This ensures all static assets (CSS, images) are accessible to the conversion API:
import urllib.parse
from django.http import HttpResponse
from django.views.decorators.http import require_POST
import pdfcrowd
@require_POST
def generate_invoice_pdf(request, invoice_id):
try:
client = pdfcrowd.HtmlToPdfClient('demo', 'demo')
client.setPageSize('A4')
client.setPageMargins('10mm', '10mm', '10mm', '10mm')
# Build the URL to convert
invoice_url = request.build_absolute_uri(f'/invoices/{invoice_id}/html/')
# Prepare the HTTP response
response = HttpResponse(content_type='application/pdf')
response['Cache-Control'] = 'max-age=0'
response['Accept-Ranges'] = 'none'
response['Content-Disposition'] = (
"attachment; filename*=UTF-8''" +
urllib.parse.quote(f'invoice_{invoice_id}.pdf', safe='')
)
client.convertUrlToStream(invoice_url, response)
return response
except pdfcrowd.Error as why:
return HttpResponse(
str(why),
status=why.getStatusCode(),
content_type='text/html'
)
Create a corresponding HTML view that renders the invoice:
from django.shortcuts import render, get_object_or_404
def invoice_html(request, invoice_id):
invoice = get_object_or_404(Invoice.objects.select_related('customer'), pk=invoice_id)
return render(request, 'invoices/invoice.html', {
'invoice': invoice,
'line_items': invoice.items.all(),
})
# urls.py
urlpatterns = [
path('invoices/<int:invoice_id>/html/', invoice_html, name='invoice_html'),
path('invoices/<int:invoice_id>/pdf/', generate_invoice_pdf, name='invoice_pdf'),
]
Other Conversion Methods
Besides URL conversion, the API supports converting HTML strings and local files.
Use convertStringToStream for rendering Django templates - ideal for documents like invoices or contracts that don't need a public URL:
from django.template.loader import render_to_string
html_content = render_to_string('invoices/invoice_pdf.html', {
'invoice': invoice,
'line_items': invoice.items.all(),
})
client.convertStringToStream(html_content, response)
Use convertFileToStream for processing uploaded HTML files:
if hasattr(uploaded_file, 'temporary_file_path'):
client.convertFileToStream(uploaded_file.temporary_file_path(), response)
else:
# Small files are kept in memory, use convertString instead
client.convertStringToStream(uploaded_file.read().decode('utf-8'), response)
Note that when using convertString, any relative URLs for images or CSS won't resolve. Use convertUrl for pages with static assets, or add a <base> tag to your template (requires request in the template context):
<head>
<base href="{{ request.scheme }}://{{ request.get_host }}/">
...
</head>
Adding a "Save as PDF" Button
Create a reusable include template for the PDF button:
<!-- templates/includes/pdf_button.html -->
<form method="post" action="{% url 'save_as_pdf' %}">
{% csrf_token %}
<input type="hidden" name="url" value="{{ request.build_absolute_uri }}">
<input type="hidden" name="filename" value="{{ pdf_filename|default:'page.pdf' }}">
<button type="submit">Save as PDF</button>
</form>
Then include it on any page that needs PDF export:
<!-- templates/invoices/invoice_detail.html -->
{% extends "base.html" %}
{% block content %}
<h1>Invoice {{ invoice.number }}</h1>
...
{% include "includes/pdf_button.html" with pdf_filename=invoice.number|add:".pdf" %}
{% endblock %}
Create a generic view that converts any URL:
from django.conf import settings
@require_POST
def save_as_pdf(request):
try:
client = pdfcrowd.HtmlToPdfClient('demo', 'demo')
client.setPageSize('A4')
# Pass session cookie for authenticated pages
session_cookie = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
if session_cookie:
client.setCookies(f'{settings.SESSION_COOKIE_NAME}={session_cookie}')
url = request.POST.get('url')
filename = request.POST.get('filename', 'page.pdf')
# Validate URL is from your domain
if not url.startswith(request.build_absolute_uri('/')[:-1]):
return HttpResponse('Invalid URL', status=400)
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = (
"attachment; filename*=UTF-8''" +
urllib.parse.quote(filename, safe='')
)
client.convertUrlToStream(url, response)
return response
except pdfcrowd.Error as why:
return HttpResponse(
str(why),
status=why.getStatusCode(),
content_type='text/html'
)
# urls.py
urlpatterns = [
path('save-as-pdf/', save_as_pdf, name='save_as_pdf'),
]
Now any page can include the PDF button where needed, with a custom filename passed via with.
The example above includes session cookie forwarding via setCookies(), which allows converting password-protected pages. You can pass multiple cookies if needed:
client.setCookies('sessionid=abc123; csrftoken=xyz789')
Configuration Best Practices
Store your API credentials in Django settings:
# settings.py
import os
PDFCROWD_USERNAME = os.environ.get('PDFCROWD_USERNAME')
PDFCROWD_API_KEY = os.environ.get('PDFCROWD_API_KEY')
Then use them in your views:
client = pdfcrowd.HtmlToPdfClient(
settings.PDFCROWD_USERNAME,
settings.PDFCROWD_API_KEY
)
Learn More
- HTML to PDF Python API - getting started guide
- Python API Reference - all available options and methods