# Puppeteer HTML to PDF: page.pdf() guide

Puppeteer HTML to PDF with page.pdf(): options, margins, headers, footers, page breaks, Docker fixes, or skip the browser with an API.

**Quick answer.** In Puppeteer, load your HTML with `page.setContent(html, { waitUntil: "networkidle0" })` and call `page.pdf({ format: "A4", printBackground: true })`. To get the same Chromium output without installing a browser, send the HTML to an API instead.

```bash
curl -X POST https://html-pdf.com/v1/pdf \
  -H "Authorization: Bearer $HTML_PDF_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Invoice #042</h1>", "format": "A4"}' \
  -o invoice.pdf
```

Puppeteer drives headless Chrome from Node.js, and `page.pdf()` is Chrome's own "Save as PDF" exposed as an API. That makes it the most common way to turn HTML into a PDF in JavaScript.

This guide shows the full DIY setup, the options that matter and the problems you hit in production. It ends with a way to get the same Chromium output without running a browser yourself.

## Install Puppeteer

One npm package gets you started.

```bash
npm install puppeteer
```

The `puppeteer` package downloads a compatible Chrome build on install. If you already have Chrome on the machine, use `puppeteer-core` and pass `executablePath` instead.

## Convert an HTML string to PDF

This script turns an HTML string into an A4 PDF file.

