How wkhtmltopdf Uses Qt Media Print Styles
Learn exactly what --print-media-type changes in wkhtmltopdf, how CSS falls through the cascade, and how to troubleshoot missing print styles.
Short answer: wkhtmltopdf --print-media-type input.html output.pdf asks wkhtmltopdf to render using the CSS print media type instead of the default screen media type. The manual documents --no-print-media-type as the default. The switch controls media selection; it does not repair missing stylesheets, broken asset URLs, unsupported CSS, or every layout problem.
This distinction matters because wkhtmltopdf uses an older Qt/WebKit rendering stack. Its project status page says Qt 4 has been unsupported since 2015 and that the WebKit version in Qt 4 had not been updated since 2012. Treat modern CSS behavior as something to verify with your exact binary and a reduced test case.
What --print-media-type actually does
CSS can target different media types. A rule inside @media print applies when the document is rendered as print media. A rule inside @media screen applies when it is rendered as screen media. Rules without a media condition remain part of the normal cascade and can still apply unless another rule overrides them.
With the command below, wkhtmltopdf selects print media:
wkhtmltopdf --print-media-type input.html output.pdf
Without that option, the documented default is equivalent to selecting screen media:
wkhtmltopdf --no-print-media-type input.html output.pdf
The C API exposes the same behavior as load.printMediaType: selecting print media instead of screen media. The API documentation also states that this setting has no effect for wkhtmltoimage; it affects PDF loading, not the image converter.
Minimal reproducible example
Create input.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: sans-serif; color: #222; }
.screen-only { display: block; }
.print-only { display: none; }
@media print {
body { color: #000; }
.screen-only { display: none; }
.print-only { display: block; }
}
</style>
</head>
<body>
<p class="screen-only">Screen version</p>
<p class="print-only">Print version</p>
</body>
</html>
Generate a PDF using print media:
wkhtmltopdf --print-media-type input.html print.pdf
Generate a comparison using the default screen media:
wkhtmltopdf --no-print-media-type input.html screen.pdf
The unqualified body rule is available in both modes. The conditional rules are selected according to the media type. If an unqualified rule and a conditional rule both match, normal CSS precedence still applies: selector specificity, source order, and declarations such as !important determine the winner.
Why unqualified styles can appear to disappear
A historical issue report described print rules appearing while styles without an explicit media condition seemed absent. That report is a user’s unresolved question, not authoritative evidence of a general wkhtmltopdf rule. When you see this symptom, isolate the cause instead of assuming that --print-media-type removes all screen-independent CSS.
Check the cascade first
- Look for a later
@media printrule that overrides an earlier declaration. - Compare selector specificity. For example,
.invoice .totalcan override a plain.total. - Search for
display: none,visibility: hidden, zero dimensions, and white text on a white background. - Check whether a print rule changes positioning, overflow, or page dimensions and makes content appear off-page.
Check stylesheet loading
- Use absolute or correctly resolved URLs for external stylesheets and fonts.
- Confirm that the conversion process can read local files and reach remote assets in its execution environment.
- Reduce the document to one inline stylesheet. If the inline version works, investigate the external resource path or access problem.
Check the binary
Different distributions and patched-Qt builds can behave differently. Record the exact wkhtmltopdf version, operating system, and packaging source before comparing results between machines.
Designing CSS that works for both media types
Put shared declarations outside media queries, then add only print-specific changes inside @media print. This makes the intended cascade explicit:
/* Shared rules */
.invoice { width: 100%; color: #222; }
.invoice h1 { font-size: 24px; }
/* Print adjustments */
@media print {
.navigation, .chat-widget, .cookie-banner { display: none; }
.invoice { color: #000; }
.invoice h1 { font-size: 20px; }
}
Avoid copying the entire stylesheet into @media print unless you have a specific reason. Duplicated declarations make conflicts harder to diagnose and do not change how the cascade works.
Step-by-step troubleshooting checklist
- Confirm the mode. Run once with
--print-media-typeand once with--no-print-media-type. - Create a minimal file. Use one inline stylesheet and one element whose visibility changes in
@media print. - Remove unrelated variables. Temporarily remove JavaScript, web fonts, external CSS, and remote images.
- Inspect paths. Verify every stylesheet, image, and font URL from the converter’s host.
- Check overrides. Search for later rules, higher-specificity selectors, and
!important. - Record versions. Reproduce with the same wkhtmltopdf build before changing CSS.
- Reintroduce features gradually. Add external CSS, assets, and scripts one at a time until the failure returns.
| Symptom | Likely cause | Practical fix |
|---|---|---|
| Print-only rule never appears | Screen media is still selected, or the stylesheet did not load | Use --print-media-type; then test the rule in an inline minimal file |
| Shared rule seems missing | A print rule overrides it, or the element is hidden or moved | Inspect specificity, source order, display, visibility, overflow, and positioning |
| External CSS has no effect | Bad URL or inaccessible resource | Use a resolvable path and test with inline CSS |
| Layout differs across hosts | Different wkhtmltopdf or patched-Qt builds | Pin and record the exact binary and environment |
| Modern CSS behaves unexpectedly | Legacy Qt/WebKit support limits | Reduce the feature, provide a fallback, or evaluate a maintained renderer |
Security and reliability considerations
The wkhtmltopdf project status page warns against processing untrusted HTML or JavaScript without sanitization, stating that doing so can lead to complete server takeover. Treat HTML-to-PDF conversion as a privileged operation: sanitize user content, isolate the conversion process, restrict network and filesystem access where possible, and avoid passing attacker-controlled options to a shell.
For repeatable output, pin the converter version, keep input fixtures for regression checks, and make asset URLs deterministic. A successful process exit does not prove that every stylesheet or image loaded; inspect the resulting PDF and test representative documents.
Performance and tool selection
Keep documents small during diagnosis. Inline critical print CSS, reduce unnecessary assets, and avoid loading scripts that do not affect the PDF. The project status page points to WeasyPrint or Prince for controlled report generation and to Puppeteer for dynamic-JavaScript sites. These are project suggestions for different requirements, not a benchmark or a complete feature comparison.
Choose based on the rendering engine your documents require, JavaScript needs, CSS compatibility, deployment constraints, security isolation, maintenance, and licensing. The sources reviewed here do not establish current prices or head-to-head performance results.
Or skip the browser setup
If you need a clean screenshot or PDF from a URL instead of maintaining a local Qt/WebKit conversion stack, ScreenshotNeo provides a GET endpoint and an MCP server for AI clients. See the API documentation for all options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account.
FAQ
Does --print-media-type apply to images?
No. The C API documentation says load.printMediaType has no effect for wkhtmltoimage. The setting is documented for PDF rendering.
Must every declaration be placed inside @media print?
No. Unqualified rules remain part of the cascade. Put shared styles outside the media query and override only print-specific differences.
Does the flag guarantee modern CSS support?
No. It selects the media type. The project status page describes the underlying Qt/WebKit stack as old, so verify the CSS features your document needs.
Is wkhtmltopdf safe for user-submitted HTML?
The project warns against untrusted HTML and JavaScript without sanitization. Sanitize input and isolate the conversion process before accepting user content.
What should I use for JavaScript-heavy pages?
The project status page names Puppeteer as an alternative to consider for dynamic-JavaScript sites. Evaluate it against your security, deployment, and maintenance requirements.


