# 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": "
Invoice #042
", "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 = `
Invoice #042
Total: $120.00
`;
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: "",
footerTemplate: `
Page of
`,
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:
'' +
'Page of
',
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 `` and `` 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.