Menu
Sign up

Puppeteer PDF: page.pdf() options, page breaks, headers and footers

Generate PDFs from HTML with Puppeteer: page size and margins, backgrounds, page breaks, repeated table headers, page-number footers, fonts and Docker.

Published November 4, 2026 · 5 min read

Puppeteer turns HTML into a PDF with page.pdf(): Chrome prints the page with its print engine, so you get real text, vector graphics, CSS page breaks and running headers and footers. The options that matter are format (or width and height), margin, printBackground, displayHeaderFooter with its templates, and preferCSSPageSize; the rest of the work is print CSS. Everything below ran against Puppeteer 25.7 and Chrome, and the PDFOptions reference lists the full set.

The minimal version

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

await page.setContent(html, { waitUntil: 'load' });
// or: await page.goto('https://example.com/invoice/114', { waitUntil: 'networkidle0' });

const pdf = await page.pdf({
  format: 'A4',
  printBackground: true,
  margin: { top: '20mm', right: '15mm', bottom: '20mm', left: '15mm' },
});

await browser.close();
// pdf is a Uint8Array; write it with fs.writeFile or send it in a response.

Three defaults catch people out:

  • format defaults to letter. In Europe that is the wrong paper; set A4 (or width and height).
  • No backgrounds without printBackground: true. A coloured header comes out white. Puppeteer also notes that colours are adjusted for printing by default — add -webkit-print-color-adjust: exact to elements whose colours must stay exact (page.pdf()).
  • Print media. The page is rendered with @media print styles. If your page hides things in print, or you want the screen look, call page.emulateMediaType('screen') first.

page.pdf() waits for document.fonts.ready by default (waitForFonts: true), so web fonts loaded by the page are in the file; what it cannot wait for is data your page fetches after load — use waitUntil: 'networkidle0' with goto, or wait for a selector that only appears when the content is there.

Paper size: options or CSS

You can set the page size in the call (format: 'A4', or width: '100mm', height: '150mm' for a label) or in CSS with @page { size: 100mm 150mm; margin: 0 }. The CSS size is used only with preferCSSPageSize: true; without it, format wins — in our test the same page came out 100×150 mm with the flag and 210×297 mm (A4) without. Keeping the size in CSS means the template carries its own paper, which is handy when one service prints several document types.

landscape: true turns the paper; scale (0.1 to 2) shrinks or enlarges the rendering — a quick fix for a table that is a little too wide, at the cost of smaller text.

Page breaks

Puppeteer has no page-break option of its own; Chrome follows CSS fragmentation rules:

h2.chapter       { break-before: page; }     /* always start a new page */
tr, .card, figure { break-inside: avoid; }   /* never split these */
thead            { display: table-header-group; } /* repeat on every page */
tfoot            { display: table-footer-group; }

In a 90-row invoice table, thead repeated at the top of each of the three pages the table ran onto, and the "Terms" section with break-before: page started on its own page. If a block is taller than a page, break-inside: avoid cannot be honoured and it splits anyway.

Headers, footers and page numbers

const pdf = await page.pdf({
  format: 'A4',
  printBackground: true,
  margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
  displayHeaderFooter: true,
  headerTemplate: '<span></span>',
  footerTemplate: `
    <div style="font-size:9px; width:100%; text-align:center; color:#666">
      Invoice 2026-114 · page <span class="pageNumber"></span> of <span class="totalPages"></span>
    </div>`,
});

The templates support five classes that Chrome fills in: date, title, url, pageNumber and totalPages. Things to know:

  • They are separate documents. Your page's stylesheet does not apply inside them, so put the styles inline — including font-size: in our test a footer without one came out at under 1 pt.
  • They live in the margin. The header and footer are drawn in the top and bottom margins, so give those margins room; with zero margins our footer printed on top of the last lines of text.
  • An empty header still needs a template. With displayHeaderFooter: true and no headerTemplate, Chrome prints its default header — the date and the document title. Pass '<span></span>' to hide it.
  • Keep them self-contained. Put a small logo in as a data URI rather than relying on the template to fetch a URL.

Running it in production

The PDF part is short; operating Chrome is the real work:

  • Docker. Use an image with Chrome's dependencies and fonts installed — Puppeteer documents an official one — and give it enough shared memory (--shm-size=1gb or --disable-dev-shm-usage). Install the fonts your documents use; a missing font silently falls back to another one.
  • Reuse the browser. Launching Chrome for every request costs startup time and memory each time. Keep one browser and open a page per job, with a queue in front so a burst does not start fifty tabs.
  • Timeouts. page.pdf() has a 30-second default timeout; a very long document can exceed it. Set timeout deliberately and close the page in finally.
  • User content. If the HTML comes from users, the browser can fetch URLs from inside your network. Block private addresses (or run Chrome with no network and inline everything) before you print anything a stranger wrote.

Other options worth knowing

Option What it does
pageRanges: '1-3, 5' Print only some pages; '1' keeps a one-page summary
omitBackground: true No default white background — a PDF with transparency
tagged (default true) Tagged, more accessible PDF (experimental)
outline: true A bookmark outline from headings (experimental)
path: 'out.pdf' Also write the file to disk

When an API is the simpler option — and when it is not

If your documents are one page each — certificates, event tickets, badges, shipping labels, coupons, one-page receipts — you do not need to run Chrome yourself. The Shareglint API keeps the HTML and CSS as a template and returns a one-page vector PDF for each request ("format": "pdf"), the page exactly the template's size, or renders a whole CSV in one batch; 100 renders a month are free and Starter is $9 for 2,000. The PDF generation API page has the details and the certificates use case a worked example.

If your documents run over several pages — invoices with long item lists, reports, contracts — the image API is the wrong tool: it makes exactly one page per PDF, with no page breaks, repeated headers or page-number footers. Keep Puppeteer for those, or use a service built for pagination such as DocRaptor or PDFShift. For images rather than PDFs, the Puppeteer screenshot guide covers page.screenshot() the same way.

FAQ

Why does my Puppeteer PDF have no background colours?

page.pdf() leaves out background graphics unless you pass printBackground: true. Puppeteer's docs also note that colours are adjusted for printing by default; the CSS property -webkit-print-color-adjust: exact keeps them as designed.

How do I add page numbers to a Puppeteer PDF?

Set displayHeaderFooter: true and pass a footerTemplate containing elements with the classes pageNumber and totalPages, for example Page <span class="pageNumber"></span> of <span class="totalPages"></span>. Give the template inline styles, including a font size, and leave room for it in the page margin.

How do I control page breaks?

With print CSS: break-before: page on an element that must start a page, break-inside: avoid on rows or cards that must not split, and display: table-header-group on a thead so it repeats at the top of every page a table runs onto.

Does page.pdf() use my @page size?

Only with preferCSSPageSize: true. Otherwise the format, width and height options win, and format defaults to letter.

Is there an API that does this without running Chrome?

For one-page documents such as certificates, tickets and labels, an HTML template API returns a vector PDF per request. For multi-page documents with page breaks and footers you need Puppeteer or a service built for pagination; the image API described here makes one page per PDF.