Add headers, footers and page numbers to HTML-to-PDF
PDF headers, footers and page numbers: add repeating "Page 1 of 5" footers with Chrome, Puppeteer, Gotenberg or an HTML to PDF API.
Chromium (and every tool built on it) prints headers and footers from separate HTML templates. They are not part of your page, which is why they behave differently from normal HTML.
Quick answer. Pass a footer_html template with the pageNumber and totalPages classes, and give it room with a bottom margin.
curl https://html-pdf.com/v1/pdf -H "Authorization: Bearer $HTML_PDF_KEY" -d '{
"html": "<h1>Report</h1>",
"margin": {"bottom": "20mm"},
"footer_html": "<div style=\"font-size:9px;width:100%;text-align:center\">Page <span class=\"pageNumber\"></span> of <span class=\"totalPages\"></span></div>"
}' -o report.pdf
The placeholder classes
These are the classes Chromium fills in automatically inside header and footer templates.
| class | value |
|---|---|
pageNumber |
current page number |
totalPages |
total number of pages |
date |
print date |
title |
document <title> |
url |
document URL |
<div style="font-size:9px; width:100%; text-align:center; color:#666;">
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</div>
With html-pdf.com
This is the full API call with both a header and a footer. Send the templates as header_html and footer_html, and leave room for them with margin.
curl https://html-pdf.com/v1/pdf -H "Authorization: Bearer $HTML_PDF_KEY" -d '{
"html": "<h1>Quarterly report</h1><p>…</p>",
"margin": {"top": "25mm", "bottom": "20mm", "left": "15mm", "right": "15mm"},
"header_html": "<div style=\"font-size:9px;width:100%;padding:0 15mm;display:flex;justify-content:space-between\"><span>Acme Inc.</span><span class=\"date\"></span></div>",
"footer_html": "<div style=\"font-size:9px;width:100%;text-align:center\">Page <span class=\"pageNumber\"></span> of <span class=\"totalPages\"></span></div>"
}' -o report.pdf
From an AI agent, just ask "Add page numbers in the footer". The MCP tool html_to_pdf takes the same footer_html option.
Pure CSS: @page margin boxes
This section shows how to put page numbers in your stylesheet instead of a template. Modern Chromium (version 131 and later) supports CSS paged-media margin boxes.
@page {
margin: 20mm;
@bottom-center { content: "Page " counter(page) " of " counter(pages); font-size: 9px; color: #666; }
@top-right { content: "Acme Inc."; font-size: 9px; }
}
Send it with "prefer_css_page_size": true so the @page margins are used. No separate header or footer template is needed.
With Puppeteer
This is the same setup in Puppeteer, using displayHeaderFooter and the two templates.
await page.pdf({
path: "report.pdf",
format: "A4",
displayHeaderFooter: true,
headerTemplate: `<div style="font-size:9px;width:100%;text-align:right;padding-right:15mm"><span class="date"></span></div>`,
footerTemplate: `<div style="font-size:9px;width:100%;text-align:center">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>`,
margin: { top: "25mm", bottom: "20mm" },
});
With Gotenberg
This is how to add headers and footers in Gotenberg. Upload header.html and footer.html next to index.html. Each must be a full HTML document.
curl -F files=@index.html -F files=@header.html -F files=@footer.html \
-F marginTop=1 -F marginBottom=0.8 \
http://localhost:3000/forms/chromium/convert/html -o out.pdf
Why your header doesn't show (or looks tiny)
These are the most common reasons a header or footer goes missing or looks wrong.
- No margin. Headers render inside the page margin. Zero margin means an invisible header. Give at least ~15-25 mm.
- Default font size is very small. Always set
font-sizeinline (8-10px is typical). - Page CSS doesn't apply. The template doesn't see your stylesheet or web fonts. Use inline styles, and stick to system fonts or embed fonts as data URIs.
- No background colors. Add
-webkit-print-color-adjust: exactto the element. - External images don't load. Use a base64
data:URI for logos in headers. - Different first page? Templates repeat on every page. For a cover without a header, render the cover as its own PDF and merge, or design the header so it also fits the cover.
Skip the setup
This is the simplest way to get page numbers without running a browser. Page numbers, headers and footers work out of the box on html-pdf.com with one JSON call.
You get 100 free pages, then from $0.005 per page, and credits never expire. See also CSS page breaks.
FAQ
How do I add page numbers to a PDF generated from HTML?
Pass a footer_html template that contains <span class="pageNumber"></span> and <span class="totalPages"></span>, and set a bottom margin so it has room. Chromium fills in the numbers on every page.
Why is my Puppeteer header or footer not showing?
Headers and footers render inside the page margin, so with zero margin they are invisible. Also set font-size inline, because the default is tiny and your page CSS does not apply to the template.
Can I add page numbers with CSS only?
Yes. Chrome 131 and later support @page margin boxes, for example @bottom-center { content: "Page " counter(page) " of " counter(pages) }. Send it with prefer_css_page_size set to true.
How do I skip the header on the first page?
Header and footer templates repeat on every page. Render the cover as its own PDF and merge it, or design the header so it also fits the cover.
