# 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 2026-11-04 · https://shareglint.com/en/guides/puppeteer-pdf

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](https://pptr.dev/api/puppeteer.pdfoptions) lists the full set.

## The minimal version

```javascript
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()`](https://pptr.dev/api/puppeteer.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:

```css
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

```javascript
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](https://shareglint.com/en/pdf-generation-api) page has the details and the [certificates use case](https://shareglint.com/en/use-cases/certificates) 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](https://shareglint.com/en/guides/puppeteer-screenshot) 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.
