Everything you need to know about Puppeteer screenshot automation.
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.
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.
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.
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.
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);
});
});
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.
Puppeteer's page.goto() accepts a waitUntil option:
'load' — fires when the load event fires (default)'domcontentloaded' — fires when HTML is parsed'networkidle0' — no network connections for 500ms (strictest)'networkidle2' — max 2 network connections for 500ms (more lenient)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.
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.
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.
await page.screenshot({
clip: { x: 0, y: 0, width: 800, height: 400 }
});
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.
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 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.
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.