ScreenshotNeo

BlogHow-to

How to Print Web Pages with Puppeteer

Generate PDFs from web pages with Puppeteer. Learn print CSS, page sizing, margins, colors, page ranges, and fixes for common rendering issues.

By the ScreenshotNeo team4 October 20269 min read

Puppeteer prints a rendered web page to PDF with page.pdf(). Navigate to the page, choose whether it should use print or screen CSS, then set paper size, margins, backgrounds, and other PDF options. By default, PDF output uses print media CSS, omits background graphics, and uses Letter paper.

Generate a PDF from a web page

Install Puppeteer in a Node.js project, save this as print-page.js, and run node print-page.js. The script writes the PDF to the current directory and closes the browser even if navigation or PDF generation fails.

const puppeteer = require('puppeteer');

(async () => {
  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' });
  } finally {
    await browser.close();
  }
})();

page.pdf() returns a promise containing PDF bytes. Supplying path writes those bytes to a file; omit it when you want to handle the bytes yourself, such as returning them from an HTTP endpoint or storing them in object storage. See the Page.pdf() API reference and PDF generation guide. For ScreenshotNeo’s API options, see the ScreenshotNeo documentation.

Choose the right page readiness condition

The guide’s networkidle2 example is a useful starting point, not a guarantee that every page’s content is ready. Pages can continue fetching analytics or polling after the content you need appears, while some render important content only after client-side work. Choose the condition that matches the page:

  • domcontentloaded: proceed when the document has been parsed. Use this when you will explicitly wait for a selector or application state next.
  • load: wait for the load event, including dependent resources such as images. It may take longer on pages with slow resources.
  • networkidle2: wait for network activity to quiet according to Puppeteer’s navigation condition. This can be unsuitable for pages with persistent connections or background polling.

For a page that renders its main content after navigation, wait for a known selector rather than relying only on a network condition:

await page.goto('https://example.com/report', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-report-ready="true"]', { timeout: 15000 });
await page.pdf({ path: 'report.pdf' });

Use a selector that actually indicates the content is ready, not merely that a shell element exists. For sites you control, a dedicated ready marker is often clearer than guessing from timing.

page.pdf() renders with the print CSS media type. Print styles may hide navigation, simplify layouts, change colors, or apply page breaks. Keep that default when you want a document designed for paper.

To render the screen stylesheet instead, set the media type before generating the PDF:

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

Screen CSS does not automatically make a PDF fit paper well. Wide layouts can be scaled or clipped, and long pages can break at awkward points. If you control the site, add print-specific rules:

@media print {
  nav,
  .cookie-banner,
  .screen-only {
    display: none !important;
  }

  article {
    max-width: none;
  }

  h1, h2, h3 {
    break-after: avoid;
  }

  img, table, pre {
    break-inside: avoid;
  }

  a {
    color: inherit;
  }
}

Use page-break rules carefully: a rule that prevents breaks inside a large element can leave substantial blank space or push content to another page.

Set paper size, orientation, and margins

Pass layout options to page.pdf(). This example requests landscape A4, explicit margins, and background graphics:

await page.pdf({
  path: 'landscape-report.pdf',
  format: 'A4',
  landscape: true,
  margin: {
    top: '16mm',
    right: '14mm',
    bottom: '18mm',
    left: '14mm'
  },
  printBackground: true
});
Option What it controls Notes
format Named paper size, such as Letter or A4. Letter is the default. If set, it takes priority over width and height.
width, height Custom paper dimensions. Useful when a named format does not fit the document.
landscape Page orientation. Use for wide tables or charts; test that the page still fits the printable width.
margin Top, right, bottom, and left page margins. Specify lengths such as 12mm or 0.5in.
preferCSSPageSize Whether CSS @page size wins over the API size. Defaults to false. Set true when the page’s CSS page sizing should take priority.
pageRanges Which pages to include. For example, a range such as 1-3 selects a subset. Confirm the resulting page count for generated content.

When using CSS-controlled page geometry, define it explicitly and enable preferCSSPageSize:

@page {
  size: A4 landscape;
  margin: 12mm;
}
await page.pdf({
  path: 'css-sized.pdf',
  preferCSSPageSize: true
});

Control backgrounds, colors, and headers

Background graphics are omitted by default. Enable them with printBackground: true if the design depends on colored backgrounds, shaded table rows, or background images.

Browsers may adjust colors for printing. For closer color fidelity, add this CSS to the page:

