If page numbers are missing from a Puppeteer PDF, enable displayHeaderFooter and put Puppeteer’s pageNumber and totalPages classes in a valid footerTemplate or headerTemplate. Reserve space with PDF margins, and style the template with inline CSS rather than assuming your application’s Tailwind stylesheet is available inside it.
The smallest working fix
Use this configuration in the same page.pdf() call that writes the production PDF:
await page.pdf({
path: 'output.pdf',
displayHeaderFooter: true,
footerTemplate: `
<div style="width:100%; text-align:center; font-size:10px; color:#374151;">
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</div>
`,
margin: {
top: '0.5in',
bottom: '0.5in'
}
});
pageNumber is replaced with the current page and totalPages with the final page count when Puppeteer renders the PDF. They are special class names understood by the PDF header/footer renderer; they are not Tailwind utilities and do not need to exist in your application data.
The documented default for displayHeaderFooter is false. A template string by itself therefore does not make a footer visible. Header and footer margins are also unset by default, so a footer can be clipped or overlap the document unless you reserve room.
#1 Best Overall
- Create and edit PDFs. Collaborate with ease. E-sign documents and collect signatures. Get everything done in one app, wherever you go.
- Edit text and images without jumping to another app.
- E-sign documents or request e-signatures on any device. Recipients don’t need to log in to e-sign.
- Convert PDFs to editable Microsoft Word, Excel, or PowerPoint documents.
- Share PDFs for collaboration. Commenting features make it easy for reviewers to comment, mark up, and annotate.
A complete Puppeteer example with a Tailwind page
The following script is a complete pattern for a page that already uses Tailwind classes. Replace the URL with your application and keep the footer styling self-contained.
const puppeteer = require('puppeteer');
async function createPdf() {
const browser = await puppeteer.launch({
headless: true
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 900, deviceScaleFactor: 1 });
await page.goto(process.env.PAGE_URL || 'https://example.com', {
waitUntil: 'networkidle0'
});
// PDF output uses print media by default. Use this only when you
// deliberately want screen rules instead of print rules.
// await page.emulateMediaType('screen');
await page.pdf({
path: 'output.pdf',
format: 'A4',
printBackground: true,
displayHeaderFooter: true,
headerTemplate: '<div></div>',
footerTemplate: `
<div style="width:100%; padding:0 18mm; box-sizing:border-box;
text-align:center; font-family:Arial,sans-serif;
font-size:9px; color:#4b5563;">
Page <span class="pageNumber"></span>
of <span class="totalPages"></span>
</div>
`,
margin: {
top: '18mm',
right: '16mm',
bottom: '18mm',
left: '16mm'
}
});
} finally {
await browser.close();
}
}
createPdf().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Run it with PAGE_URL=https://your-site.example node create-pdf.js. The page can use Tailwind for its normal content; the footer does not depend on the Tailwind build being loaded.
How Puppeteer decides whether numbering appears
1. The PDF call must enable the templates
Inspect the actual page.pdf() invocation, not a helper object that may be overwritten later. Confirm that displayHeaderFooter: true reaches the call that creates the file. If a configuration merge sets it back to false, both header and footer disappear even when their template strings are correct.
2. Use the exact built-in classes
The hooks are pageNumber and totalPages, with that capitalization and spelling. Put them on elements in the template:
footerTemplate: `
<div>
<span class="pageNumber"></span> / <span class="totalPages"></span>
</div>
`
Do not replace them with data-page, a React variable, a Tailwind class, or text generated in the page’s main DOM. Puppeteer injects the values only into its header/footer template.
Rank #2
- Create and edit PDFs. Collaborate with ease. E-sign documents and collect signatures. Get everything done in one app, wherever you go.
- Edit text and images without jumping to another app.
- E-sign documents or request e-signatures on any device. Recipients don’t need to log in to e-sign.
- Convert PDFs to editable Microsoft Word, Excel, or PowerPoint documents.
- Share PDFs for collaboration. Commenting features make it easy for reviewers to comment, mark up, and annotate.
3. Reserve physical space
Set a top margin when using a header and a bottom margin when using a footer. The margin must be large enough for the template’s line height, padding and font. Start with 0.5 inch or an equivalent millimetre value, then inspect the PDF at the target paper size. A very tall template with a small margin can be clipped; a very large margin reduces usable content area and may create additional pages.
4. Keep template CSS explicit
A header or footer is supplied as a separate HTML template string. Puppeteer does not promise that the application’s generated Tailwind stylesheet, font-face rules or utility-class extraction is available in that template. Inline CSS makes the intended width, alignment, font size and colour unambiguous. If you use a class in the template, define its style inside the template or verify that your exact browser setup loads the required stylesheet there.
Tailwind CSS issues that look like a numbering bug
Print media can hide or move content
Page.pdf() renders with print CSS. Review @media print rules for selectors that hide the content area, change display properties, or apply positioning that pushes the document under the footer. A global rule such as footer { display:none; } can also affect a template if your setup shares styles unexpectedly.
If the screen design is intentionally required, call await page.emulateMediaType('screen') immediately before page.pdf(). This changes media-query selection; it does not remove the need for displayHeaderFooter, the special classes or adequate margins.
Tailwind utilities are not the numbering hooks
Utilities such as text-center, text-xs and text-gray-600 can style your page content, but they do not create page numbers. The special hooks must remain on elements in the Puppeteer template. For predictable output, use inline declarations there even if the main document is fully Tailwind-based.
Rank #3
- Perfect Adobe Acrobat Pro alternative – lifetime license for Windows 10 and 11.
- EDIT text, images, pages, hyperlinks, designs in PDF documents. ORGANIZE PDFs.
- READ and Comment on PDFs – Intuitive reading modes & document commenting and mark up tools!
- CREATE, COMBINE, SCAN and COMPRESS PDFs.
- FILL forms & Digitally Sign PDFs. Work with Digital certificates
Colour changes are separate from missing text
Chromium modifies PDF colours for print by default. If the number is present but its colour or background is wrong, review print colour handling and consider -webkit-print-color-adjust: exact; in the relevant styles. This setting addresses colour reproduction, not whether the page-number hooks are populated.
When to use a document-level alternative
You can also render a numbering element inside the document’s own print layout. That approach is useful when the number must participate in an application-specific grid, share an established component system, or appear in a location that header/footer templates cannot reach. It gives your existing print CSS control over placement, but automatic total-page injection is the key advantage of Puppeteer’s templates. A document-level design generally needs its own pagination or preprocessing strategy to know the final count, so choose it only when that trade-off is acceptable.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
| Approach | Automatic current and total values | Styling source | Best fit |
|---|---|---|---|
| Puppeteer header/footer template | Yes, through pageNumber and totalPages |
Template HTML and inline CSS | “Page X of Y” with minimal pagination code |
| Elements in the document body | Not supplied by these hooks | Your page and Tailwind print styles | Custom placement or a component-driven print layout |
Production checklist
- Verify the final
page.pdf()options containdisplayHeaderFooter: true. - Use the exact
pageNumberandtotalPagesclasses in the template. - Set top and/or bottom margins that exceed the template’s rendered height.
- Keep footer width at 100% and account for horizontal padding with
box-sizing:border-box. - Inspect print media rules and decide deliberately whether to call
emulateMediaType('screen'). - Wait for the page’s real content, images and application data before generating the PDF.
- Generate a multi-page fixture and check the first, middle and final pages.
- Test with the same Puppeteer and browser versions used in production. The official documentation consulted for this explanation identifies Puppeteer 25.12.0 on pages current on September 29, 2026; your installed version may differ.
Troubleshooting missing or incorrect values
| Symptom | Likely cause | Fix |
|---|---|---|
| No header or footer at all | displayHeaderFooter is absent, false, or overwritten |
Log the options immediately before page.pdf() and set it to true there. |
| Footer is visible but shows no numbers | Class names are misspelled or placed in the page body | Put class="pageNumber" and class="totalPages" on elements in footerTemplate or headerTemplate. |
| Footer is cut off or overlaps text | Margins are unset or shorter than the template | Increase the corresponding PDF margin and retest at the actual paper size. |
| Tailwind alignment or colour does not apply | The separate template cannot see the generated Tailwind CSS | Move critical styles inline in the template; treat shared stylesheet availability as setup-dependent. |
| Content differs from the browser view | Print media rules are active | Inspect @media print; call emulateMediaType('screen') only when screen styling is the intended output. |
| Numbers appear, but the total seems wrong | Content changed after capture began, or a delayed resource altered pagination | Wait for the application’s readiness signal, images and data before calling page.pdf(); then verify several pages. |
| Colours look washed out while numbering works | Print colour adjustment changed the output | Review -webkit-print-color-adjust and the print colour rules. |
Reliability and performance considerations
Page count is calculated from the layout that exists when the PDF is rendered. Late-loading fonts, images, API data or expanded accordions can change pagination. The PDF guide states that Page.pdf() waits for fonts by default, but your application still needs an explicit readiness condition for other asynchronous work. A network-idle wait is useful for simple pages; a page-specific “report ready” marker is safer for applications with long polling or analytics requests.
Use a deterministic viewport, paper format and margin set. Small changes in font availability, device scale, content width or print rules can move a line to the next page and therefore change both the current number and the final total. Keep a representative long document in automated tests rather than testing only a one-page sample.
Header/footer templates are rendered for every page. Keep them lightweight: simple HTML and inline CSS are less fragile than loading another stylesheet or script. If a template contains complex layout, test it with the browser version deployed in production, because PDF output depends on the Chromium build as well as the Puppeteer package.
Rank #4
- EDIT text, images & designs in PDF documents. ORGANIZE PDFs. Convert PDFs to Word, Excel & ePub.
- READ and Comment PDFs – Intuitive reading modes & document commenting and mark up.
- CREATE, COMBINE, SCAN and COMPRESS PDFs
- FILL forms & Digitally Sign PDFs. PROTECT and Encrypt PDFs
- LIFETIME License for 1 Windows PC or Laptop. 5GB MobiDrive Cloud Storage Included.
Or skip the browser setup
If your goal is a clean capture or PDF of a URL rather than control over a local Puppeteer process, ScreenshotNeo provides a website screenshot API and MCP server. One GET request can return PNG, JPEG, WebP or PDF output. Its cleanup step accepts consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture; each step can be turned off.
Free tools Windows power users keep installed
One-click scans. No signup required.
For a direct request, 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
Equivalent clients are useful when the capture is part of an existing service:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo reports the result in X-Page-Verdict and X-Billed headers. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed; only clean shots are billed. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients. The service also supports full-page captures with lazy images loaded, CSS-selector element captures, custom CSS and JavaScript, click-before-capture actions, waits, request blocking, cookies and headers, device presets, PDF margins and page ranges, signed links, async webhooks, bulk capture and a usage API.
The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan. Create a free ScreenshotNeo account to start.
Recommended Free Tools
FAQ
Will the total page count stay stable if the source content changes?
No. totalPages describes the pagination of the specific render. A changed title, font, image dimension or print rule can legitimately produce a different total.
Best Value
- Full-featured PDF Editor: Edit text in the document
- Fully convert PDF to Word and Excel and continue editing
- NEW: Further development of existing functions
- NEW: Even faster and more user-friendly
- NEW: Over 75 small improvements in all areas
Can I put a Tailwind class on the number span?
Yes, but only if that class is available to the template renderer. The numbering behaviour comes from the special Puppeteer class; use inline declarations for critical visual properties when stylesheet sharing is uncertain.
Why does a one-page test pass while production fails?
A one-page document may not expose clipping, margin collisions, delayed resources or reflow. Test a fixture long enough to produce several pages and inspect the final page, where an incorrect margin or late layout change is easiest to detect.
Frequently Asked Questions
Will the total page count stay stable if the source content changes?
No. totalPages describes the pagination of the specific render. A changed title, font, image dimension or print rule can legitimately produce a different total.
Can I put a Tailwind class on the number span?
Yes, but only if that class is available to the template renderer. The numbering behaviour comes from the special Puppeteer class; use inline declarations for critical visual properties when stylesheet sharing is uncertain.
Why does a one-page test pass while production fails?
A one-page document may not expose clipping, margin collisions, delayed resources or reflow. Test a fixture long enough to produce several pages and inspect the final 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.




