Use a PDF parser on the bytes returned by page.pdf(). Puppeteer generates the PDF, but it does not return a page-count number. In Node.js, load the returned Uint8Array with a library such as pdf-lib, then call getPageCount(). This counts the finalized PDF produced with your actual paper size, margins, scale, print CSS, and page-range settings.
What page.pdf() returns
Puppeteer’s Page.pdf() method prints the page and returns Promise<Uint8Array>. The result is the PDF file itself, not metadata containing a page count. Therefore, a reliable count requires a second step: parse those bytes as a PDF and ask the parsed document how many pages it contains.
Do not estimate pages from the HTML height, the number of DOM sections, or a character count. Pagination depends on print CSS, font metrics, paper dimensions, margins, scale, images, and breaks. Count the completed artifact instead.
Recommended method: parse the PDF with pdf-lib
Install the packages
npm install puppeteer pdf-lib
The following complete script navigates to a page, generates the PDF in memory, parses those exact bytes, prints the count, and saves the same bytes to disk.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
- 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.
import puppeteer from 'puppeteer';
import { PDFDocument } from 'pdf-lib';
import { writeFile } from 'node:fs/promises';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
const pdfBytes = await page.pdf({
path: 'output.pdf',
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
waitForFonts: true
});
const pdfDoc = await PDFDocument.load(pdfBytes);
const pageCount = pdfDoc.getPageCount();
console.log(`PDF has ${pageCount} pages`);
// Optional: write the in-memory artifact when path was not supplied.
// await writeFile('output.pdf', pdfBytes);
} finally {
await browser.close();
}
PDFDocument.load(pdfBytes) parses the generated document, and getPageCount() returns the number of pages it contains. The count and the saved file refer to the same byte sequence, so there is no risk of counting one version while delivering another.
If the PDF already exists on disk
import { readFile } from 'node:fs/promises';
import { PDFDocument } from 'pdf-lib';
const bytes = await readFile('output.pdf');
const pdfDoc = await PDFDocument.load(bytes);
console.log(pdfDoc.getPageCount());
Reading the completed file is preferable to inferring a count before printing. If another process can replace the file, open it after that process has finished and, where necessary, use a temporary filename followed by an atomic rename.
How print settings change the count
The number you obtain is the count for the exact PDF options used during that call. Puppeteer prints with the print CSS media type by default. Paper size, width and height, orientation, margins, scale, page ranges, and CSS page sizing can all change where content breaks.
Paper size and orientation
format (for example, A4 or the default letter), explicit width/height, and landscape determine the printable rectangle. A narrower or shorter page usually creates more pages, but the exact result depends on the document’s styles.
Margins and scale
Larger margins reduce usable space. The scale option changes the rendered size and can move lines, images, and block elements across page boundaries. Record these values with the count if you use it for billing, tests, or downstream processing.
CSS page sizing and media type
When preferCSSPageSize is enabled, CSS @page dimensions can take precedence over the format setting. If you need screen styles rather than print styles, call await page.emulateMediaType('screen') before page.pdf(). Otherwise, your parser is correctly counting a PDF that may not match what the page looks like on screen.
Fonts and late-loading content
Puppeteer documents waitForFonts as true by default. Keep that behavior unless you have a specific reason to change it. Also wait for application data, images, charts, and any custom web fonts that your page loads after navigation; otherwise an incomplete render can produce a different page count on the next run.
Rank #2
- Fast PDF reader with night mode, reading mode, search and bookmarks
- Highlight, underline, draw, add notes and text on any PDF
- Fill PDF forms and sign documents with your finger
- Merge, extract, rotate and reorder pages; scan documents with your camera
- Works on Fire TV: send PDFs from your phone over Wi-Fi and read them on the big screen
Page ranges
A pageRanges option asks Puppeteer to output only selected pages. The parser reports the number of pages in that reduced PDF, not the number that would have been produced without the range.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteCounting pages while adding “Page N of M”
If your goal is a footer such as “Page 2 of 7,” you do not need a Node-side count just to label the output. Puppeteer’s header and footer templates support special classes named pageNumber and totalPages.
const pdfBytes = await page.pdf({
displayHeaderFooter: true,
footerTemplate: `
<div style="font-size:9px;width:100%;text-align:center;">
Page <span class="pageNumber"></span> of
<span class="totalPages"></span>
</div>`,
margin: { bottom: '40px' }
});
Puppeteer substitutes those classes during PDF creation. This is different from an API that returns a count to your JavaScript. If another part of your application needs the number—for example, to store it in a database—parse the resulting bytes with pdf-lib as shown earlier.
Alternative parser: PDF.js
An application that already uses Mozilla’s PDF.js can read the loaded document’s numPages property. The shape is conceptually:
import fs from 'node:fs/promises';
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
const data = await fs.readFile('output.pdf');
const loadingTask = pdfjsLib.getDocument({ data });
const pdf = await loadingTask.promise;
console.log(`PDF has ${pdf.numPages} pages`);
Choose pdf-lib or PDF.js based on the library already used by your project and its broader PDF requirements. The documented APIs establish how to obtain the count; they do not establish a universal performance, bundle-size, or reliability winner. Verify compatibility with your installed package versions and the kinds of PDFs your application receives.
A production-friendly counting pattern
- Wait for the intended content. Navigate with an appropriate
waitUntilvalue, then wait for a selector, application-ready flag, or other deterministic signal. - Set output options explicitly. Specify format or dimensions, margins, orientation, scale, media type, and page ranges rather than relying on defaults that may change during maintenance.
- Generate once. Keep the returned bytes and write those same bytes to storage or an HTTP response.
- Parse those bytes. Call
PDFDocument.load()andgetPageCount(), or readnumPageswith PDF.js. - Validate the result. Reject an empty or corrupt artifact, log the options used, and keep the browser cleanup in a
finallyblock.
For high-volume jobs, avoid generating a second PDF solely for counting. Parsing the already-created bytes prevents duplicated rendering work and eliminates disagreements caused by nondeterministic content.
Troubleshooting page-count problems
The value is undefined or not a number
page.pdf() does not expose a count property. Treat its result as bytes and pass it to a parser. With pdf-lib, the call is pdfDoc.getPageCount(); with PDF.js, it is pdf.numPages.
Rank #3
- Edit PDFs with Ease. Modify text, images, and layouts directly within your PDF documents.
- Convert & Organize. Export PDFs to Word, Excel, or ePub, and organize files with ease.
- Read & Annotate. Enjoy intuitive reading modes and powerful tools to comment, highlight, and mark up PDFs.
- Create & Manage PDFs. Create new PDFs, combine multiple files, scan documents, and compress for easy sharing.
- Fill & Sign Forms. Complete forms and digitally sign documents with secure e-signature tools.
The count changes between runs
Look for late network requests, web fonts, animations, timestamps, responsive breakpoints, and data that arrives after navigation. Wait for a deterministic ready condition, disable or finish animations, and use fixed viewport and print settings. Ensure the same browser and package versions are used when reproducibility matters.
The count differs from the browser preview
Compare print CSS with screen CSS. Puppeteer uses print media unless you call emulateMediaType('screen'). Then compare paper size, margins, scale, @page rules, and whether preferCSSPageSize is enabled.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesThe footer says the wrong total
Confirm that the template uses the exact class names pageNumber and totalPages, and that displayHeaderFooter: true is set. Increase the corresponding margin so the footer does not overlap content. For a value in Node.js, independently parse the final bytes.
PDFDocument.load() fails
Check that you passed the complete PDF byte array, not an HTML error page, JSON response, or truncated file. Preserve the original Uint8Array when writing and transferring data, and wait for the write to complete before another process reads the file. For encrypted, malformed, or unusual input PDFs, test the parser against that exact file and consider PDF.js if it better fits your existing pipeline.
Only part of the document appears
Inspect pageRanges, CSS break-before/break-after rules, hidden elements, and application-level clipping. A parser cannot recover pages that Puppeteer was instructed not to render.
Performance, reliability, and cost considerations
Counting itself is normally a lightweight parse compared with launching Chromium and rendering a page, but the exact cost depends on document size and your chosen library. Generate once, parse once, and retain the bytes you will deliver. Reuse a browser process when safe, while creating isolated pages for concurrent jobs and always closing pages and browsers on success or failure.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →For reliable automation, log the URL, viewport, media type, paper settings, margins, scale, page range, browser version, and parser version alongside the count. Store a hash or object identifier for the PDF so a later audit can prove which artifact was counted. Treat a parser error as a failed job rather than silently reporting zero pages.
Rank #4
- All-in-one office pack - Documents, Sheets, Slides & PDF
- Cross-platform (Android, iOS, Windows PC)
- Supports Microsoft Office formats
- Use 30+ charts & 250+ formulas in Sheets
- In-depth features for document creation & formatting
Or skip the browser setup
If you need a screenshot or PDF from a URL rather than a Puppeteer-controlled render, ScreenshotNeo provides a single API request. Its cleanup step accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also offers an MCP server for AI agents, with take_screenshot, get_page_info, and capture_pdf tools.
For PDF capture, see the ScreenshotNeo documentation. A direct request looks like this:
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 endpoint can be called from 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)
Or 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}`);
The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Frequently Asked Questions
Can I calculate the count from the HTML before calling page.pdf()?
No. HTML height and element counts do not account for print pagination, fonts, margins, paper size, scale, or page breaks. Count the completed PDF.
Does totalPages give my Node.js code a number?
No. It is a header/footer template placeholder that Puppeteer replaces while creating the PDF. Parse the output if application code needs a numeric value.
Which parser should a new project choose?
pdf-lib is a straightforward choice for loading bytes and calling getPageCount(). PDF.js is sensible when your project already depends on it. Confirm compatibility with your installed versions and input files.
The Bottom Line
Generate the PDF, keep the returned bytes, parse that exact artifact, and call getPageCount() (or read PDF.js’s numPages). The resulting number is the page count for the print settings and content you actually delivered.
Recommended Free Tools
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.

