"Page X of Y" in Puppeteer and Playwright PDFs, and why the footer looks wrong
Both Puppeteer and Playwright can stamp page numbers on a PDF. Almost everyone's first attempt comes out microscopic, unstyled or missing entirely. Here's why, and how to fix it.
The basic version
page.pdf() takes a footerTemplate (and headerTemplate). Chrome fills any element with these class names:
| Class | Value |
|---|---|
pageNumber |
current page |
totalPages |
total pages |
date |
print date |
title |
document title |
url |
document URL |
await page.pdf({
path: 'out.pdf',
format: 'A4',
displayHeaderFooter: true,
headerTemplate: '<span></span>', // an empty header, or Chrome prints its default one
footerTemplate: `
<div style="font-size:10px; width:100%; text-align:center; color:#555;">
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</div>`,
margin: { top: '20mm', bottom: '20mm' },
});
Why it goes wrong
- It's invisible. Header and footer templates render in their own context with a tiny default font size: without an inline size, the footer text in my test came out under 1pt. Always set
font-sizeinline. - It prints on top of your content. The template draws in the page margin area, and Chrome doesn't move the body out of its way. With
margin: 0, the footer lands over your last line of text. Givetopandbottomreal space. - Your CSS doesn't apply. The template doesn't see your page's stylesheets. Inline every style.
- Images and fonts don't load. The template won't fetch external resources. Embed a logo as a base64 data URI and stick to system fonts.
- You get a default header you didn't ask for. With
displayHeaderFooter: true, leaving outheaderTemplateprints Chrome's default (date and title). Pass an empty element.
The limits
The template is the same on every page. You can't show "Chapter 3" in the header only on chapter 3's pages, number pages differently in an appendix, skip the footer on a cover page, or use your document's own fonts. That's what CSS paged media (@page margin boxes and named strings) is for, and Chrome doesn't support it by itself. Paged.js is the usual workaround; see the next post.
I'm building Galley PDF, an HTML-to-PDF API with page numbering and running headers that use your own fonts and CSS. Join the waitlist.