How to Add an Image Header in wkhtmltopdf with pdfkit
Add a logo or image header to wkhtmltopdf PDFs with pdfkit using header-html, margins, spacing, and practical fixes for missing headers.
Use a separate HTML document for the header, put the image in that document, and pass it to pdfkit as wkhtmltopdf’s header-html option. Reserve space with margin-top, then tune header-spacing so the body starts below the image.
wkhtmltopdf documents HTML documents for headers and footers, and pdfkit forwards wkhtmltopdf settings through its options dictionary. See the wkhtmltopdf usage manual, the pdfkit README, and the wkhtmltopdf page settings.
1. Create the header HTML document
Keep the header independent from the main document. Use an image URL or a path that the wkhtmltopdf process can read.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>
<body style="margin:0">
<img src="file:///absolute/path/to/logo.png"
alt=""
style="display:block; height:40px;">
</body>
</html>
The file:// form is illustrative. Absolute paths, relative paths, and remote URLs can resolve differently across operating systems and wkhtmltopdf builds. If the image is remote, make sure the renderer can reach it without an interactive login.
2. Generate the PDF with Python and pdfkit
Install pdfkit and ensure the wkhtmltopdf executable is installed and available on PATH. If it is elsewhere, pass its location to pdfkit.configuration().
import pdfkit
options = {
"header-html": "/absolute/path/to/header.html",
"margin-top": "25mm",
"header-spacing": "5",
}
pdfkit.from_file("input.html", "output.pdf", options=options)
pdfkit option names omit wkhtmltopdf’s leading --. For example, use "header-html", not "--header-html". The top margin must be tall enough for the header; spacing adds a gap between the header and body.
3. Complete runnable example
This example creates both HTML files and writes a PDF. Replace the image path with an image accessible to your renderer.
from pathlib import Path
import pdfkit
work = Path("pdf-work").resolve()
work.mkdir(exist_ok=True)
header = work / "header.html"
header.write_text("""<!doctype html>
<html><head><meta charset='utf-8'></head>
<body style='margin:0'>
<img src='file:///absolute/path/to/logo.png'
alt='' style='display:block;height:40px;'>
</body></html>""", encoding="utf-8")
body = work / "input.html"
body.write_text("""<!doctype html>
<html><head><meta charset='utf-8'>
<style>body{font-family:Arial,sans-serif} h1{color:#222}</style>
</head><body>
<h1>Report</h1>
<p>Content begins below the image header.</p>
</body></html>""", encoding="utf-8")
config = pdfkit.configuration() # Add wkhtmltopdf='/path/to/wkhtmltopdf' if needed
options = {
"header-html": str(header),
"margin-top": "25mm",
"header-spacing": "5",
"encoding": "UTF-8",
}
pdfkit.from_file(str(body), str(work / "output.pdf"),
options=options, configuration=config)
4. Tune margins and spacing
| Setting | Purpose | What to check |
|---|---|---|
header-html |
Separate HTML header document | Path or URL is readable by wkhtmltopdf |
margin-top |
Reserves vertical page space | Increase it when the body overlaps the header |
header-spacing |
Gap between header and body | Large values can push the header outside the page |
margin-left/margin-right |
Horizontal page margins | Align the header image with body content |
page-size or orientation |
Page geometry | Header width may need responsive CSS |
Start with a modest image height and a top margin larger than that height. Reduce spacing if the header disappears near the top edge; increase the margin if content begins too high.
5. Image paths, remote assets, and local-file access
- Use an absolute path while diagnosing path problems.
- Confirm the account running your application can read the image and header files.
- Use a complete URL for remote images and verify DNS, TLS, authentication, and redirects.
- wkhtmltopdf loads images by default; the
--no-imagesoption disables them. Do not pass it when you need a logo. - Local resources can be affected by wkhtmltopdf local-file-access controls. Check the installed binary’s help output if a local image is rejected.
- Prefer a small, appropriately sized PNG, JPEG, or WebP to reduce rendering time and memory use.
6. Header behavior across pages
An HTML header is rendered as a page header by wkhtmltopdf, so it can appear on each page. Keep the header’s height stable. If its content changes height between pages, reserve enough top margin for the largest version. Test long documents because overlap may only become visible after pagination.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| No header at all | Wrong option name, missing file, or unsupported binary | Use header-html without leading dashes, verify the path, and run wkhtmltopdf --version and wkhtmltopdf --help. |
| Header appears but image is blank | Image URL cannot be resolved or images were disabled | Try an absolute local path, check permissions, remove --no-images, and test the URL from the same host. |
| Body overlaps the image | Top margin is too small | Increase margin-top until the body starts below the full header. |
| Header is clipped or off-page | Top margin or spacing is excessive | Reduce header-spacing and confirm the page size and header dimensions. |
| Local file access error | Binary security settings block local resources | Inspect the binary’s local-file-access flags and use an allowed path or a controlled URL. |
| Different machines produce different output | Different wkhtmltopdf builds or patched-Qt support | Record the binary version, compare help output, and deploy a known build consistently. |
| pdfkit cannot find wkhtmltopdf | Executable is not on PATH |
Pass pdfkit.configuration(wkhtmltopdf='/absolute/path/to/wkhtmltopdf'). |
| Remote image times out | Network, TLS, redirect, or authentication issue | Make the asset reachable without a browser session, or download it locally before conversion. |
8. Validate before shipping
- Open the generated PDF and inspect the first and last page.
- Test a document long enough to create several pages.
- Test on the same operating system and wkhtmltopdf build used in production.
- Check that the header does not cover selectable body text.
- Check missing-image behavior and decide whether conversion should fail or continue.
- Log the input path, header path, renderer version, and conversion error output.
9. Performance, reliability, and cost notes
Header rendering adds another HTML resource to each conversion. Keep the header DOM and image small, avoid unnecessary remote requests, and reuse a stable local asset when possible. Set an application-level timeout around pdfkit because wkhtmltopdf can wait on unreachable resources. For repeatable output, pin the wkhtmltopdf build and fonts, and avoid time-dependent remote content.
pdfkit and wkhtmltopdf are software components you run yourself, so your costs are compute, storage, and any remote asset traffic. Header support can vary by installed build; verify the local binary instead of assuming every documented option is enabled.
10. Or skip the browser setup
If your goal is a clean image or PDF of a web page rather than a locally generated wkhtmltopdf document, ScreenshotNeo provides a website screenshot API and MCP server. See the ScreenshotNeo API documentation.
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}`);
- Cookie banners, newsletter popups, and chat widgets are removed before the shot.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers report the page verdict and billing status.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - Free accounts include 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account and start with 1,000 screenshots a month at no charge.
11. FAQ
Can I put the image directly in the main HTML?
That image belongs to the document body. For a repeating page header, use a separate document through header-html.
Do I need a separate header file for every image?
No. One header document can contain multiple images and styles, provided the renderer can resolve every resource.
Why does a header work from the command line but not through pdfkit?
Compare the generated command, option spelling, executable path, working directory, and wkhtmltopdf versions. pdfkit passes options through, but the binary still controls feature support.
How do I add text beside the image?
Add ordinary HTML elements beside the <img> in the header document and reserve enough top margin for the resulting height.


