ScreenshotNeo

BlogHTML to image & PDF

Puppeteer PDF Options: A Practical Guide

Set paper size, margins, orientation, colors, page ranges, and output behavior with Puppeteer’s PDF API, with runnable examples and troubleshooting.

By the ScreenshotNeo team4 October 20269 min read

page.pdf(options) generates a PDF using print CSS by default. Set format or dimensions for paper geometry, margin for whitespace, landscape for orientation, and printBackground: true to include background graphics. Use preferCSSPageSize: true when the page’s CSS @page rules should control the paper size. The examples below follow Puppeteer 25.12.0; check your installed version when option support or rendered behavior matters. See the official PDFOptions reference.

1. Generate a PDF with Puppeteer

Install Puppeteer in a Node.js project, save this as make-pdf.mjs, then run node make-pdf.mjs. Puppeteer downloads a compatible browser during installation by default.

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',
    landscape: false,
    printBackground: true,
    margin: {
      top: '16mm',
      right: '14mm',
      bottom: '16mm',
      left: '14mm',
    },
    preferCSSPageSize: false,
    scale: 1,
    waitForFonts: true,
  });
} finally {
  await browser.close();
}

page.pdf() returns a Uint8Array as well as writing the file when path is supplied. Omit path when you want to send or store the bytes yourself. For options and type definitions, consult the Puppeteer PDFOptions documentation.

2. Choose who controls paper size

There are three common ways to set page geometry. Choose one as the source of truth to avoid surprising scaling.

Approach Settings Behavior Good fit
Named paper format: 'A4', 'Letter', etc. format takes precedence over width and height. Standard paper sizes.
Explicit dimensions width and height Accept numbers or CSS length strings with units. Custom labels, receipts, or fixed-size documents.
CSS page rules @page { size: ... } and preferCSSPageSize: true CSS page size gets priority over API paper dimensions. Print stylesheets already define document geometry.

If CSS page size is not preferred, Puppeteer scales page content to fit the selected paper size. The default for preferCSSPageSize is false. The default format is letter, so specify your intended paper size explicitly for reproducible output.

await page.setContent(`
  <style>
    @page { size: A4 landscape; margin: 12mm; }
    h1 { break-after: avoid; }
  </style>
  <h1>Quarterly report</h1>
  <p>The CSS page rule controls the PDF paper geometry.</p>
`);

await page.pdf({
  path: 'report.pdf',
  preferCSSPageSize: true,
  printBackground: true,
});

Do not assume CSS @page dimensions win without enabling preferCSSPageSize. If dimensions appear unexpectedly scaled, check whether format is set, whether CSS page size is preferred, and whether both API and CSS rules are defining geometry.

3. Set margins and orientation

landscape defaults to false. Set it to true to request landscape output. The margin object accepts optional top, bottom, left, and right values; each can be a number or a string with units. Margins are unset by default.

await page.pdf({
  path: 'wide-report.pdf',
  format: 'A4',
  landscape: true,
  margin: {
    top: '0.5in',
    right: '0.4in',
    bottom: '0.5in',
    left: '0.4in',
  },
});

Use explicit units such as mm, in, or px for readability. With no margin option, the PDF has no API-defined margins, though the document’s own CSS and layout still affect where content appears.

4. Control print CSS, colors, and backgrounds

Puppeteer uses print media for page.pdf(). Print styles can hide navigation, change layout, or alter colors relative to a normal browser view. To use screen media instead, call page.emulateMediaType('screen') before generating the PDF.

await page.emulateMediaType('screen');
await page.pdf({
  path: 'screen-styled.pdf',
  format: 'Letter',
  printBackground: true,
});

Printed background graphics are omitted by default. Set printBackground: true to include them. The default is false. For CSS-controlled print colors, use -webkit-print-color-adjust: exact in the page stylesheet, for example:

<style>
  .brand-panel {
    background: #173d70;
    color: white;
    -webkit-print-color-adjust: exact;
  }