```js
// pdf.mjs (run with: node pdf.mjs)
import puppeteer from "puppeteer";

const html = `
  <html>
    <head><style>
      body { font-family: system-ui, sans-serif; margin: 0 }
      h1 { color: #1f2937 }
    </style></head>
    <body><h1>Invoice #042</h1><p>Total: $120.00</p></body>
  </html>`;

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setContent(html, { waitUntil: "networkidle0" });
  await page.pdf({
    path: "invoice.pdf",
    format: "A4",
    printBackground: true,
    margin: { top: "15mm", right: "15mm", bottom: "15mm", left: "15mm" },
  });
} finally {
  await browser.close();
}
```

`waitUntil: "networkidle0"` waits until images and web fonts referenced by the HTML have loaded. Without it you can get a PDF with missing images or fallback fonts.

## Convert a URL to PDF

To print a live page, navigate to it first and then call `page.pdf()`.

```js
await page.goto("https://example.com", { waitUntil: "networkidle0" });
await page.emulateMediaType("screen"); // use screen CSS instead of @media print
const pdf = await page.pdf({ format: "Letter", printBackground: true }); // returns a buffer if no path
```

Some pages render content with JavaScript after load, such as charts and dashboards. Wait for that content explicitly, for example with `await page.waitForFunction("window.chartsReady === true")`.

## The page.pdf() options you will actually use

These are the options that cover almost every real document.

| Option | What it does |
|---|---|
| `format` | Paper size: `A4`, `Letter`, `Legal`, `A3`, and others |
| `width` / `height` | Custom size, e.g. `"210mm"` |
| `landscape` | Rotate the page |
| `margin` | `{ top, right, bottom, left }` |
| `printBackground` | Include background colors and images (off by default) |
| `preferCSSPageSize` | Let `@page { size: ... }` in your CSS win |
| `scale` | Zoom the rendering, from 0.1 to 2 |
| `pageRanges` | Only output some pages, e.g. `"1-3"` |
| `displayHeaderFooter`, `headerTemplate`, `footerTemplate` | Repeated header and footer |

## Headers, footers and page numbers

This section shows how to repeat a footer with page numbers on every page. Chrome fills special classes inside the templates, namely `pageNumber`, `totalPages`, `date`, `title` and `url`.

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

Watch for two gotchas. The templates don't inherit your page's CSS, so style them inline and set a font size or the text comes out tiny. They also need a top or bottom margin to have room.

More in [headers, footers and page numbers](https://html-pdf.com/pdf-headers-footers-page-numbers).

## Page breaks

You control pagination with CSS, not with Puppeteer options.

```css
.page { break-after: page; }
tr, .card { break-inside: avoid; }
thead { display: table-header-group; } /* repeat table headers on each page */
```

See [HTML to PDF page breaks](https://html-pdf.com/html-to-pdf-page-breaks) for the full set of rules.

## Problems you hit in production

Here is what tends to go wrong once Puppeteer runs on a server.

- **Chrome won't start in Docker.** Slim images lack the shared libraries Chrome needs, and containers often need `--no-sandbox` or a non-root user. You end up maintaining a custom image.
- **Serverless size limits.** Chrome is too big for many function bundles. On AWS Lambda people use `@sparticuz/chromium` with `puppeteer-core`, and still fight cold starts.
- **Memory and zombie processes.** Each browser uses a lot of RAM, and a crash that skips `browser.close()` leaves processes behind. At volume you need a pool or a queue.
- **Fonts.** Servers rarely have the fonts your designers used. Install them in the image or load them with `@font-face`.
- **Security.** Rendering user-supplied HTML or URLs in a browser on your network can expose internal services unless you block private addresses.

None of this is hard on its own, but it is infrastructure you now own.

## Or skip the server: one API call

This section shows the same PDF made with one HTTP request. html-pdf.com runs headless Chromium for you (via Gotenberg), so the output matches what `page.pdf()` gives you, without installing Chrome. The options map almost one to one.

```js
import { writeFile } from "node:fs/promises";

const res = await fetch("https://html-pdf.com/v1/pdf", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.HTML_PDF_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    html,
    format: "A4",
    print_background: true,
    margin: "15mm",
    footer_html:
      '<div style="font-size:9px;width:100%;text-align:center">' +
      'Page <span class="pageNumber"></span> of <span class="totalPages"></span></div>',
    wait_for_expression: "document.fonts.status === 'loaded'",
  }),
});
if (!res.ok) throw new Error(JSON.stringify(await res.json()));
await writeFile("invoice.pdf", Buffer.from(await res.arrayBuffer()));
```

| Puppeteer | html-pdf.com |
|---|---|
| `page.setContent(html)` / `page.goto(url)` | `html` / `url` (public pages) |
| `printBackground` | `print_background` (default `true`) |
| `preferCSSPageSize`, `pageRanges`, `scale` | `prefer_css_page_size`, `page_ranges`, `scale` |
| `headerTemplate` / `footerTemplate` | `header_html` / `footer_html` |
| `emulateMediaType("screen")` | `media: "screen"` |
| `waitForFunction(...)` | `wait_for_expression` |

You also get `max_pages`, which fails a document that runs long without charging you. `response: "url"` returns a 24-hour download link instead of the file. Private network addresses are blocked, and your HTML isn't stored.

Get a key, then open the link we email you to confirm your address. That adds 100 free credits (1 credit = 1 page).

```bash
curl -X POST https://html-pdf.com/v1/signup -H "Content-Type: application/json" -d '{"email": "you@example.com"}'
```

Keep Puppeteer if you already run browsers at scale, need pages behind a login, or need to click through a page before printing. Use the API when PDF generation is the only reason you run Chrome.

More: [HTML to PDF in Node.js](https://html-pdf.com/html-to-pdf-nodejs) · [Chrome headless --print-to-pdf](https://html-pdf.com/headless-chrome-print-to-pdf) · [API docs](https://html-pdf.com/docs)

## FAQ

### How do I convert HTML to PDF with Puppeteer?

Launch a browser, open a page, load the HTML with `page.setContent(html, { waitUntil: "networkidle0" })`, then call `page.pdf({ path: "out.pdf", format: "A4" })`. Close the browser in a `finally` block so Chrome processes don't pile up.

### Why are background colors missing from my Puppeteer PDF?

`page.pdf()` leaves out backgrounds by default. Pass `printBackground: true` to keep background colors and images.

### How do I add page numbers in Puppeteer?

Set `displayHeaderFooter: true` and put `<span class="pageNumber"></span>` and `<span class="totalPages"></span>` in `footerTemplate`. Style the template inline and leave a bottom margin so the footer has room.

### Can I generate PDFs like Puppeteer without installing Chrome?

Yes. html-pdf.com runs headless Chromium for you, so a `POST https://html-pdf.com/v1/pdf` request with your HTML returns the same kind of PDF. You get 100 free credits once, and 1 credit is 1 page.
