ScreenshotNeo

BlogHTML to image & PDF

How to Show a Puppeteer PDF Footer Only on the Last Page

Puppeteer has no documented last-page-only footer switch. Learn when to use document flow, how to reserve footer space, and how to handle a footer fixed to the final page.

By the ScreenshotNeo team30 September 20269 min read

How to Show a Puppeteer PDF Footer Only on the Last Page

Short answer: Puppeteer’s footerTemplate does not have a documented option to show its content only on the last page. It is a repeating print footer template, and its documented page placeholders are pageNumber and totalPages. For content that only needs to come after the document, put it at the end of the HTML. If it must sit at the bottom edge of the final PDF sheet, determine the final pagination and add it in a separate, validated step.

The distinction matters: “at the end” describes reading order; “at the bottom of the last page” describes physical placement. Pagination changes with content, fonts, print CSS, margins, and browser versions. A robust implementation must decide which of these two outcomes it needs.

Set displayHeaderFooter: true to enable header and footer templates. The documented default is false. Set footerTemplate to a string of HTML; special classes expose the current page and total page count. The API reference does not document a conditional such as “render only when pageNumber equals totalPages.” See the Puppeteer PDFOptions reference.

This means a template such as the following is a repeating footer, not a last-page footer:

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

The placeholder classes provide page information for display. The docs do not promise that arbitrary JavaScript or page-dependent conditional CSS in the template will run, so do not rely on a clever template expression as a last-page feature.

2. Choose the placement that matches your requirement

Requirement Recommended direction What to expect
A closing note, signature, or summary follows the body Append it to the HTML document flow and style it for print It appears after the content; it may land partway down the final sheet.
A block must align with the bottom margin of the final sheet only Render or determine pagination, then apply the block in a separate step More control, but pagination changes require validation or recalculation.
The same footer should appear on every page Use footerTemplate with displayHeaderFooter Puppeteer’s built-in template behavior fits this case.
End-of-document content follows the body; a bottom-aligned final-page footer needs pagination-aware placement.
End-of-document content follows the body; a bottom-aligned final-page footer needs pagination-aware placement.

3. Runnable example: content at the end of the document

For most reports, a closing block in normal flow is the simplest and most reliable solution. Puppeteer’s page.pdf() uses print media, so print styles and page breaks affect the output. The example below writes a PDF with a closing block after the report content and a standard repeating page-number footer.

  1. Install Puppeteer in a Node.js project: npm install puppeteer.
  2. Save this as make-pdf.mjs.
  3. Run node make-pdf.mjs.
