ScreenshotNeo

BlogHTML to image & PDF

How to Export HTML, JavaScript, and CSS to PDF with Django wkhtmltopdf

Render Django templates with CSS and JavaScript into reliable PDFs using wkhtmltopdf, with asset fixes, wait controls, options, and troubleshooting.

By the ScreenshotNeo team1 October 20265 min read

Direct answer: install django-wkhtmltopdf and a platform-matching wkhtmltopdf binary, register the app, make static assets reachable, then expose a PDFTemplateView. wkhtmltopdf renders HTML with Qt WebKit. JavaScript is enabled by default, so use a delay or readiness signal for charts and asynchronous components.

1. Install Django wkhtmltopdf and wkhtmltopdf

python -m pip install django-wkhtmltopdf
sudo apt-get update
sudo apt-get install wkhtmltopdf
wkhtmltopdf --version

The Python package is the Django integration layer; the executable performs rendering. If the binary is not on PATH, configure its absolute path with WKHTMLTOPDF_CMD. Use the same binary version in development, CI, and production.

2. Configure Django

# settings.py
INSTALLED_APPS = ['wkhtmltopdf']
WKHTMLTOPDF_CMD = '/usr/local/bin/wkhtmltopdf'
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
WKHTMLTOPDF_CMD_OPTIONS = {'page-size': 'A4', 'margin-top': '15mm', 'margin-right': '15mm', 'margin-bottom': '15mm', 'margin-left': '15mm', 'encoding': 'UTF-8', 'enable-local-file-access': True}

WKHTMLTOPDF_CMD_OPTIONS is a dictionary. Boolean values become switches; values such as title receive an argument. Use only options supported by the installed binary.

3. Create a PDF-ready template

{% load static %}<!doctype html><html lang='en'><head><meta http-equiv='Content-Type' content='text/html; charset=utf-8'><link rel='stylesheet' href='{% static "reports/report.css" %}'></head><body><main class='report'><h1>{{ report.title }}</h1><div id='chart'></div></main><script src='{% static "reports/report.js" %}'></script></body></html>

Use valid HTML and an explicit UTF-8 meta tag for non-ASCII text. Run python manage.py collectstatic. The converter must be able to fetch CSS, JavaScript, images, and fonts. Absolute URLs or a reachable host are usually easiest for worker processes. Local files may require a narrowly scoped --allow directory.

4. Return the PDF from a Django view

# reports/views.py
from wkhtmltopdf.views import PDFTemplateView
class ReportPDFView(PDFTemplateView):
    template_name = 'reports/report.html'
    filename = 'report.pdf'
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['report'] = {'title': 'Quarterly report'}
        return context
# reports/urls.py
from django.urls import path
from .views import ReportPDFView
urlpatterns = [path('reports/quarterly.pdf', ReportPDFView.as_view(), name='quarterly-pdf')]

PDFTemplateView returns a PDFTemplateResponse. Set filename = None for inline browser display.

Per-view options

class LandscapeReportView(PDFTemplateView):
    template_name = 'reports/report.html'
    filename = 'landscape-report.pdf'
    cmd_options = {'page-size': 'A4', 'orientation': 'Landscape', 'margin-top': '10mm', 'margin-bottom': '10mm', 'javascript-delay': 1200, 'encoding': 'UTF-8'}

5. Make JavaScript deterministic

JavaScript runs unless disabled. The documented --javascript-delay default is 200 ms. Use a readiness signal when data loading time varies.

  • javascript-delay <msec> waits after page load.
  • window-status <text> waits for a page status value.
  • run-script <script> executes additional JavaScript.
  • disable-javascript turns execution off.
fetch('/reports/data.json').then(r => r.json()).then(data => { renderChart(data); window.status = 'report-ready'; });

Configure window-status: report-ready and confirm that the converter can reach the API. If a third-party script never completes, use a bounded delay or render the data server-side.

6. CSS, layout, and page breaks

Need Option Guidance
Paper and direction page-size, orientation Use A4 or Letter; choose Landscape for wide tables.
Spacing margin-top/right/bottom/left Reserve room for headers and footers.
Viewport layouts viewport-size Pin dimensions when media queries or overflow vary.
Fixed measurements disable-smart-shrinking Smart shrinking is enabled by default; disable it when exact dimensions matter.
Fonts and Unicode encoding Use UTF-8 metadata and install required fonts.
Print styling user-style-sheet, print-media-type Apply a dedicated print stylesheet.

Use page-break CSS where supported, and test long tables because older WebKit engines have incomplete modern CSS support. Backgrounds and images are enabled by default.

  1. Set STATIC_ROOT and run collectstatic.
  2. Inspect rendered HTML with the package’s ?as=html mode.
  3. Use reachable absolute asset URLs when conversion runs in a worker or container.
  4. Grant only required directories with allow when reading local files.
  5. Check DNS, certificates, authentication, and network allow-lists from the converter runtime.

External links and image loading are enabled by default, but missing dependencies can still create incomplete output. Configure load-error and media-error handling deliberately.

8. Troubleshooting

Symptom Cause Fix
Blank or unstyled PDF Unreachable CSS or static files Use ?as=html, verify STATIC_ROOT, run collectstatic, and test URLs from the converter host.
Charts missing JavaScript still running Increase javascript-delay, use window-status, or add run-script.
Images or fonts blocked Local-file policy or bad URL Serve assets through reachable URLs or use a limited allow path.
Unexpected wrapping Margins, viewport, DPI, or shrinking Set page size and margins explicitly, pin viewport-size, and review smart shrinking.
Broken accented text Missing charset or font Add the UTF-8 meta tag, set encoding, and install the font.
Conversion exits with an error Missing binary or failed media Check wkhtmltopdf --version, WKHTMLTOPDF_CMD, stderr, and error-handling options.
Works locally but not in a worker Different PATH, filesystem, DNS, or sandbox Use an absolute binary path and test from the worker image.

9. Performance, reliability, and cost

  • Rendering time: delays, remote assets, large images, and third-party scripts increase duration.
  • Concurrency: each conversion is a separate process; queue large jobs and cap workers for available CPU and memory.
  • Reliability: pin the binary and fonts, use deterministic readiness, record stderr and exit codes, and retain sample HTML for reproduction.
  • Security: treat supplied URLs and HTML as untrusted; restrict local-file access and internal network reachability.
  • Cost: self-hosting avoids per-page API fees but requires worker CPU, memory, storage, and maintenance.

10. Or skip the browser setup

ScreenshotNeo accepts one GET request and returns a clean PNG, JPEG, WebP, or PDF. See the API documentation for PDF and capture options.

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)
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}`);

ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, with X-Page-Verdict and X-Billed headers identifying the result. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots monthly with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account.

11. FAQ

Does wkhtmltopdf execute JavaScript?

Yes. It is enabled by default. Use a delay, window-status, or run-script for asynchronous pages.

Why are Django static files missing?

The converter cannot resolve the URLs or collected directory. Set and populate STATIC_ROOT, then verify URLs from the converter runtime.

Can I display the PDF in the browser?

Yes. Set the view’s filename to None.

When should I use another renderer?

Compare JavaScript and CSS coverage, asset security, readiness controls, font handling, deployment footprint, page breaks, and maintenance requirements against your documents.