Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Use Puppeteer’s built-in PDF template classes: enable displayHeaderFooter, then put pageNumber and totalPages spans in a header or footer template. Puppeteer replaces those spans while printing, so they are not JavaScript values returned by page.pdf().

Use the built-in PDF placeholders

The smallest working configuration is a footer containing two special class names:

const pdf = await page.pdf({
  format: 'A4',
  displayHeaderFooter: true,
  footerTemplate: `
    <div style="width:100%; text-align:right; font-size:9px;">
      Page <span class="pageNumber"></span>
      of <span class="totalPages"></span>
    </div>`,
  margin: { bottom: '20mm' }
});

pageNumber is replaced with the current printed page number, and totalPages is replaced with the document’s total page count. The replacement happens inside the PDF header or footer during printing; your application does not read these values from the JavaScript result.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Header and footer output is disabled by default, so displayHeaderFooter: true is required. A footer is usually the least intrusive location for “Page X of Y,” but the same classes work in headerTemplate.

A complete Puppeteer example

This Node.js script opens a page, prints it to a PDF, and adds a right-aligned page counter. Save it as pdf-with-pages.mjs, install Puppeteer, and run it with Node.js.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://example.com', { waitUntil: 'networkidle0' });

  await page.pdf({
    path: 'example-with-page-numbers.pdf',
    format: 'A4',
    printBackground: true,
    displayHeaderFooter: true,
    headerTemplate: '<div></div>',
    footerTemplate: `
      <div style="width:100%; padding:0 12mm; box-sizing:border-box;
                  text-align:right; font-family:Arial,sans-serif;
                  font-size:9px; color:#444;">
        Page <span class="pageNumber"></span>
        of <span class="totalPages"></span>
      </div>`,
    margin: {
      top: '18mm',
      right: '12mm',
      bottom: '20mm',
      left: '12mm'
    }
  });
} finally {
  await browser.close();
}

The empty header template is optional; it is shown to make the two template locations explicit. You can omit it when only a footer is needed. The path option writes the file directly. If you omit path, the current page.pdf() API returns the PDF bytes as a Promise<Uint8Array>, which you can send from an HTTP response or write with your own storage code.

What the template classes mean

Class Value inserted by Puppeteer Typical use
pageNumber The current page number “Page 3” in a footer or header
totalPages The total number of pages in the generated PDF “of 12” next to the current page
date The print date supplied by Puppeteer A generated-on line
title The page title A running document title
url The page URL A source URL in a header or footer

Only the special classes are substituted. A JavaScript expression such as ${pageNumber} is not a substitute for the class and will not calculate pagination for you.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choosing a header or footer

Footer placement

A footer keeps the counter away from the document’s title and body content. Reserve space with a bottom margin; otherwise the printed footer can be clipped or overlap the page content. The example uses 20mm, but the correct value depends on your font size, padding, and paper format.

Header placement

Use headerTemplate when the page counter belongs at the top of each sheet:

await page.pdf({
  format: 'A4',
  displayHeaderFooter: true,
  headerTemplate: `
    <div style="width:100%; text-align:right; font-size:9px;">
      Page <span class="pageNumber"></span>
      of <span class="totalPages"></span>
    </div>`,
  margin: { top: '20mm' }
});

Allocate top margin rather than bottom margin in this case. You may use both templates for a title in the header and pagination in the footer.

Make the counter visible and keep it inside the page

Use inline styles

Header and footer templates are small HTML fragments. Put alignment, width, padding, font family, color, and font size directly on the elements you print. A very small substituted value can be difficult to see, so increase the font size while diagnosing a missing or faint counter.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Reserve print margin

The PDF reference treats margin as optional and sets no margins when it is omitted. Set explicit margins whenever a header or footer matters. Then open the actual PDF and inspect the first, middle, and last pages for clipping, collision, and inconsistent alignment.

Account for the template’s box

If you add horizontal padding to a full-width element, use box-sizing:border-box or reduce its width. This prevents a nominally 100-percent-wide footer from extending beyond the printable area.

Receiving the PDF bytes instead of writing a file

When another service will store or return the document, leave out path and keep the returned value:

const pdfBytes = await page.pdf({
  format: 'A4',
  displayHeaderFooter: true,
  footerTemplate: '<div style="width:100%;text-align:right;font-size:9px;">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
  margin: { bottom: '20mm' }
});