import puppeteer from 'puppeteer';

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: 20mm 16mm 24mm; }
          body { font: 11pt/1.5 Arial, sans-serif; color: #222; }
          h1 { font-size: 22pt; }
          .closing { margin-top: 2em; break-inside: avoid; }
          .closing h2 { font-size: 14pt; }
          @media print {
            .new-page { break-before: page; }
          }
        </style>
      </head>
      <body>
        <h1>Quarterly report</h1>
        <p>Replace this sample with the report body. Long content will paginate according to print CSS.</p>
        <section class="closing">
          <h2>Sign-off</h2>
          <p>Prepared by the reporting team.</p>
        </section>
      </body>
    </html>
  `);

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

The .closing block follows the report in document order. break-inside: avoid asks the print layout engine to keep the block together when possible; it cannot guarantee that a block larger than the available page area will fit. If the sign-off must start on a fresh sheet, add a class with break-before: page to that section, accepting that a short report may then have a mostly empty preceding page.

Notice that the margin appears both in @page and in the PDF options. Choose one consistent page geometry and inspect the generated output. The PDF margin is especially important for a repeating template: content placed too close to the sheet edge can be clipped or overlap body content. Puppeteer’s print behavior and PDF options are described in Page.pdf().

4. If it must be fixed to the final page’s bottom

A bottom-aligned block on only the final sheet is not equivalent to content at the end of HTML flow. The page count is an output of layout. To target the last sheet reliably, your application needs a separate strategy that knows where the final page is after pagination.

A two-stage workflow can target the last sheet after the rendered page count is known.
A two-stage workflow can target the last sheet after the rendered page count is known.

Two-stage rendering approach

  1. Render the body without the special final-page block. Use the same print CSS, fonts, paper size, margins, and content that production will use.
  2. Determine the resulting page count and final page geometry. Use a PDF inspection or manipulation library appropriate to your stack. Do not infer the final page solely from source HTML height: print layout can split content across pages.
  3. Apply the block to the final page. Add it as a page overlay at coordinates calculated from the chosen page size and bottom margin, or produce a second render with a reserved final-page area and combine the results.
  4. Validate the merged PDF. Check the last page’s alignment and check that earlier pages contain no copy of the block.
  5. Repeat validation when content or rendering configuration changes. A small content change can add or remove a page and move the target.

This is an engineering pattern inferred from the documented API limit, not a Puppeteer last-page switch or a universal tested recipe. The precise overlay code depends on the PDF library and whether the final block has fixed or variable height. Keep the body render and overlay tied to the same page size and margins, and make the operation repeatable so retries do not add the overlay twice.

Layout considerations for the overlay

  • Reserve a bottom region in the body layout if the final content must never cover report text.
  • Use the PDF’s actual page dimensions and coordinate convention; many PDF libraries measure from a different origin than CSS does.
  • Account for font embedding, line wrapping, and the final block’s maximum height.
  • Decide what happens when the final block cannot fit: shrink within a defined limit, move it to a new page, or fail generation with a useful error.
  • Test one-page, multi-page, near-page-break, and content-that-grows cases.

5. Print options and layout details that affect the result

These settings are the ones most likely to affect footer visibility or the final page position:

Setting Effect Practical note
displayHeaderFooter Enables the header/footer template areas Documented default is false; set true when using a template.
footerTemplate Supplies print-footer HTML Use for repeating content; documented placeholders include page number and total pages.
headerTemplate Supplies the print header HTML Set an empty template if you need no visible header while enabling footer output.
format Chooses a paper format The documented default is Letter. Set the intended format explicitly for repeatable geometry.
margin Sets the PDF page margins Reserve enough bottom room for any repeating footer; inspect for clipping.
printBackground Includes background graphics Enable if the design depends on background colors or images.
preferCSSPageSize Lets CSS page size take precedence when configured Use consistently with @page; avoid conflicting CSS and API dimensions.
pageRanges Limits output to specified page ranges It changes which pages are emitted; it is not a way to target a last-page footer in the original document.
scale Scales printed content Scaling can change pagination and apparent footer size.

Confirm option names and types against the Puppeteer version installed in your project. The official API reference is versioned and may change.

6. Troubleshooting

Symptom Likely cause Fix
No footer appears displayHeaderFooter is unset or false, or template content is not visible Set it to true, add simple visible HTML, and inspect the PDF at normal zoom.
Footer is clipped or overlaps the body Bottom margin is too small for the template or body content reaches the footer region Increase bottom margin and recheck CSS page margins and API margins together.
Template text is tiny Template styles or browser print sizing make the content hard to read Specify a readable font size and width inline in the template; verify in the generated PDF.
Footer appears on every page That is the repeating-template behavior Move end-only content into HTML flow, or use a separate final-page rendering/overlay stage.
Closing block moves to an extra page The block cannot fit in remaining printable space Allow it to split, force a page break intentionally, or reserve space; test the resulting page count.
Final overlay lands on the wrong page Pagination changed between the body render and the overlay calculation Use the exact same content and rendering configuration, calculate from the produced PDF, and validate after merging.
Print layout differs from the browser view page.pdf() renders using print media, which can activate different CSS Review @media print, @page, font loading, and page-break rules.

A Puppeteer community issue contains reports of footer visibility problems tied to margins and template legibility. Treat those as troubleshooting reports rather than API guarantees: Puppeteer issue #5345.

7. Performance, reliability, and cost

One ordinary PDF render is simpler and cheaper to operate than rendering twice and merging, because it uses fewer browser operations and has fewer layout states to keep consistent. A final-page overlay adds work: inspect page count, compute placement, merge or modify the PDF, and validate the result. For high-volume generation, track render duration, failures, output page count, and retry rates in your own system; no fixed timing or throughput is guaranteed by the Puppeteer API.

For reliability, make generation deterministic where possible: pin the Puppeteer version, use stable fonts, wait for required content and fonts before creating the PDF, set paper size and margins explicitly, and retain representative PDFs for visual regression review. Retry transient browser failures with limits, and avoid applying a post-processing overlay more than once. Validate documents with unusually long tables, images, and content near page boundaries.

8. Or skip the browser setup

If your goal is a screenshot or PDF of a web page rather than custom Puppeteer layout logic, ScreenshotNeo provides a website screenshot API. For custom last-page-only PDF content, Puppeteer’s print layout and a post-processing step still give you more direct control. ScreenshotNeo’s API can handle a straightforward page capture in one request; see the 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
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)
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(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free account.

9. FAQ

Can I check pageNumber against totalPages in the template?

The documented classes expose those values for the template, but the API reference does not document a supported conditional-rendering mechanism. Do not make production behavior depend on undocumented template scripting.

Will an end-of-document block always sit at the bottom of the last page?

No. Normal HTML flow places it after preceding content, wherever that content ends on the final sheet. Bottom alignment requires a separate layout or PDF processing strategy.

Does Puppeteer use screen CSS for the PDF?

page.pdf() uses print CSS media. Set and test print-specific styles for the actual PDF output.

Use it if the signature should repeat on every page. For a single closing signature, put it in the document flow or target the final page in a separate validated step.