ScreenshotNeo

BlogHTML to image & PDF

How to Generate PDFs with Puppeteer

Use Puppeteer’s Page.pdf() to save a web page as a PDF. Learn how to control page size, margins, colors, fonts, headers, and output.

By the ScreenshotNeo team4 October 20268 min read

Use Puppeteer’s Page.pdf() method to print a rendered page to PDF. Navigate to the page, choose whether it should use print or screen styling, then call page.pdf({ path: 'output.pdf', format: 'A4' }). The path option writes the PDF to disk; without it, Puppeteer returns PDF bytes instead. Puppeteer’s PDF guide describes this as the method to use for printing PDFs.

1. Install Puppeteer and create a PDF

In a new project, install Puppeteer:

npm install puppeteer

Create generate-pdf.js with this runnable example. It launches the bundled browser, navigates to a page, saves an A4 PDF, and closes the browser even if navigation or PDF generation fails.

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: 'example.pdf', format: 'A4' });
} finally {
  await browser.close();
}

Run it with:

node generate-pdf.js

For projects using CommonJS, replace the import with const puppeteer = require('puppeteer');. The top-level await example requires Node.js to treat the file as an ES module, for example by setting "type": "module" in package.json, or by using a .mjs file.

Page.pdf() returns a Promise<Uint8Array>. Pass a path to write the PDF to a file. If you omit path, use the returned bytes yourself; Puppeteer does not write them to disk through that option.

2. Choose print CSS or screen CSS

PDF generation uses the print CSS media type. This means print-specific rules such as @media print apply. To generate a PDF using the page’s screen styling, set the media type before calling page.pdf():

await page.emulateMediaType('screen');
await page.pdf({ path: 'screen-style.pdf', format: 'A4' });

Choose print styling for documents designed for paper or export, such as reports and invoices. Choose screen styling when the PDF should resemble the rendered website. A screen-style PDF can include navigation or other web interface elements that print CSS would hide.

3. Set paper size, orientation, and margins

Puppeteer’s documented default format is Letter. Set a format explicitly when the output needs a known page size. Common choices include:

Format Dimensions Typical use
Letter 8.5 × 11 in (21.59 × 27.94 cm) Common US documents
Legal 8.5 × 14 in (21.59 × 35.56 cm) Longer US documents
A4 8.2677 × 11.6929 in (21 × 29.7 cm) Common international documents

Use landscape: true for landscape orientation. Set margins explicitly when predictable whitespace matters. Puppeteer applies no margins when the margin option is omitted.

await page.pdf({
  path: 'report.pdf',
  format: 'A4',
  landscape: false,
  margin: {
    top: '18mm',
    right: '16mm',
    bottom: '18mm',
    left: '16mm'
  }
});

You can specify width and height instead of a named format. If you set format, it takes precedence over width and height.

Let the document’s CSS control page dimensions

A site can declare page dimensions and margins with CSS @page. Set preferCSSPageSize: true to prefer those CSS dimensions:

await page.pdf({
  path: 'css-sized.pdf',
  preferCSSPageSize: true
});

By default, preferCSSPageSize is false, so the content is scaled to fit the paper size selected through PDF options. For predictable output, choose one source of page sizing: set format or dimensions in Puppeteer, or use CSS @page with preferCSSPageSize: true.

4. Preserve colors and print backgrounds

By default, Puppeteer modifies colors for printing, and it does not print background graphics. Set printBackground: true to include backgrounds. If exact CSS colors matter, add -webkit-print-color-adjust: exact to the relevant styles:

await page.addStyleTag({
  content: `
    html {
      -webkit-print-color-adjust: exact;
    }
  `
});

await page.pdf({
  path: 'branded-report.pdf',
  format: 'A4',
  printBackground: true
});

Use this when backgrounds or brand colors carry information. Backgrounds can consume more ink when printed, so omit them when a plain document is preferable.

5. Control page ranges, headers, and footers

To export only selected pages, set pageRanges. An empty string means all pages. The documented range syntax supports comma-separated pages and ranges, such as 1-5, 8, 11-13.

await page.pdf({
  path: 'excerpt.pdf',
  format: 'A4',
  pageRanges: '1-5, 8'
});

Header and footer templates require displayHeaderFooter: true. Templates can include the documented classes date, title, url, pageNumber, and totalPages:

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

Reserve enough top and bottom margin for the templates. Otherwise, header or footer content may overlap the page body or be clipped.

6. Fonts, readiness, and timeouts

Puppeteer waits for fonts to load before generating the PDF by default (waitForFonts: true). If font waiting stalls when a page is in the background, the API reference notes that bringing the page to the front may be required:

await page.bringToFront();
await page.pdf({ path: 'report.pdf', waitForFonts: true });

PDF generation has a documented default timeout of 30 seconds. Set timeout to a larger number for pages that need more time, or 0 to disable that timeout. Disabling it can leave a job waiting indefinitely, so prefer a finite limit in services that process user requests.

