How to Capture a Webpage as a PDF with Puppeteer
Generate PDFs with Puppeteer using the right print styles, page size, margins, and readiness checks. Includes runnable Node.js code and troubleshooting.
To save a webpage as a PDF with Puppeteer, launch a browser, navigate to the page, wait until the content you need is ready, then call page.pdf(). Puppeteer uses print CSS by default. Set the paper size, margins, and background behavior explicitly so the output matches your needs.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.pdf({
path: 'page.pdf',
format: 'A4',
printBackground: true,
preferCSSPageSize: true,
});
} finally {
await browser.close();
}
Install Puppeteer with npm install puppeteer and run this as an ES module, for example in a file named capture.mjs with node capture.mjs. The package normally installs its supported browser. Puppeteer documents that it is only guaranteed to work with its bundled browser, so use that browser unless you have checked compatibility for your deployment. See the PDF generation guide and the PDFOptions reference.
1. Install and run a basic PDF capture
For a simple capture, use an explicit output path and paper format. The example waits for networkidle2, which is a useful starting point for pages whose main content loads during navigation. It is not a universal signal that every application has finished rendering.
import puppeteer from 'puppeteer';
const url = process.argv[2] ?? 'https://example.com';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto(url, {
waitUntil: 'networkidle2',
timeout: 60_000,
});
await page.pdf({
path: 'page.pdf',
format: 'A4',
printBackground: true,
margin: {
top: '12mm',
right: '12mm',
bottom: '12mm',
left: '12mm',
},
});
console.log('Saved page.pdf');
} finally {
await browser.close();
}
Use waitUntil: 'load' if you want the browser load event; this is the default navigation lifecycle event. You can also use domcontentloaded or networkidle0. Choose based on the page, then add an application-specific readiness check where needed. See WaitForOptions.
2. Wait for the page content you actually need
A page can fire its load event before a client-side application has finished rendering, and network-idle waiting may be unsuitable for sites with analytics, polling, or long-lived requests. If the PDF must include a particular component, wait for its selector instead of assuming navigation alone means the content is ready.
await page.goto('https://example.com/report', {
waitUntil: 'domcontentloaded',
timeout: 60_000,
});
await page.waitForSelector('[data-report-ready="true"]', {
timeout: 30_000,
});
await page.pdf({ path: 'report.pdf', format: 'A4', printBackground: true });
Replace the selector with a condition the application sets only when the content is ready. For a site without a usable readiness marker, a short delay can help with a known animation or delayed render, but it is less reliable than waiting for a real condition. Puppeteer also provides page.waitForNetworkIdle(); it waits at least the configured idle time. Fonts are awaited by PDF generation by default through waitForFonts: true.
3. Choose print CSS or screen CSS
page.pdf() renders using the print CSS media type by default. This means print-specific rules and browser print layout affect the PDF. If you need the page’s screen stylesheet, emulate screen media before generating the PDF.
await page.emulateMediaType('screen');
await page.pdf({
path: 'screen-layout.pdf',
format: 'A4',
printBackground: true,
});
For a document intended to print, define print styles in the page itself. For example:
@media print {
nav,
.cookie-banner,
.no-print {
display: none !important;
}
.report {
width: auto;
color: #111;
}
}
@page {
size: A4 portrait;
margin: 12mm;
}
Printed colors may be adjusted by the browser. For color-sensitive output, the page’s CSS can request exact colors with -webkit-print-color-adjust: exact. Check the resulting PDF because page CSS and browser print behavior both affect the output. See Puppeteer’s Page reference.
4. Set page size, orientation, margins, and page range
Set format to a standard paper size such as A4 or Letter, or set width and height. If format is supplied, it takes priority over width and height. Set landscape: true for landscape orientation.
await page.pdf({
path: 'landscape.pdf',
format: 'A4',
landscape: true,
margin: {
top: '10mm',
right: '10mm',
bottom: '10mm',
left: '10mm',
},
pageRanges: '1-3',
printBackground: true,
});
Margins accept CSS length values. Use pageRanges when you only need selected pages. For a custom size, omit format and provide both dimensions, such as width: '210mm' and height: '297mm'.
If the page declares an @page size, set preferCSSPageSize: true to give that CSS size priority. Its default is false; in that mode the content is scaled to fit the paper size configured in PDF options.
5. Include backgrounds and add headers or footers
Background graphics are omitted by default. Enable printBackground: true when the PDF needs background colors or images. Header and footer templates require displayHeaderFooter: true.
await page.pdf({
path: 'branded-report.pdf',
format: 'Letter',
printBackground: true,
displayHeaderFooter: true,
headerTemplate: '<div style="font-size:8px; width:100%; text-align:center;">Report</div>',
footerTemplate: '<div style="font-size:8px; width:100%; text-align:center;"><span class="pageNumber"></span> / <span class="totalPages"></span></div>',
margin: { top: '18mm', bottom: '18mm', left: '12mm', right: '12mm' },
});
Templates can use placeholders for date, title, URL, page number, and total pages. Reserve enough top and bottom margin for the header and footer; otherwise they may overlap page content.
6. Save a file, return bytes, or stream the PDF
With path, Puppeteer writes the PDF to a file. Without path, page.pdf() returns PDF bytes as a Uint8Array, which is useful when an application stores the result or returns it to a caller.
const pdfBytes = await page.pdf({ format: 'A4', printBackground: true });
await import('node:fs/promises').then(({ writeFile }) => writeFile('page.pdf', pdfBytes));
For a readable stream, use page.createPDFStream(). This can fit applications that pipe output onward rather than first handling a complete byte array. See the Page API for the methods and current signatures.
7. Complete options checklist
| Need | Option or method | Behavior to account for |
|---|---|---|
| Paper geometry | format, width, height, landscape |
format takes priority over width and height. |
| Page spacing | margin |
Specify CSS lengths for each side when layout precision matters. |
| Subset of pages | pageRanges |
Use a page range such as 1-3. |
| Background graphics | printBackground |
Defaults to false; enable it when backgrounds are part of the design. |
| Honor CSS page size | preferCSSPageSize |
Defaults to false; true gives @page sizing priority. |
| Header and footer | displayHeaderFooter, headerTemplate, footerTemplate |
Enable display and reserve margin for template content. |
| Wait for fonts | waitForFonts |
Defaults to true and waits for document.fonts.ready. |
| Return destination | path, returned bytes, createPDFStream() |
Choose file output, in-memory bytes, or stream for the surrounding application. |
The documented defaults include Letter format, printBackground: false, and waitForFonts: true. Make output requirements explicit instead of relying on defaults. Option names and defaults can change across Puppeteer versions, so check the API reference matching your installed package.
8. Troubleshooting common PDF problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Background colors or images are missing | PDF background printing is disabled by default. | Set printBackground: true. |
| The PDF layout differs from the browser view | PDF uses print CSS by default, or CSS @page rules differ from configured paper options. |
Use page.emulateMediaType('screen') for screen styles; use preferCSSPageSize: true when CSS page dimensions should win. |
| Content is missing or half-rendered | Navigation completed before application rendering or delayed content finished. | Wait for a meaningful selector or application readiness condition. Use network-idle only when the page’s request pattern permits it. |
| Fonts look wrong or are absent | Font files have not loaded, or the page is using a background tab condition. | Keep waitForFonts: true (the default), ensure the page can load its fonts, and bring a background page to the front if font readiness requires it. |
| Colors look washed out compared with screen | Print color adjustment changes printed colors by default. | Set -webkit-print-color-adjust: exact in the page’s print CSS where exact colors are required. |
| Navigation times out on a busy site | Persistent analytics, polling, or streaming requests prevent a network-idle condition. | Use a less restrictive navigation event such as domcontentloaded, then wait for the required selector. Set a timeout appropriate to the environment. |
| PDF output fails in deployment | The configured browser does not match the Puppeteer runtime or is unavailable in the deployment image. | Deploy Puppeteer’s bundled browser or verify compatibility and installation for the separately configured browser. |
| Header or footer overlaps content | There is not enough page margin for the template. | Increase top or bottom margin and simplify the template. |
9. Performance, reliability, and cost considerations
PDF generation requires launching or reusing a browser, loading the page, waiting for its content, and rendering every requested page. For repeated captures in one process, reusing a browser can avoid launching a new browser for every URL; create a page per job and close pages when finished. Always close the browser in a finally block for one-off scripts so errors do not leave the process running.
For reliability, set navigation and selector timeouts, prefer explicit readiness conditions, and test pages with the same browser runtime and fonts used in deployment. Avoid treating a single network-idle setting as proof that arbitrary dynamic content is complete. For cost, Puppeteer itself is an open-source library, but running browser jobs consumes your own compute, memory, storage, and operational time; size the environment for the pages and concurrency you handle. The sources in this guide provide no general benchmark or per-capture cost figure.
10. Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server for developers. For a PDF, call its PDF endpoint with the target URL and your access key; see the ScreenshotNeo docs for request options.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-d format=pdf \
-o page.pdf
In a Puppeteer how-to, you can keep the browser workflow above when you need direct browser control, custom page logic, or a local capture pipeline. ScreenshotNeo is an alternative when you want a single API request instead of managing browser installation and execution. It accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; those steps can each be turned off. Bot checks, blank pages, and failed loads are never billed, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month with no card.
11. FAQ
Can Puppeteer capture a page that requires authentication?
It can, if your script establishes the required page state before generating the PDF. Add the login or session setup to the browser workflow and wait for a post-authentication selector before printing.
Does Puppeteer create a PDF from the whole page?
page.pdf() prints the page according to its print layout and pagination. Prepare print CSS for the content and page breaks you want in the document.
Why is my PDF not the same size as the webpage?
A PDF has paper dimensions and pagination. Choose a paper size or CSS @page dimensions and decide whether to use print or screen media.
Should I use a returned byte array or a stream?
Use returned bytes when the PDF is small enough for your normal in-memory workflow. Use createPDFStream() when a readable stream better fits how your application sends or stores output.


