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.

To leave a cover page unnumbered and show “1” on physical page two, generate the cover and numbered content as separate PDFs, then merge them. Puppeteer’s page.pdf() can add a footer with pageNumber and totalPages, but its documented template API does not provide a reliable “skip only the first page” switch. If physical page two should display “2”, generate the entire document once with the footer enabled.

First decide what “start on the second page” means

There are two legitimate numbering conventions:

Physical PDF page Displayed number Use this when
Cover (page 1) No number The cover is outside the report’s numbered section.
First content page (page 2) 1 The report, thesis, or invoice body has its own sequence.
Cover (page 1) No number The cover is decorative, but the document keeps one physical sequence.
First content page (page 2) 2 Readers need numbers to match the PDF’s physical page order.

Do not change the numeral with CSS until you have chosen the convention. Puppeteer’s built-in pageNumber follows the physical PDF sequence, so a one-pass document naturally displays 2 on physical page two.

How Puppeteer PDF footers work

Puppeteer generates PDFs using the print CSS media type. Set displayHeaderFooter: true and provide a footerTemplate (or headerTemplate). The default for displayHeaderFooter is false, so a footer template alone does not turn numbering on.

Inside a header or footer template, Chromium replaces <span class="pageNumber"></span> with the current page and <span class="totalPages"></span> with the total page count. These placeholders are injected by the PDF header/footer mechanism; ordinary body HTML does not receive those values.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
PDF Extra 2024| Complete PDF Reader and Editor | Create, Edit, Convert, Combine, Comment, Fill & Sign PDFs | Lifetime License | 1 Windows PC | 1 User [PC Online code]
  • 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.

Option A: page two displays 2 (single PDF)

Use this when the physical sequence is the authoritative sequence. Keep the cover in the same HTML document and enable the footer for every page.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.setContent(`
      <!doctype html>
      <html>
      <head>
        <meta charset="utf-8">
        <style>
          @page { size: A4; margin: 22mm 18mm 22mm; }
          * { box-sizing: border-box; }
          body { font-family: Arial, sans-serif; margin: 0; }
          .cover { height: 245mm; display: grid; place-items: center; page-break-after: always; }
          h1 { font-size: 32px; }
          .content { page-break-before: always; }
        </style>
      </head>
      <body>
        <section class="cover"><h1>Report title</h1></section>
        <main class="content">
          <h2>Introduction</h2>
          <p>Your numbered content starts here.</p>
        </main>
      </body>
      </html>`, { waitUntil: 'networkidle0' });

    await page.pdf({
      path: 'report.pdf',
      format: 'A4',
      displayHeaderFooter: true,
      headerTemplate: '<div></div>',
      footerTemplate: `
        <div style="width:100%; font-size:9px; text-align:center;">
          <span class="pageNumber"></span> / <span class="totalPages"></span>
        </div>`,
      printBackground: true,
      margin: { top: '18mm', bottom: '18mm', left: '18mm', right: '18mm' }
    });
  } finally {
    await browser.close();
  }
})();

Because the footer is enabled for the complete document, the first physical page is 1 and the first content page is 2. Header and footer margins must be large enough to prevent the footer from colliding with body text.

Option B: page two displays 1 (cover excluded)

Render two PDFs. The cover has no header or footer. The body starts its own PDF sequence at 1. Merge the files in order. This is a composition of Puppeteer’s documented controls and a PDF-merging library, not a special page.pdf() mode.

1. Render the unnumbered cover

await page.setContent(coverHtml, { waitUntil: 'networkidle0' });
await page.pdf({
  path: 'cover.pdf',
  format: 'A4',
  displayHeaderFooter: false,
  printBackground: true
});

2. Render the numbered body

await page.setContent(bodyHtml, { waitUntil: 'networkidle0' });
await page.pdf({
  path: 'body.pdf',
  format: 'A4',
  displayHeaderFooter: true,
  headerTemplate: '<div></div>',
  footerTemplate: `
    <div style="width:100%; font-size:9px; text-align:center;">
      <span class="pageNumber"></span> / <span class="totalPages"></span>
    </div>`,
  printBackground: true,
  margin: { bottom: '18mm' }
});

The first page of body.pdf is numbered 1. After it is placed behind cover.pdf, that page becomes physical page two in the final file but still displays 1.

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

3. Merge the PDFs

One practical choice is pdf-lib. Install it with npm install pdf-lib, then merge the cover before the body:

const fs = require('node:fs/promises');
const { PDFDocument } = require('pdf-lib');

async function mergePdfFiles(output, ...files) {
  const merged = await PDFDocument.create();
  for (const file of files) {
    const source = await PDFDocument.load(await fs.readFile(file));
    const pages = await merged.copyPages(source, source.getPageIndices());
    pages.forEach(page => merged.addPage(page));
  }
  await fs.writeFile(output, await merged.save());
}

mergePdfFiles('report.pdf', 'cover.pdf', 'body.pdf')
  .catch(error => { console.error(error); process.exitCode = 1; });

Keep the same paper size, orientation, fonts, and intended margins in both renders. Otherwise the merged file can show a visible jump between the cover and body.

A complete split-and-merge script

The following script demonstrates the whole workflow. Replace the sample HTML with your templates or generated content.

const puppeteer = require('puppeteer');
const fs = require('node:fs/promises');
const { PDFDocument } = require('pdf-lib');

const footerTemplate = `
  <div style="width:100%; font-size:9px; text-align:center;">
    <span class="pageNumber"></span> / <span class="totalPages"></span>
  </div>`;

async function merge(output, inputs) {
  const result = await PDFDocument.create();
  for (const input of inputs) {
    const source = await PDFDocument.load(await fs.readFile(input));
    const pages = await result.copyPages(source, source.getPageIndices());
    pages.forEach(page => result.addPage(page));
  }
  await fs.writeFile(output, await result.save());
}

(async () => {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    const common = `<style>@page{size:A4;margin:22mm 18mm}body{font-family:Arial}</style>`;
    const coverHtml = `<html><head>${common}</head><body><div style="height:245mm;display:grid;place-items:center"><h1>Cover</h1></div></body></html>`;
    const bodyHtml = `<html><head>${common}</head><body><h1>Chapter 1</h1><p>Body content.</p></body></html>`;

    await page.setContent(coverHtml, { waitUntil: 'networkidle0' });
    await page.pdf({ path: 'cover.pdf', format: 'A4', displayHeaderFooter: false, printBackground: true });

    await page.setContent(bodyHtml, { waitUntil: 'networkidle0' });
    await page.pdf({
      path: 'body.pdf', format: 'A4', displayHeaderFooter: true,
      headerTemplate: '<div></div>', footerTemplate,
      printBackground: true, margin: { bottom: '18mm' }
    });

    await merge('report.pdf', ['cover.pdf', 'body.pdf']);
  } finally {
    await browser.close();
  }
})();

Why CSS-only fixes are unreliable

Paged-media CSS defines counter(page) and counter(pages), and Chrome documents those counters for print margin content. Puppeteer’s header/footer template path is separate from ordinary document CSS. In addition, compatibility guidance for Puppeteer reports that @page :first is unsupported. A production workflow should therefore not depend on @page :first to hide only the first footer.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
MobiPDF Lifetime - Professional PDF Editor for Windows | Edit, Sign & Convert PDFs | Best Adobe Acrobat Pro Alternative | Lifetime License
  • 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.

If you use CSS counters for other print styling, verify the result with the exact Chromium version bundled by your deployed Puppeteer release. Do not assume a browser preview and generated PDF paginate identically.

Tables of contents and total-page counts

In the split method, totalPages in the body footer is the number of body pages, not the final merged document’s total. That is usually correct when the body is a separately numbered section. If you need a single table of contents whose entries and total count include the cover, render the complete document in one pass or run a separate post-processing step that rewrites page labels and links.

Changing fonts, waiting for web fonts, image loading, margins, or content length can change page breaks. Generate the final body before relying on its total-page value.

Reliability and performance checklist

  • Pin the Puppeteer package (and therefore its Chromium revision) in deployments.
  • Use waitUntil: 'networkidle0' only when the page can become idle; applications with long-lived connections may require an explicit readiness selector instead.
  • Wait for web fonts and critical images before calling page.pdf(); otherwise late layout shifts can move content to another page.
  • Set explicit paper size, margins, orientation, and printBackground so local and CI output match.
  • Close each browser and page in a finally block to avoid leaked Chromium processes.
  • For repeated jobs, reuse a browser process but create a fresh page per document and cap concurrency according to available memory.
  • Inspect the merged PDF, not only the intermediate files, and test a short document, a multi-page document, and a document whose final line lands near a page boundary.

Troubleshooting

The footer is missing

