# 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.