Use django-wkhtmltopdf with the platform-appropriate wkhtmltopdf executable, then expose a Django PDFTemplateView. The integration renders your template with wkhtmltopdf’s Qt WebKit engine and returns a PDF response. The reliable setup is: install both layers, register the Django app, collect static files, make every asset reachable by the converter, configure the executable path when necessary, and choose an explicit JavaScript readiness strategy for dynamic content.
What you are installing
There are two separate components:
django-wkhtmltopdf: Django views and response integration. Its stated purpose is to let a Django site output dynamic PDFs.wkhtmltopdf: the command-line renderer. It converts HTML to PDF with the Qt WebKit engine and supplies switches for JavaScript, delays, scripts, CSS, viewport sizing, images, links, local-file permissions, and load-error handling.
The Python package does not replace the executable. Install a binary suitable for the operating system and CPU architecture where your Django process runs. The integration searches for wkhtmltopdf on PATH; set WKHTMLTOPDF_CMD when it is installed elsewhere.
Install and configure the integration
1. Install the Python package and binary
Install the package in the same virtual environment as Django:
python -m pip install django-wkhtmltopdf
Install the wkhtmltopdf binary using the package supplied for your target platform. Verify that the executable is available to the service account, not only to your interactive shell:
Recommended Free Tools
#1 Best Overall
wkhtmltopdf --version
If that command is not on PATH, configure its absolute path in Django settings.
2. Register the app and executable
# settings.py
INSTALLED_APPS = [
# ...
"wkhtmltopdf",
]
# Only needed when wkhtmltopdf is not on PATH.
WKHTMLTOPDF_CMD = "/absolute/path/to/wkhtmltopdf"
WKHTMLTOPDF_CMD_OPTIONS = {
"quiet": True,
"margin-top": "15mm",
"margin-right": "15mm",
"margin-bottom": "15mm",
"margin-left": "15mm",
}
Boolean values represent switches; options that require a value use a string (for example, title or a margin). Keep options conservative at first, then add the controls your layout requires.
Prepare a template that renders outside the browser
Create a normal Django template, but assume that the converter may run without your browser’s session, hostname resolution, or development server conveniences.
<!-- templates/reports/invoice.html -->
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>{{ invoice.number }}</title>
<link rel="stylesheet" href="{{ css_url }}">
</head>
<body>
<h1>Invoice {{ invoice.number }}</h1>
<p>Issued {{ invoice.issued_at }}</p>
<table>
{% for line in invoice.lines %}
<tr><td>{{ line.description }}</td><td>{{ line.total }}</td></tr>
{% endfor %}
</table>
<script src="{{ chart_js_url }}"></script>
</body>
</html>
The UTF-8 meta element prevents many non-ASCII failures. Fonts must also be installed or fetched successfully by the renderer.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #2
Make CSS, JavaScript, images, and fonts reachable
Set STATIC_ROOT and populate it with Django’s static-file collection command. The installation guidance specifically requires collected static files because the converter must read them, including on a local machine.
# settings.py
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
python manage.py collectstatic
Use absolute URLs, or another URL form that the converter can resolve, for stylesheets, scripts, images, and fonts. A browser showing the page correctly does not prove that the wkhtmltopdf process can reach those URLs. If you use local files, grant only the directories required by the renderer with its --allow option. External images and links are enabled by default, while local-file access is restricted unless explicitly permitted.
For pages built from widgets, identify the CSS and JavaScript assets the widget actually needs and include those assets in the generated HTML. Avoid relying on browser extensions, relative paths that resolve only under your development server, or authentication cookies that the converter will not possess.
Return a PDF from a Django URL
Use PDFTemplateView
# urls.py
from django.urls import path
from wkhtmltopdf.views import PDFTemplateView
urlpatterns = [
path(
"reports/invoice/<int:pk>/pdf/",
PDFTemplateView.as_view(
template_name="reports/invoice.html",
filename="invoice.pdf",
),
name="invoice-pdf",
),
]
The default response is a PDFTemplateResponse. The view renders the named template and sends the generated file as a download. Set filename=None when you want inline display rather than a forced download:
PDFTemplateView.as_view(
template_name="reports/invoice.html",
filename=None,
)
For object-specific data, subclass the view and provide context in the usual Django way:
# views.py
from django.shortcuts import get_object_or_404
from wkhtmltopdf.views import PDFTemplateView
from .models import Invoice
class InvoicePDFView(PDFTemplateView):
template_name = "reports/invoice.html"
filename = "invoice.pdf"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
invoice = get_object_or_404(Invoice, pk=self.kwargs["pk"])
context["invoice"] = invoice
context["css_url"] = self.request.build_absolute_uri("/static/reports/invoice.css")
context["chart_js_url"] = self.request.build_absolute_uri("/static/reports/chart.js")
return context
Protect the URL with your normal authentication and authorization checks. The renderer must still be able to request every asset and data endpoint used by the template.
Control JavaScript and asynchronous content
JavaScript is enabled by default. A page that starts a chart after an API request can therefore finish conversion before the chart exists unless you tell wkhtmltopdf when it is ready.
Choose one readiness mechanism
- Fixed delay:
--javascript-delay 1500waits the specified milliseconds after page load. The documented default is 200 ms. Increase it only as far as your slowest expected render requires. - Window status: have your page set a status value after data and charts are complete, then use
--window-status ready. This is more deterministic than guessing a delay. - Additional script: use
--run-scriptwhen a small post-load action must be executed by the renderer. - Disable execution: use
--disable-javascriptfor static documents where scripts are unnecessary.
For a chart, expose a clear completion signal rather than waiting indefinitely:
Free tools Windows power users keep installed
One-click scans. No signup required.
<script>
renderChart(data).then(function () {
window.status = "pdf-ready";
});
</script>
Whichever method you choose, ensure that the required JavaScript files and API endpoints are reachable from the rendering process. A longer delay cannot repair a blocked request.
Layout, pages, and visual fidelity
wkhtmltopdf can set paper size, orientation, margins, DPI, viewport dimensions, image and background behavior, and link loading. Smart shrinking is enabled by default: it changes the pixel-to-DPI relationship to fit content. Disable it when fixed measurements are more important than automatic fitting.
WKHTMLTOPDF_CMD_OPTIONS = {
"page-size": "A4",
"orientation": "Portrait",
"margin-top": "12mm",
"margin-right": "12mm",
"margin-bottom": "12mm",
"margin-left": "12mm",
"viewport-size": "1280x900",
"javascript-delay": 800,
"background": True,
# "disable-smart-shrinking": True, # enable for fixed-scale layouts
}
Use CSS print rules and deliberate page breaks for multipage documents. Test long tables, unusually large images, and narrow viewports; those cases expose wrapping and scaling problems that a short sample page hides.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Debug before changing production options
Inspect the HTML first
Render the view as HTML (the package documents an ?as=html inspection path), then open that output and verify that the exact CSS, images, fonts, and scripts have usable URLs. This separates Django template errors from converter errors.
Best Value
Common symptoms and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Blank or unstyled PDF | Collected files are missing or URLs cannot be resolved | Set and populate STATIC_ROOT; inspect the HTML; use absolute or otherwise reachable asset URLs. |
| Chart or dynamic widget missing | Conversion finished before asynchronous work completed | Increase javascript-delay, use window-status, or add run-script; confirm network calls complete. |
| Local image or font is blocked | Local-file access is restricted | Serve the asset through a reachable URL or grant only its directory with --allow. |
| Unexpected wrapping or scale | Viewport, margins, page size, or smart shrinking | Set page and viewport values explicitly, then decide whether to disable smart shrinking. |
| Accented characters or symbols are broken | Missing UTF-8 declaration or font | Add the UTF-8 meta tag and make a suitable font available to the renderer. |
| Intermittent conversion failures | Missing dependencies are being ignored | Configure load-error and media-error handling deliberately so failures are visible rather than silently hidden. |
Performance, reliability, and security considerations
- Readiness versus throughput: every JavaScript delay holds a renderer process longer. Prefer a completion signal for variable network work and keep the page’s requests finite.
- Asset locality: serving versioned static assets from a dependable URL is usually easier to reproduce than relying on ad-hoc local paths. If local access is unavoidable, allow only the required directories.
- Failure visibility: do not suppress load or media errors until you understand which dependencies are optional. A PDF that renders without a chart may be worse than a failed request that alerts you.
- Authentication: the converter does not automatically inherit a user’s browser session. Pass data through the server-side template or arrange an explicitly reachable, authorized endpoint.
- Output validation: check the HTTP status, content type, nonzero file size, and presence of expected text or pages before storing or emailing a PDF.
Or skip the browser setup
If you only need a clean capture or PDF from a URL, ScreenshotNeo provides a single HTTP request and an MCP server for AI agents. Before capture it accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.
For PDF output, use the API documented at ScreenshotNeo’s documentation. The same service supports full-page captures, custom CSS and JavaScript, waits for selectors, delays or network idle, device and viewport controls, cookies and headers, and PDF paper, margin, orientation, and page-range settings.
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 includes an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Frequently Asked Questions
Can I use wkhtmltopdf without Django?
Yes. wkhtmltopdf is independently usable from its command line; django-wkhtmltopdf adds Django views and response handling.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsShould I wait with a delay or window status?
Use a status signal when completion time varies with network or data. Use a short fixed delay only when rendering time is predictable.
Why does my browser page work while the PDF does not?
The converter has a separate process, viewport, filesystem policy, font environment, and network access. Verify each dependency from the converter’s point of view.
Quick Recap
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.