</style>

omitBackground: true hides the default white page background and permits transparent PDFs; it defaults to false. This is distinct from printBackground, which controls whether page background graphics are printed. Verify transparency and color behavior in the PDF viewer or downstream workflow where it matters.

5. Select pages and adjust scale

pageRanges selects pages using a string such as '1-5, 8, 11-13'. Its default is the empty string, which prints all pages. scale defaults to 1 and accepts values from 0.1 through 2.

await page.pdf({
  path: 'selected-pages.pdf',
  format: 'A4',
  pageRanges: '1-3, 6',
  scale: 0.95,
});

Page numbers refer to the paginated PDF output, not DOM elements or sections. If your range selects no pages or exceeds the document’s page count, check the actual pagination and simplify the range before adding it to a production job.

6. Add headers and footers

Headers and footers are disabled unless displayHeaderFooter: true. Provide HTML through headerTemplate and footerTemplate. Puppeteer supports special classes for injected date, title, url, pageNumber, and totalPages values.

await page.pdf({
  path: 'numbered.pdf',
  format: 'A4',
  displayHeaderFooter: true,
  margin: { top: '20mm', bottom: '20mm' },
  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>',
});

Reserve enough top and bottom margin for the templates or they can overlap page content. Keep the template markup self-contained and validate it with the Puppeteer version and browser protocol you deploy.

7. Configure output and execution

Option Documented behavior and default When to set it
path Optional disk output; omitted means the PDF is not written to disk. Relative paths resolve from the current working directory. Set a deterministic path for a file, or omit it and use returned bytes.
timeout Milliseconds; default 30,000. Set to 0 to disable this timeout. Allow longer rendering for complex pages. The page default timeout can also be changed with Page.setDefaultTimeout().
waitForFonts Defaults to true; waits for document.fonts.ready. Keep enabled when output depends on web fonts. A background page might need Page.bringToFront().
outline Requests a document outline; experimental, default false. Use only when you need an outline and have checked support in your deployed version.
tagged Requests an accessible tagged PDF; experimental, documented default true. Review the output and compatibility requirements for your accessibility workflow.

A timeout of zero removes this particular PDF-operation timeout; it does not make navigation, resource loading, or the overall job reliably bounded. Set sensible timeouts at the navigation and job levels too.

8. Complete examples in cURL, Python, and Node.js

Puppeteer is a Node.js library, so the direct Puppeteer example is the Node.js code above. For a service that exposes PDF generation over HTTP, clients can request the resulting bytes using cURL or Python. These client examples require an endpoint you control that runs Puppeteer; they are not commands to the Puppeteer library itself.

cURL client for your own PDF endpoint

curl --fail --silent --show-error \
  -X POST 'http://localhost:3000/pdf' \
  -H 'Content-Type: application/json' \
  --data '{"url":"https://example.com","format":"A4"}' \
  -o page.pdf

Python client for your own PDF endpoint

import requests

response = requests.post(
    'http://localhost:3000/pdf',
    json={'url': 'https://example.com', 'format': 'A4'},
    timeout=90,
)
response.raise_for_status()
with open('page.pdf', 'wb') as output:
    output.write(response.content)

Minimal Node.js PDF service

This service accepts a URL and returns a PDF. In production, validate and restrict destinations to prevent requests to internal services, enforce request limits, and run browser processes with appropriate isolation.

import express from 'express';
import puppeteer from 'puppeteer';

const app = express();
app.use(express.json({ limit: '32kb' }));

app.post('/pdf', async (req, res) => {
  const { url, format = 'A4' } = req.body ?? {};
  if (typeof url !== 'string' || !/^https?:\/\//i.test(url)) {
    return res.status(400).json({ error: 'url must be an http or https URL' });
  }

  let browser;
  try {
    browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
    const pdf = await page.pdf({ format, printBackground: true, timeout: 30000 });
    res.type('application/pdf').send(Buffer.from(pdf));
  } catch (error) {
    res.status(502).json({ error: 'PDF generation failed' });
  } finally {
    await browser?.close();
  }
});