// pdfBytes is the generated PDF data (a Uint8Array).
// Send it as application/pdf or persist it using your application’s storage layer.

The byte array is the complete PDF output. It does not contain separate pageNumber or totalPages properties; those values are rendered into the printed template.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Controlling which pages print

Puppeteer’s PDF options include pageRanges for selecting pages. For example:

await page.pdf({
  format: 'A4',
  displayHeaderFooter: true,
  footerTemplate: '<div style="text-align:right;font-size:9px;">Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
  margin: { bottom: '20mm' },
  pageRanges: '2-4'
});

Check the rendered result when combining ranges with page labels. The API documents page selection, but the cited reference does not define whether displayed numbers are renumbered relative to the selected range. Do not assume that selecting pages 2–4 will display “Page 1 of 3”; verify the behavior in the Puppeteer version you have installed.

Version and API details

The official option reference consulted for this feature is surfaced as Puppeteer 25.12.0, while the corroborating Puppeteer Core type definition is 24.42.0. Option names and placeholder classes are established in both references, but details can change between releases. Read the API reference matching your installed package when a version-specific behavior, type, or default matters.

The documented default for displayHeaderFooter is false. The current method signature returns Promise<Uint8Array> when no output path is supplied. Neither fact changes the template mechanism: enable the display flag and place the classes in template HTML.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshooting page numbers

The footer or header does not appear

  • Confirm displayHeaderFooter: true is present in the same page.pdf() call.
  • Confirm the HTML is in headerTemplate or footerTemplate, not in the page body.
  • Open the generated PDF rather than relying on a browser preview that may cache an older file.

The classes print literally or show no value

  • Use the exact class names pageNumber and totalPages, including capitalization.
  • Put each class on an element, such as <span class="pageNumber"></span>.
  • Do not try to read them as JavaScript variables before calling page.pdf().

The values are present but almost invisible

  • Increase the inline font-size temporarily.
  • Use a contrasting text color and a plain system font while testing.
  • Remove complicated CSS from the template until substitution is visible, then add styling back incrementally.

The footer is clipped or overlaps the document

  • Increase the bottom margin for a footer or the top margin for a header.
  • Reduce template padding or font size if the reserved area is still insufficient.
  • Inspect pages with long headings, tables, and the final page; their layout can expose collisions that a short test page does not.

The total is unexpected when using page ranges

Page ranges select output pages, but the cited API description does not specify renumbering semantics for the substituted labels. Render the exact range with your installed version and treat that output as authoritative for your application.

You expected page numbers in the returned object

page.pdf() returns PDF data, not a pagination object. The current return type is a Uint8Array; the page labels exist inside the rendered document.

Production checklist

  • Set displayHeaderFooter: true.
  • Place pageNumber and totalPages in a header or footer template.
  • Use readable inline styling.
  • Reserve margin space for the template.
  • Render and inspect the actual PDF, including its last page.
  • Test any pageRanges combination you rely on.
  • Match documentation and tests to the Puppeteer version installed in deployment.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is simply a reliable screenshot or PDF of a URL rather than controlling a local Puppeteer process, ScreenshotNeo provides a website screenshot API and MCP server. Its PDF capture supports paper size, margins, landscape mode, and page ranges, while the API handles the browser session for you.

For a one-call capture, see the ScreenshotNeo API documentation and run:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in 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)

And in 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 accepts the cookie or consent banner like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month without a card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account to try it.

FAQ

Can I use both a header and a footer?

Yes. Enable headers and footers once, then provide both templates and allocate top and bottom margins for their combined height.

Best Value
The SQL Programming Language: .
  • Used Book in Good Condition

Does totalPages count pages that are not printed by a range?

The cited API material documents page selection but does not define how substituted totals behave with ranges. Verify the generated PDF with the exact range and package version you deploy.

What does Puppeteer return when I need the PDF in memory?

Without a file path, the current page.pdf() API returns a Promise<Uint8Array> containing the PDF bytes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Can the page number be placed in the document body instead of a template?

The documented substitution mechanism is limited to the HTML supplied through headerTemplate and footerTemplate. Put the special classes there and enable displayHeaderFooter.

Why does my PDF have no visible margin around the footer?

Puppeteer applies no margins when the option is omitted. Set an explicit bottom margin for a footer or top margin for a header, then inspect the rendered PDF for clipping.

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.