* {
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

Use this only when matching on-screen colors matters; ink-heavy backgrounds can make a PDF larger and less practical to print. You can also set omitBackground: true to produce output without the page background.

To add repeating headers or footers, enable displayHeaderFooter and provide templates. Puppeteer supports special classes including date, title, URL, page number, and total pages:

await page.pdf({
  path: 'with-footer.pdf',
  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' }
});

Templates are HTML snippets, not normal page content. Keep them self-contained and allow enough top or bottom margin so they do not overlap the document.

Return bytes, select pages, and use other PDF options

Without path, the result is a Uint8Array you can write or send yourself:

const pdfBytes = await page.pdf({ format: 'A4' });
require('node:fs').writeFileSync('page.pdf', pdfBytes);

For a subset of a longer document, set pageRanges. To make the output smaller on the page, adjust scale, which defaults to 1 and accepts values from 0.1 through 2. Scaling is a layout adjustment, not a substitute for choosing appropriate paper dimensions or fixing overflowing content.

Other useful PDF options include timeout for the PDF operation and waitForFonts, which defaults to true. Font waiting can help avoid fallback-font output, but fonts that never finish loading can delay generation. Consult the PDFOptions reference for the full option definitions supported by your installed Puppeteer version.

Complete script with configuration

This version accepts a URL argument, waits for the document, waits for a target element, and writes a configured PDF. Replace the selector and settings to match the site.

const puppeteer = require('puppeteer');

async function printPage(url, outputPath) {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.setViewport({ width: 1280, height: 900 });
    await page.goto(url, {
      waitUntil: 'domcontentloaded',
      timeout: 30000
    });
    await page.waitForSelector('main', { timeout: 15000 });

    // Leave the default print media enabled for print-specific CSS.
    const pdf = await page.pdf({
      path: outputPath,
      format: 'A4',
      printBackground: true,
      preferCSSPageSize: true,
      margin: { top: '14mm', right: '12mm', bottom: '16mm', left: '12mm' },
      waitForFonts: true,
      timeout: 30000
    });
    return pdf;
  } finally {
    await browser.close();
  }
}

const url = process.argv[2] || 'https://example.com';
const output = process.argv[3] || 'page.pdf';
printPage(url, output).catch(error => {
  console.error('PDF generation failed:', error);
  process.exitCode = 1;
});

The script returns bytes as well as writing the file because Puppeteer returns the PDF data from page.pdf(). If memory matters for your workload, avoid retaining the returned bytes after writing the file.

Or skip the browser setup

ScreenshotNeo provides a screenshot and PDF API: one GET request returns a PNG, JPEG, WebP, or PDF. Its PDF options include paper size, margins, landscape orientation, and page ranges. See the API documentation.

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
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com", "format": "pdf"},
    timeout=90,
)
r.raise_for_status()
open("page.pdf", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com',
  format: 'pdf'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await (await import('node:fs/promises')).writeFile('page.pdf', bytes);

Cookie banners, newsletter popups, and chat widgets are removed before capture. Bot checks, blank pages, and failed loads are never billed; response headers report the page verdict and billing status. Its MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000. Create a free account and get 1,000 screenshots a month with no card.

Troubleshooting

Symptom Likely cause Fix
PDF is blank or content is missing The page had not rendered the relevant content before capture, or the selector wait targeted the wrong element. Wait for a meaningful selector or application-ready signal after navigation. Check that the page did not redirect to an error or bot-check page.
Screen layout looks different in the PDF page.pdf() uses print media CSS by default. Use or update the site’s print stylesheet, or call page.emulateMediaType('screen') before printing.
Background colors or images are missing Background printing is off by default. Set printBackground: true; use print color adjustment CSS if colors are still modified.
Content is clipped or unexpectedly small Paper geometry, margins, orientation, CSS page sizing, or scale do not match the content. Inspect @page, set preferCSSPageSize deliberately, choose landscape or custom dimensions when appropriate, and adjust margins or scale.
PDF operation times out Fonts, page resources, or rendering are slow, or the configured timeout is too short. Check navigation and font loading separately, wait for the needed content, and raise the relevant timeout only when the longer wait is expected.
Footer overlaps page content The document has insufficient bottom margin for the footer template. Increase the bottom margin and keep the template compact.
Some images or fonts are absent Resources had not loaded, failed remotely, or are blocked by the site’s rendering environment. Wait for the required elements or fonts, verify resource URLs are accessible to the browser, and inspect page errors before PDF generation.

Performance, reliability, and cost

PDF creation requires a browser process and page rendering, so runtime and memory use depend on the page, its assets, and the PDF dimensions. Reuse a browser process for multiple jobs when building a service, while creating a separate page per job and closing pages when finished. Always close the browser in a finally block so failures do not leave browser processes running.

For reliability, set navigation and selector timeouts intentionally, choose a readiness signal based on the page, and handle navigation, PDF, and file errors. A network-idle condition alone cannot establish that a page’s application data is complete. If generating many PDFs, limit concurrent browser pages to the capacity of the host and observe memory use under the real workload.

Puppeteer itself does not impose a per-PDF API charge in this workflow; the operating costs are the machine, browser runtime, storage, and any infrastructure used to serve or retain PDFs. ScreenshotNeo offers a managed API with a Free plan of 1,000 shots a month and paid plans from $5 for 3,000; only clean shots are billed, and cache hits cost nothing.

FAQ

Does Puppeteer print to a physical printer?

page.pdf() creates a PDF file or returns PDF bytes. Printing that file on paper is a separate step handled by a printer or another application.

Does Puppeteer wait for fonts before creating the PDF?

Yes. PDF generation waits for fonts by default. The waitForFonts option controls this behavior.

Can I save only selected pages?

Yes. Use the pageRanges PDF option to request a page range, then check the output for the expected pages.

Which paper size is used if I do not specify one?

The default format is Letter. Set format, or use custom dimensions or CSS page sizing when the document requires a different size.