app.listen(3000);

9. WebDriver BiDi support differs

The general PDFOptions API and WebDriver BiDi do not document the same option set. Puppeteer’s BiDi support page lists format, height, landscape, margin, pageRanges, printBackground, scale, and width for Page.pdf() and Page.createPDFStream(). If you depend on header/footer templates, CSS page-size preference, tagged output, or other options outside that set, verify the backend and version you use. See the official WebDriver BiDi support notes.

10. Troubleshooting

Symptom Likely cause Fix
PDF uses Letter despite CSS declaring A4 The API paper size controls output because preferCSSPageSize is false by default. Set preferCSSPageSize: true, and inspect other paper settings for conflicts.
Content appears scaled or clipped The selected paper geometry differs from CSS @page, or content exceeds the printable area. Choose one geometry source, review margins, and use preferCSSPageSize when CSS should win.
Background colors or images are missing printBackground defaults to false; print CSS may also alter colors. Set printBackground: true and consider -webkit-print-color-adjust: exact.
PDF layout differs from the browser screenshot PDF generation uses print media by default. Use page.emulateMediaType('screen') before page.pdf() if screen styling is intended.
Fonts look wrong or fallback fonts appear Fonts may not have loaded before capture, or the page is in a background state. Keep waitForFonts: true, wait for page-specific font or content readiness, and bring a background page to the front if needed.
Header/footer is absent Header/footer display is disabled by default, or the active protocol backend supports a smaller option set. Enable displayHeaderFooter; check BiDi support if using that backend.
PDF call times out Complex layout, slow resources, or a short configured timeout. Wait for the page’s required content explicitly, adjust the timeout, and bound the overall job separately.
Some requested pages are missing pageRanges does not match the final pagination. Inspect the page count and range syntax; omit the option to print all pages.

11. Performance, reliability, and cost

PDF generation cost is chiefly operational: browser CPU and memory, page load time, output storage, and any hosting or rendering service charges. Puppeteer’s cited API documentation does not specify a universal rendering benchmark or per-document cost. Measure your own pages and concurrency limits rather than relying on a generic speed estimate.

  • Reuse a browser process where your workload and isolation model permit it; create and close pages per job and ensure cleanup runs on both success and error.
  • Wait for the content your document needs. networkidle2 can be a useful navigation condition, but pages with ongoing requests may require a page-specific readiness signal instead.
  • Set bounded navigation and PDF timeouts, and return a clear failure response when a job exceeds them.
  • For reliability, record the Puppeteer and browser versions, chosen PDF options, navigation outcome, and errors. Rendering can change with browser version and site CSS.
  • Limit untrusted URL access and resource consumption if exposing a PDF endpoint. Validate allowed destinations, cap request size and job duration, and avoid unrestricted access to internal network resources.

12. Or skip the browser setup

If your goal is a page capture or shareable PDF without maintaining Puppeteer and a browser runtime, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It also supports PDF capture. Its one-request API can return PNG, JPEG, WebP, or PDF; see the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o page.pdf

Set the output format to PDF using the API’s documented parameters. ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. An MCP server exposes screenshot and PDF tools to AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000.

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

13. FAQ

Does Puppeteer use print or screen CSS for PDFs?

Print CSS by default. Call page.emulateMediaType('screen') before page.pdf() to use screen media.

Can I make a transparent PDF?

omitBackground: true hides the default white background and permits transparent PDFs. Check whether your downstream PDF viewers preserve and display that transparency as expected.

Does Puppeteer support tagged PDFs?

The general options reference documents tagged and marks it experimental, with a default of true. Confirm behavior with your installed version and output requirements.

Why do BiDi PDF options differ from the general API?

The official BiDi support page documents a smaller supported subset. Check that page when selecting a protocol backend and depending on less common PDF options.