Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
Django

How to Generate PDFs With wkhtmltopdf in Django (Secure, Deployable Guide)

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use Django to render a controlled HTML template, then invoke the separate wkhtmltopdf executable to convert that HTML into a PDF. The reliable integration has four parts: install and verify a compatible wkhtmltopdf build, render print-oriented HTML, run the converter with bounded resources, and return or store the resulting bytes without deleting them too early.

This guide shows a complete Django pattern, explains static assets and deployment traps, and helps you decide when wkhtmltopdf is the wrong renderer.

How the Django-to-PDF pipeline works

wkhtmltopdf is not a Django library. Django creates HTML; the command-line program reads one or more page objects and writes a PDF file. Global options apply to the whole document, while page options apply to individual input pages. The command also supports cover and table-of-contents objects.

  1. Load and authorize the record being printed.
  2. Render a dedicated Django template containing print CSS and accessible assets.
  3. Write the HTML to a temporary file (or provide an input form supported by your wrapper).
  4. Run wkhtmltopdf with an output path, timeout and checked exit status.
  5. Return the PDF as an attachment or save it in durable storage.

Install and verify the executable

The official downloads page identifies 0.12.6 as the current stable series, released June 11, 2020, and lists binaries by operating system and architecture. That does not guarantee support for every newer distribution. Install the package appropriate for the same OS and architecture used in production, then verify the actual binary:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
wkhtmltopdf --version
which wkhtmltopdf

Some features require the project’s patched Qt build; distribution packages can omit them. Compare the command output and a representative PDF in your deployment image rather than assuming that a package named wkhtmltopdf has identical capabilities.

The project also records that Qt 4 has been unsupported since 2015 and its WebKit had not been updated since 2012. Those statements describe the project’s foundation, not every downstream package. They are nevertheless important maintenance and compatibility warnings.

Create a print-specific Django template

