How to take screenshots with Puppeteer: complete guide

How to take screenshots with Puppeteer: complete guide

Everything you need to know about Puppeteer screenshot automation.

March 28, 2026 · 10 min read

Puppeteer is Google's Node.js library for controlling headless Chrome. It's been the go-to screenshot tool since 2017 and still powers a huge amount of browser automation. This guide covers every screenshot technique Puppeteer supports, with working code examples.

Setup

npm install puppeteer

This installs Puppeteer and downloads a compatible Chromium binary (~170MB). If you want to use an existing Chrome installation instead:

npm install puppeteer-core

Then point it at your Chrome path when launching.

Basic screenshot

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
await browser.close();

Default viewport: 800×600. Default format: PNG. The screenshot captures exactly what you'd see in a browser window at that size.

Setting the viewport

await page.setViewport({
  width: 1440,
  height: 900,
  deviceScaleFactor: 2  // Retina
});

Always set the viewport before navigating — some pages make layout decisions based on the initial viewport size. Setting it after goto triggers a re-layout, which is slower and can cause visual glitches.

Full-page capture

await page.screenshot({
  path: 'fullpage.png',
  fullPage: true
});

This scrolls the page internally and stitches the result into a single image. Same lazy loading caveats as Playwright — images below the fold may not load. Scroll the page first if this matters:

await page.evaluate(async () => {
  await new Promise((resolve) => {
    let totalHeight = 0;
    const distance = 500;
    const timer = setInterval(() => {
      window.scrollBy(0, distance);
      totalHeight += distance;
      if (totalHeight >= document.body.scrollHeight) {
        clearInterval(timer);
        window.scrollTo(0, 0);
        resolve();
      }
    }, 100);
  });
});

Element screenshots

const element = await page.$('.pricing-table');
await element.screenshot({ path: 'pricing.png' });

Captures just the element's bounding box. Returns null if the selector doesn't match — always check for null in production code.

Wait strategies

Puppeteer's page.goto() accepts a waitUntil option:

await page.goto(url, { waitUntil: 'networkidle0' });

For SPAs: use networkidle0 plus page.waitForSelector() to wait for a specific element that signals the app is ready.

JPEG and quality control

await page.screenshot({
  path: 'compressed.jpg',
  type: 'jpeg',
  quality: 80
});

JPEG quality 80 gives a good balance between file size and visual quality. Below 60 you'll see artifacts around text. Above 90 the file size gains are minimal.

Device emulation

Puppeteer ships with predefined device profiles:

import { KnownDevices } from 'puppeteer';
const iPhone = KnownDevices['iPhone 14 Pro'];

const page = await browser.newPage();
await page.emulate(iPhone);
await page.goto('https://example.com');
await page.screenshot({ path: 'mobile.png' });

This sets viewport, user agent, DPR, and touch support to match the device. Useful for capturing mobile versions of sites.

Clip regions

await page.screenshot({
  clip: { x: 0, y: 0, width: 800, height: 400 }
});

Transparent backgrounds

await page.screenshot({
  path: 'transparent.png',
  omitBackground: true
});

Removes the default white background, producing a transparent PNG. Only works if the page itself has a transparent background — most don't.

PDF generation

Puppeteer can also generate PDFs (not just screenshots):

await page.pdf({
  path: 'page.pdf',
  format: 'A4',
  printBackground: true,
  margin: { top: '1cm', bottom: '1cm', left: '1cm', right: '1cm' }
});

PDFs use the print stylesheet (@media print), so the output may look different from a screenshot.

Puppeteer vs Playwright

Puppeteer works, but if you're starting a new project, consider Playwright instead. Playwright supports multiple browsers, has better auto-wait behaviour, and is more actively developed. The API is very similar — migration is straightforward.

Or skip the browser entirely

If you just need URL-to-image conversion without managing headless browsers, a screenshot API eliminates the infrastructure. nightglass runs Chromium on dedicated hardware — half a cent per capture, no browser to manage. See how it compares on price, or check the quickstart guide to try it.

If you're running in Lambda or Vercel Functions: headless browsers don't run in serverless environments. For a broader look at Puppeteer vs alternatives, the complete headless browser guide covers the tradeoffs.