await page.pdf({
  path: 'large-report.pdf',
  format: 'A4',
  timeout: 60000
});

Navigation readiness is separate from PDF generation readiness. The example waits for networkidle2 before printing, but pages with long-running network requests or delayed content may need a different navigation condition or an explicit wait for the content the document requires. Avoid assuming that a successful navigation means every application-specific chart or data request has finished.

7. Return PDF bytes instead of saving a file

When a server needs to send a PDF in an HTTP response or store it through another library, omit path and consume the returned bytes:

const pdfBytes = await page.pdf({ format: 'A4' });
// For example, pass pdfBytes to your storage or HTTP response code.

The exact response or storage code depends on your server framework. The Puppeteer API returns a Uint8Array; it does not choose a destination when no path is supplied.

8. Relevant PDF options at a glance

Option What it controls Documented default or note
path File destination Omit to receive bytes without writing through this option
format Named paper size Letter; takes precedence over width and height
width, height Paper dimensions Use instead of a named format when needed
landscape Page orientation false
margin Whitespace around content No margins when omitted
printBackground Background graphics false
scale Content scaling 1; allowed range 0.1–2
preferCSSPageSize Whether CSS @page sets paper dimensions false; content is scaled to fit the selected paper size
pageRanges Pages to include Empty string means all pages
displayHeaderFooter Whether header and footer templates are shown Must be enabled for templates
headerTemplate, footerTemplate Custom page furniture Can use the documented template classes
waitForFonts Wait for fonts before printing true
timeout PDF operation timeout in milliseconds 30,000 ms; 0 disables the timeout
tagged Tagged PDF output true in the current API reference
outline PDF outline generation Experimental and defaults to false

Tagged output and the experimental outline option describe API output settings; they do not guarantee how a particular PDF viewer or assistive technology will present the document. Puppeteer’s current API reference is version 25.12.0, and defaults or experimental labels may change, so check the PDFOptions reference when updating an implementation.

9. Troubleshooting common PDF problems

Symptom Likely cause What to do
The PDF has the wrong page size format overrides width and height, or CSS page sizing is not preferred Choose one sizing authority. Use a format or dimensions, or set CSS @page and preferCSSPageSize: true.
Backgrounds are missing printBackground defaults to false Set printBackground: true.
Colors look faded or different Print color adjustment changes output by default Apply -webkit-print-color-adjust: exact to the necessary elements.
The PDF looks unlike the browser page Page.pdf() uses print media styles Call page.emulateMediaType('screen') before generating the PDF, or adjust the page’s print CSS.
Fonts are missing or delayed A font may not have loaded, or background-page font waiting may stall Keep waitForFonts: true, verify font availability, and bring the page to the front if needed.
PDF generation times out The operation exceeded its 30-second default Investigate slow or complex page content, then set an appropriate finite timeout.
Header or footer is absent Templates require displayHeaderFooter: true Enable it and provide adequate margins for the template.
The PDF was not saved No path was provided Supply path, or write the returned Uint8Array using your application’s file or response code.
Some page content is blank or incomplete Application content may load after navigation reaches its selected wait condition Wait for the specific content or state needed by the document before calling page.pdf().

10. Performance, reliability, and cost considerations

PDF generation renders a page in a browser, so the work depends on the page and browser workload. The official example is an API usage example, not a performance or reliability benchmark; there is no benchmark in the cited documentation to use as a general runtime estimate.

  • Keep browser cleanup in a finally block. This ensures the browser is closed after both success and errors.
  • Wait for the content the PDF actually needs. Network-idle navigation can be useful, but application-specific content may need an explicit readiness condition.
  • Use finite timeouts for request handling. A timeout helps keep a slow or stalled operation from occupying a request indefinitely.
  • Limit output to needed pages. Use pageRanges when the reader only needs selected pages.
  • Account for browser operation in your own deployment. The Puppeteer API reference documents options, not hosting or infrastructure prices; runtime cost depends on where and how you run the browser.

11. Or skip the browser setup

If you need a PDF from a public URL without managing Puppeteer and a browser, ScreenshotNeo provides a one-call PDF endpoint. See the ScreenshotNeo API docs for its API.

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

ScreenshotNeo accepts cookie and consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. See ScreenshotNeo for the service details.

Sign up free for 1,000 screenshots a month, with no card required.

12. FAQ

Does Puppeteer wait for web fonts before creating the PDF?

Yes. The current PDF option waitForFonts defaults to true. A background page may need to be brought to the front if font waiting stalls.

Can I create only pages 1 through 3?

Yes. Set pageRanges: '1-3' in the PDF options.

Can CSS choose the PDF paper size?

Yes. Define the size using CSS @page and set preferCSSPageSize: true.

Does Puppeteer add page numbers automatically?

No. Enable displayHeaderFooter and add a footer template containing the pageNumber and, if useful, totalPages classes.

Official references