Do not print your interactive page blindly. Create a template such as templates/invoices/invoice.html with deterministic content, print CSS and URLs that the converter can actually reach.

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Invoice {{ invoice.number }}</title>
  <style>
    @page { size: A4; margin: 18mm 14mm; }
    body { font: 12px/1.45 sans-serif; color: #222; }
    .page-break { page-break-after: always; }
    thead { display: table-header-group; }
    tr { page-break-inside: avoid; }
  </style>
</head>
<body>
  <h1>Invoice {{ invoice.number }}</h1>
  <p>{{ invoice.customer_name }}</p>
  <table>...</table>
</body>
</html>

Use validated model values and Django’s escaping. If you use images, stylesheets or fonts, give the renderer reachable absolute URLs or local paths and test those paths in the production environment. A template that looks correct in a browser can still produce missing assets when the converter runs in a container without DNS, credentials or filesystem access.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A safe illustrative Django view

The following is a teaching outline. It deliberately makes the temporary-directory lifetime visible: returning a path from inside the context manager would delete the PDF before Django could read it. For production, either read the bytes before cleanup, stream from a file whose lifetime extends through the response, or place the file in durable storage.

from django.http import HttpResponse
from django.template.loader import render_to_string
from django.shortcuts import get_object_or_404
from django.conf import settings
import os
import subprocess
import tempfile


def invoice_pdf(request, invoice_id):
    invoice = get_object_or_404(Invoice, pk=invoice_id)
    # Enforce the same authorization rules as the HTML invoice view.
    html = render_to_string("invoices/invoice.html", {"invoice": invoice})
    executable = getattr(settings, "WKHTMLTOPDF_PATH", "wkhtmltopdf")

    with tempfile.TemporaryDirectory() as directory:
        html_path = os.path.join(directory, "invoice.html")
        pdf_path = os.path.join(directory, "invoice.pdf")
        with open(html_path, "w", encoding="utf-8") as html_file:
            html_file.write(html)
        try:
            completed = subprocess.run(
                [executable, "--quiet", html_path, pdf_path],
                check=True,
                capture_output=True,
                text=True,
                timeout=30,
            )
        except (OSError, subprocess.CalledProcessError, subprocess.TimeoutExpired):
            # Log stderr and return a controlled 5xx response in real code.
            raise
        with open(pdf_path, "rb") as pdf_file:
            data = pdf_file.read()

    response = HttpResponse(data, content_type="application/pdf")
    response["Content-Disposition"] = (
        f'attachment; filename="invoice-{invoice.number}.pdf"'
    )
    return response

Configure an absolute executable path when service managers have a different PATH from your shell. Log stderr, elapsed time and the exit code, but avoid logging invoice contents or secrets.

Returning bytes with HttpResponse or a file with FileResponse

Use HttpResponse for bytes

If the PDF is already in memory, HttpResponse(data, content_type="application/pdf") is straightforward. Set Content-Disposition to inline for browser viewing or attachment for a download and provide a safe filename.

Use FileResponse for a binary file

Django’s FileResponse is optimized for binary file-like objects and supports as_attachment=True and a filename. Django closes the supplied file automatically. Do not pass a file that has already been closed by an enclosing with block. For BytesIO, call seek(0) before constructing the response.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from django.http import FileResponse

pdf_file.seek(0)
return FileResponse(
    pdf_file,
    as_attachment=True,
    filename="invoice-123.pdf",
    content_type="application/pdf",
)

Options that matter in real documents

  • Page geometry: set paper size, orientation and margins explicitly so deployment defaults cannot change pagination.
  • Long pages: use print CSS, repeating table headers and deliberate page breaks; verify rows containing images or long text.
  • JavaScript: only rely on scripts after testing the actual build. A converter based on old WebKit is not equivalent to a current browser.
  • Remote resources: ensure DNS, TLS, authentication and local file permissions work from the worker process. Prefer bundled, versioned assets for invoices and reports.
  • Headers, footers and covers: pass the relevant command options in a list, never through a shell string. The command supports separate cover and table-of-contents objects when your document needs them.
  • Concurrency: each conversion is a child process. Limit simultaneous jobs, use a queue for expensive reports, and cap request timeouts.

Security requirements

The official downloads page warns: Do not use wkhtmltopdf with any untrusted HTML – be sure to sanitize any user-supplied HTML/JS, otherwise it can lead to complete takeover of the server it is running on! Treat this as a hard boundary.

  • Render controlled templates with validated data rather than accepting arbitrary markup.
  • Do not interpolate user text into command arguments. Pass an argument array to subprocess.run with shell=False (the default).
  • Run the converter as a low-privilege account, restrict outbound network access where possible, and isolate temporary directories.
  • Consider operating-system controls such as AppArmor or SELinux, which the project status page recommends.
  • Authorize the invoice or report before rendering it; a PDF endpoint must not become an object-ID enumeration leak.

When wkhtmltopdf is a poor fit

Choose based on the page’s JavaScript needs, HTML/CSS fidelity, security and maintenance posture, deployment packaging, and licensing cost.

Requirement Practical direction
Controlled reports with modest CSS and little JavaScript wkhtmltopdf can be workable after build and output validation.
Modern, JavaScript-heavy application pages The project status recommends a browser automation tool such as Puppeteer; evaluate isolation and operating cost separately.
Application-controlled reports where a maintained HTML renderer is preferred The project names WeasyPrint or commercial Prince as alternatives; compare fidelity, packaging and licensing before switching.
Untrusted HTML Do not feed it directly to wkhtmltopdf. Sanitize and isolate, or redesign the workflow.

These are project recommendations, not guarantees of drop-in compatibility. Rebuild representative documents and compare pagination, fonts, links, forms and charts before changing engines.

Or skip the browser setup

If your actual need is a clean image or PDF of a URL rather than server-side Django template rendering, ScreenshotNeo makes one HTTP request. It accepts consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server lets Claude, Cursor and other MCP clients call take_screenshot, get_page_info and capture_pdf.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

See the ScreenshotNeo API documentation for options such as full-page lazy-image loading, CSS-selector element capture, device presets, custom CSS/JavaScript, cookies and headers, waiting conditions, PDF paper settings, signed webhooks and bulk capture.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

There is a free allowance of 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account when a hosted capture endpoint fits your workflow.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting checklist

FileNotFoundError or “command not found”

The executable is absent or not on the service user’s PATH. Install a supported binary, set WKHTMLTOPDF_PATH to its absolute path, and verify it as the same user that runs Django.

Exit status is non-zero

Capture stderr and reproduce the exact argument list in the deployment container. Check unreadable input files, unwritable output directories, malformed HTML, missing patched-Qt features and inaccessible remote assets.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Blank pages or missing images

Inspect the generated HTML, test every asset URL from the worker environment, and replace browser-only relative paths with reachable absolute URLs or local files. Confirm that authentication headers and certificates are available to the converter.

Conversion hangs

Set a subprocess timeout, terminate the child process on expiry, and move large jobs to a queue. Look for JavaScript waiting forever, unreachable hosts and resource-heavy pages.

The response downloads a zero-byte or missing PDF

Ensure the process completed successfully, the output exists and has non-zero size, and the file or bytes remain alive until Django has consumed them. With BytesIO, seek to position zero.

Layout differs between laptop and production

Compare executable versions, patched-Qt status, fonts, locale, timezone, available resources and network access. Build and test the same container or image used in production.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Operational checklist before launch

  • Pin and record the tested wkhtmltopdf build and OS image.
  • Render representative short, long, multilingual and image-heavy documents.
  • Measure conversion time and memory under expected concurrency.
  • Set process, request and queue timeouts with cleanup on failure.
  • Protect endpoints with authorization and rate limits.
  • Keep templates controlled and treat every user-supplied HTML or script as hostile.
  • Monitor exit codes, stderr, PDF size and page-count anomalies without exposing sensitive data.

Frequently Asked Questions

Can I generate a PDF without writing an HTML file first?

wkhtmltopdf consumes HTML input and writes a PDF output; a wrapper may support other input forms, but the dependable Django pattern is to render and persist temporary HTML before invoking the executable.

Should I use a Python wkhtmltopdf wrapper?

A wrapper can simplify argument construction, but it does not remove the need to install and verify the executable, enforce security boundaries, handle timeouts and test the build used in deployment.

Is wkhtmltopdf a current browser engine?

No. The project documents its Qt 4 and old WebKit foundations, so modern JavaScript and CSS may require a different renderer.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.