Confirm displayHeaderFooter: true. A footer template is ignored when that flag is false. Also check that your template contains the documented pageNumber span and that the bottom margin leaves room for it.

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

Page two shows 2 instead of 1

You rendered one PDF, so Chromium is numbering physical pages. Render the cover and body separately, then merge them if the body must begin at 1.

The footer overlaps text

Increase margin.bottom and keep the footer’s CSS small. The margin in the PDF options, rather than padding inside the footer template, reserves printable space.

The cover unexpectedly has a number

Ensure the cover render sets displayHeaderFooter: false. In the split workflow, do not reuse a page configured for the body without explicitly changing the option.

Total pages are wrong

Check whether you are reading the body total after merging. Body totals exclude the cover by design. Also wait for fonts, images, and other layout-affecting resources before rendering.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #3
Scrivar PDF Pro - Organize, Edit, Compress, Convert, Merge, eSign, OCR & 30+ tools | Lifetime License
  • EVERY PDF TOOL UNLOCKED - 30+ tools in one app: edit text and images, convert, merge, split, compress, sign, OCR, redact, watermark, batch process, and more. No feature gates, no upsells, nothing held back.
  • PAY ONCE, OWN FOREVER — A one-time purchase, not a subscription. Other apps runs $240/year — Scrivar is yours for life, with free updates included.
  • UNLIMITED eSIGN, BUILT IN — Send contracts and forms for signature and track every step. Recipients sign in their browser with no account or app needed. Replace DocuSign and save hundreds a year.
  • PC, MAC, AND WEB — Install on any Win 10/11 PC or macOS 11+ Mac (Intel or Apple Silicon), or work in your browser at scrivar.com. Same tools, same account, everywhere you work.
  • OCR + FULL OFFICE CONVERSION — Turn scanned documents into searchable, selectable text, and convert PDFs to and from Word, Excel, and PowerPoint with formatting kept intact.

Blank pages appear

Look for an explicit page-break-before combined with a preceding page-break-after, oversized fixed-height elements, or margins that leave no usable page area. Test without forced breaks, then add only the break that is required.

The merged PDF has inconsistent sizing

Use the same format or explicit width and height for both PDFs. Mixing A4, Letter, portrait, or landscape settings produces visibly different pages even when the merge succeeds.

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 to capture a finished web page as an image or PDF rather than control Puppeteer pagination, ScreenshotNeo provides a single HTTP request. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

See the complete parameter list in the ScreenshotNeo documentation. Example cURL request:

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.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

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)

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 each month with no card. Paid plans start at $5 for 3,000 shots, and every feature is included on every plan. Create a free ScreenshotNeo account.

Practical decision guide

  • Choose one-pass Puppeteer when page two should display 2 and you need one document-wide total.
  • Choose split-and-merge when the cover is unnumbered and page two must display 1.
  • Choose a post-processing workflow when a single merged total, cross-document links, or custom PDF page labels matter more than a simple footer.

Frequently Asked Questions

Can I set the first visible body number with a Puppeteer option?

No documented PDF option sets an arbitrary starting offset. The dependable approaches are physical numbering in one render or a separately rendered body merged after an unnumbered cover.

Does hiding a footer remove the page from the total?

No. A hidden or absent footer does not alter pagination; it only changes what is drawn in the header or footer area.

Should I merge PDFs inside Puppeteer?

Puppeteer creates each PDF but does not provide a built-in merge operation. Use a PDF library such as pdf-lib or an equivalent tool after rendering.

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

Quick Recap

Bestseller No. 1
PDF Extra 2024| Complete PDF Reader and Editor | Create, Edit, Convert, Combine, Comment, Fill & Sign PDFs | Lifetime License | 1 Windows PC | 1 User [PC Online code]
PDF Extra 2024| Complete PDF Reader and Editor | Create, Edit, Convert, Combine, Comment, Fill & Sign PDFs | Lifetime License | 1 Windows PC | 1 User [PC Online code]
READ and Comment PDFs – Intuitive reading modes & document commenting and mark up.; CREATE, COMBINE, SCAN and COMPRESS PDFs
$99.99
Bestseller No. 2
MobiPDF Lifetime - Professional PDF Editor for Windows | Edit, Sign & Convert PDFs | Best Adobe Acrobat Pro Alternative | Lifetime License
MobiPDF Lifetime - Professional PDF Editor for Windows | Edit, Sign & Convert PDFs | Best Adobe Acrobat Pro Alternative | Lifetime License
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.
$99.99

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.