Use a separate HTML file with --header-html, give the PDF enough top margin, and let wkhtmltopdf pass page metadata to that file as query-string values. A small JavaScript subst() function copies those values into elements whose classes are page, topage, title, date, or another supported variable. The result is a header that changes automatically on every page.
The working pattern
wkhtmltopdf renders the header as a separate HTML document. During conversion it appends values such as the current page and total pages to that document’s URL. Your header script reads the query string and fills marked elements. The body document does not need to know the final page count.
- Create a header HTML file containing placeholders and the substitution script.
- Pass it with
--header-html. - Reserve vertical space with
--margin-top. - Use
--header-spacingto control the gap between the header and body. - Convert the source HTML to PDF and inspect a multi-page result.
Minimal conversion command
wkhtmltopdf
--header-html header.html
--margin-top 25mm
--header-spacing 5
input.html output.pdf
The margin is part of the layout, not a cosmetic setting. If it is smaller than the rendered header, the header can overlap the first lines of content or be clipped. If the spacing is excessive, the header may be pushed outside the printable area; reduce it or increase the margin.
Build a dynamic header.html
Save this as header.html. It supports the page number, total page count, document title, local date, and ISO date. Add your own markup and CSS around the placeholders as needed.
#1 Best Overall
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script>
function subst() {
const vars = {};
const query = document.location.search.substring(1).split('&');
for (const item of query) {
if (!item) continue;
const pair = item.split('=', 2);
vars[pair[0]] = decodeURIComponent(pair[1] || '');
}
for (const name of ['page', 'topage', 'title', 'date', 'isodate']) {
for (const el of document.getElementsByClassName(name)) {
el.textContent = vars[name] || '';
}
}
}
</script>
</head>
<body style="border:0; margin:0" onload="subst()">
<table style="width:100%; border-bottom:1px solid #888">
<tr>
<td class="title"></td>
<td style="text-align:right">Page <span class="page"></span> of <span class="topage"></span></td>
</tr>
</table>
</body>
</html>
The class names are the API. Keep the subst() call on page load, use textContent for untrusted values, and place the same class on multiple elements if a value must appear more than once. wkhtmltopdf’s documented HTML-header mechanism supplies the metadata; it does not execute a server-side template in your application.
Supported values in the HTML header
| Class/token | Meaning | Typical use |
|---|---|---|
page / [page] |
Current page number | “Page 3” |
frompage / [frompage] |
First page in the current range | “Pages 3–7” when ranges are used |
topage / [topage] |
Last page or total page number | “of 12” |
webpage / [webpage] |
Web-page address | Source identification |
section / [section] |
Current section | Section labels |
subsection / [subsection] |
Current subsection | Nested navigation |
date / [date] |
Formatted date | Human-readable report date |
isodate / [isodate] |
ISO-formatted date | Machine-friendly date |
time / [time] |
Time of conversion | Build timestamp |
title / [title] |
Page title | Document title |
doctitle / [doctitle] |
Document title value | Report or book title |
sitepage / [sitepage] |
Page within a site or object | Site-level numbering |
sitepages / [sitepages] |
Total pages within that site/object | Site-level totals |
HTML headers use the class form. Text-only options use bracketed tokens. Do not write [page] inside the HTML file and expect it to be substituted; put a class="page" element there instead.
Use a plain text header when HTML is unnecessary
For a fixed label plus page numbers, the built-in left, center, and right options are simpler and avoid a second HTML document.
wkhtmltopdf
--header-left "Project report"
--header-right "Page [page] of [topage]"
--margin-top 18mm
input.html output.pdf
These options also accept tokens such as [frompage], [webpage], [section], [subsection], [date], [isodate], [time], [title], [doctitle], [sitepage], and [sitepages]. Choose HTML when you need branding, tables, images, conditional elements, or custom JavaScript; choose text options for a one-line header.
Pass your own dynamic values with –replace
--replace adds named substitutions for header and footer text. Quote values containing spaces or punctuation.
wkhtmltopdf
--replace customer "Acme Ltd"
--header-right "[customer] — Page [page] of [topage]"
--margin-top 18mm
input.html output.pdf
This is useful for a customer name, invoice identifier, environment label, or build number that your application already knows. It is intended for header and footer text; it is not a general-purpose variable system for arbitrary body HTML.
Headers, footers, margins, and page geometry
Reserve space deliberately
Measure the header’s rendered height at the fonts and image sizes you deploy. Set --margin-top above that height, then use --header-spacing for a small visual gap. A border or background does not increase the PDF’s available page area; it still consumes the margin region.
Add a dynamic footer
Footers use the corresponding options: --footer-html, --footer-left, --footer-center, --footer-right, --footer-spacing, and --margin-bottom. The same substitution variables and HTML-document approach apply. Give the footer its own margin so body content cannot collide with it.
Free tools Windows power users keep installed
One-click scans. No signup required.
Rank #3
Keep layout predictable
- Use a fixed or carefully constrained header height; long titles can wrap and change it.
- Prefer absolute or otherwise accessible URLs for images and stylesheets.
- Keep table widths explicit when the header contains columns.
- Test at the exact paper size, orientation, and margin settings used in production.
Wait for JavaScript and asynchronous data
JavaScript is enabled by default in the documented command-line behavior, but a header that fetches data asynchronously can run before the data arrives. Increase the delay when a known amount of time is sufficient:
wkhtmltopdf
--header-html header.html
--javascript-delay 1500
--margin-top 25mm
input.html output.pdf
A more deterministic pattern is to have the page set a known window.status value after its data and layout are ready, then wait for that value:
wkhtmltopdf
--header-html header.html
--window-status ready-for-pdf
--margin-top 25mm
input.html output.pdf
Use a delay for a simple, bounded page. Use --window-status when readiness depends on network requests or client-side rendering. Ensure the status is always set on success; otherwise conversion can wait indefinitely or until the process timeout imposed by your wrapper.
Make assets and local files load reliably
The header is loaded by the wkhtmltopdf process, not by your browser session. A relative image or stylesheet path that works interactively may fail when the header is opened from another directory or a remote worker. Use an absolute, reachable URL or a path valid in the conversion environment. If your deployment restricts local-file access, configure it according to your security policy rather than assuming a desktop default.
Recommended Free Tools
Rank #4
- Used Book in Good Condition
Keep credentials out of query strings and header markup. If the header must display tenant-specific information, pass a controlled value with --replace or generate a temporary header file with permissions limited to the conversion user.
Reproducibility and version management
The upstream wkhtmltopdf repository was archived by its owner on January 2, 2023 and is read-only. That maintenance status does not prevent existing binaries from working, but it increases the importance of pinning the exact binary and testing it in the same operating-system image used in production.
- Record the wkhtmltopdf version and the operating-system image.
- Run a regression PDF containing a long title, an image, a page break, and a footer.
- Compare output after any font, WebKit, container, or binary change.
- Set an external process timeout and capture stderr for failed jobs.
- Keep a known-good binary available for rollback.
Troubleshooting dynamic headers
| Symptom | Likely cause | Fix |
|---|---|---|
| Header is absent | The file cannot be reached or the option points to the wrong path. | Use a path or URL accessible to the wkhtmltopdf process, verify permissions, and check stderr. |
| Header overlaps body text | Top margin is smaller than the rendered header. | Increase --margin-top; then tune --header-spacing. |
| Header is clipped or appears outside the page | Spacing is excessive for the available margin. | Reduce spacing or increase the top margin and retest at the target paper size. |
| Page or title values are blank | Class names do not match, query parsing was removed, or subst() never ran. |
Use the documented class names, preserve the on-load call, and inspect the generated header file in isolation. |
| Images or CSS do not appear | Relative paths, inaccessible local files, authentication, or blocked resources. | Use absolute accessible paths, make required resources available to the worker, and account for local-file restrictions. |
| Async values are missing | Rendering finished before client-side work completed. | Increase --javascript-delay or set and wait for a reliable window.status. |
| Results differ between machines | Different binaries, fonts, WebKit behavior, or operating-system libraries. | Pin the binary and environment, install the same fonts, and test in deployment. |
| Conversion hangs | A readiness status is never set or a resource never responds. | Set status on every success path, enforce an outer timeout, and remove or bound slow dependencies. |
Or skip the browser setup
If your goal is a clean image or PDF of a URL rather than a wkhtmltopdf-specific local pipeline, ScreenshotNeo makes the capture a single API request. It accepts consent banners like a visitor, removes more than 60 known consent platforms plus newsletter popups and chat widgets before capture, and lets you turn each cleanup step off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; each response identifies the result with X-Page-Verdict and X-Billed headers.
For API parameters, authentication, PDF settings, and the complete option list, see the ScreenshotNeo documentation.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
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)
Node.js
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 also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Every feature is included on every plan: full-page and element capture, device presets and custom viewports, dark mode, retina scale, CSS and JavaScript, clicks, waits, request blocking, custom headers and cookies, user-agent, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage API, and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work, which can simplify migration.
Best Value
- THE PERFECT GIFT IDEA: The perfect gift can be hard to find, but with this unique, not-sold-in-stores coffee and tea mug, you’re sure to give the best gift every time.
- TREAT YOURSELF OR A FRIEND: Whether you’re buying this high quality mug for yourself, a friend, boss, co-worker, or family member they’re sure to love its distinctive, long-lasting design. It’s a great, multi-functional gift for anyone for any occasion.
- PREMIUM QUALITY: Our premium, full-color sublimation imprint appears on both sides of this 11 ounce, white ceramic mug. Each mug is crafted from the highest grade ceramic, and all of our designs are printed and sublimated in the United States.
- MICROWAVE AND DISHWASHER SAFE: This 11 ounce, white ceramic coffee mug has a large, easy-to-grip C-handle and is both microwave and dishwasher safe.
- SATISFACTION GUARANTEED:Your complete satisfaction is our top priority. We meticulously package our mugs to ensure they arrive on time and in great condition.
The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; higher plans are Growth at $15 for 15,000, Pro at $39 for 60,000, Scale at $99 for 250,000, and Business at $249 for 1,000,000. Yearly billing gives two months free. Create a free ScreenshotNeo account to start with the 1,000 monthly shots and no card.
When to choose each approach
- Choose wkhtmltopdf when you own the HTML input, need its established CLI flags, or must reproduce an existing server-side PDF workflow.
- Choose ScreenshotNeo when the source is a live URL, consent and popup cleanup matters, you want image or PDF output without managing a browser binary, or an AI agent should perform captures through MCP.
- Use both when internal reports are rendered locally but public pages, previews, or fallback captures are handled through an API.
Frequently Asked Questions
Can one header file show different content on different page ranges?
Yes. The header receives the current page and range metadata, so your script can branch on the supplied values or display them directly. Keep the layout height stable so a conditional change does not collide with the body.
What happens if a document title contains characters such as an ampersand?
Read the decoded query value and assign it with textContent, as in the template. Do not concatenate it into innerHTML; that avoids treating title text as markup.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Is a dynamic header recalculated for every page?
wkhtmltopdf renders the supplied header document as part of the PDF conversion and provides page metadata for substitution. The same header design can therefore display a different current-page value on each page.
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.

