How to Automate Website Screenshots with Puppeteer
Use Puppeteer to automate website screenshots with JavaScript. Capture one page, full-page screenshots, URL batches, and mobile viewports with a reusable script.
5 min readWebsite captureBy Temposhot Team
Puppeteer is a Node.js library maintained by the Chrome team. It controls Chrome or Firefox through a JavaScript API and is a common choice for scripted website screenshots. The examples below grow from a five-line capture into a reusable runner that reads URLs from a file, runs pages in parallel and writes a report.
When Puppeteer is the right tool
Puppeteer fits when screenshots belong inside code you already run:
- a Node.js service that renders previews or social images
- a cron job or CI step that captures pages after a deploy
- a scraping job that also needs a visual record
- a team that already uses Chrome DevTools Protocol features
It is less suited to one-off jobs by people who do not maintain Node.js projects.
Install Puppeteer for a screenshot script
mkdir site-shots && cd site-shots
npm init -y
npm install puppeteer
The puppeteer package downloads a compatible Chrome build during install. The lighter puppeteer-core package skips that download and needs a path to an installed browser instead. The scripts below use .mjs files so top-level await works.
Capture one website screenshot
// one.mjs
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900 });
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
await page.screenshot({ path: 'example.png' });
await browser.close();
page.setViewport sets the width and height. Add deviceScaleFactor: 2 to the same call for an image at twice the pixel density, similar to a Retina display.
Capture a full-page screenshot
await page.screenshot({ path: 'example-full.png', fullPage: true });
With fullPage: true the image covers the whole scrollable document. For a smaller file, save JPEG or WebP:
await page.screenshot({ path: 'example.jpg', type: 'jpeg', quality: 80, fullPage: true });
quality accepts 0 to 100 and applies to JPEG and WebP only. A full-page screenshot of a long page can be tens of thousands of pixels tall, so JPEG often keeps the file size manageable.
Build a reusable URL runner
// run.mjs
import { readFile, mkdir, writeFile } from 'node:fs/promises';
import puppeteer from 'puppeteer';
const urls = (await readFile('urls.txt', 'utf8'))
.split('\n').map((l) => l.trim()).filter(Boolean);
const outDir = `shots/${new Date().toISOString().slice(0, 10)}`;
await mkdir(outDir, { recursive: true });
const browser = await puppeteer.launch();
async function shoot(url, index) {
const page = await browser.newPage();
try {
await page.setViewport({ width: 1440, height: 900 });
await page.goto(url, { waitUntil: 'networkidle2', timeout: 45000 });
const path = `${outDir}/${String(index + 1).padStart(3, '0')}.jpg`;
await page.screenshot({ path, type: 'jpeg', quality: 80, fullPage: true });
return { url, path, ok: true };
} finally {
await page.close();
}
}
A fresh page per URL means a crash on one site does not affect the next. The dated output folder keeps earlier runs intact.
Add concurrency for bigger batches
const LIMIT = 4;
const queue = [...urls.entries()];
const results = [];
async function worker() {
while (queue.length) {
const [i, url] = queue.shift();
results.push(await shootWithRetry(url, i));
}
}
await Promise.all(Array.from({ length: LIMIT }, worker));
await browser.close();
Each worker takes the next URL from a shared queue. Start with 4 and raise the limit only after checking memory, since each open page uses its own renderer process.
Capture mobile screenshots
Puppeteer includes device descriptors through KnownDevices.
import puppeteer, { KnownDevices } from 'puppeteer';
const page = await browser.newPage();
await page.emulate(KnownDevices['iPhone 13']);
await page.goto('https://example.com');
page.emulate sets the viewport, pixel ratio, touch support and user agent in one call. For a plain mobile width without user agent changes, call setViewport with isMobile: true and hasTouch: true.
Handle lazy-loaded content
Images that load on scroll stay blank in a capture taken right after navigation. Scroll through the page first:
await page.evaluate(async () => {
for (let y = 0; y < document.body.scrollHeight; y += window.innerHeight) {
window.scrollTo(0, y);
await new Promise((r) => setTimeout(r, 200));
}
window.scrollTo(0, 0);
});
The lazy loading entry explains the pattern.
Choose the right wait strategy
| Option | Returns when |
|---|---|
load | The load event fires. This is the default. |
domcontentloaded | The HTML is parsed. |
networkidle0 | No network connections for at least 500 ms. |
networkidle2 | No more than 2 connections for at least 500 ms. |
networkidle0 can time out on pages that poll or stream. networkidle2 tolerates that background traffic. When a page has a clear ready signal, page.waitForSelector on that element is the most reliable choice. The default navigation timeout is 30 seconds.
Add retries and a report
async function shootWithRetry(url, i, tries = 3) {
for (let attempt = 1; attempt <= tries; attempt++) {
try {
return await shoot(url, i);
} catch (error) {
if (attempt === tries) return { url, ok: false, error: error.message };
}
}
}
// after Promise.all in run.mjs
await writeFile(`${outDir}/report.json`, JSON.stringify(results, null, 2));
The report lists every URL with its file path or error. Feed the failed entries back into urls.txt for a second pass.
A practical folder structure
site-shots/
run.mjs
urls.txt
shots/
2026-04-10/
001.jpg
002.jpg
report.json
One folder per run date, one report per folder. That is enough structure to compare runs later without a database.
Common mistakes
- Using
networkidle0on every site. Pages with analytics or chat widgets never settle. - Leaving pages open. Memory grows until the process slows down or crashes.
- Running too many pages at once. A limit of 20 on a laptop often causes more timeouts, not faster runs.
- Capturing consent overlays. Click the reject button by selector or hide the banner with
page.addStyleTag. - Ignoring site terms. Automated capture sends real requests. Check each site's terms and keep the rate low.
Alternatives to a Puppeteer screenshot script
- Playwright has a similar API and also drives WebKit. See How to automate website screenshots with Playwright.
- Hosted screenshot APIs run the browser for you and return images over HTTP.
- Browser extensions such as the Temposhot extension save one full-page PNG of the active tab.
- Temposhot Website Capture runs a pasted URL list on a Mac without code, with Desktop 1920×1080, Tablet 768×1024 or Mobile 390×844 sizes and a 0-60 s delay per row.
FAQ
Does Puppeteer work with Firefox?
Yes. Recent versions can launch Firefox as well as Chrome. Chrome remains the most widely used option.
Can Puppeteer save WebP screenshots?
Yes. Set type: 'webp' in the screenshot options. The quality value applies to WebP as it does to JPEG.
Why does page.goto time out on some sites?
The page did not reach the chosen wait event within 30 seconds. Switch to networkidle2 or load, raise timeout, or wait for a selector instead.
References
Need no-code website screenshot automation?
Temposhot Website Capture runs the same kind of URL batch locally on a Mac, with per-row settings and no script to maintain. See website screenshot automation or the capture options guide.
puppeteerwebsite-captureautomationnodejs
Terms used here: full page screenshot, headless browser, playwright, puppeteer, screenshot api, screenshot automation, viewport screenshot
Turn repeatable screenshots into finished work
Temposhot captures on a timer or on every click and exports the result as a PDF, images, ZIP or MP4.
Related posts
- How to Capture Website Screenshots in Bulk on MacCapture website screenshots in bulk on Mac with Temposhot. Paste a URL list, choose desktop/tablet/mobile presets, run parallel capture, and export for audits or reports.
- How to Automate Website Screenshots with PlaywrightUse Playwright to automate website screenshots with JavaScript. Capture one page, full pages, mobile views, and URL batches with retries.
- How to Automate Website Screenshots Without CodeChoose a no-code way to automate website screenshots: URL-list capture, an automation platform with a screenshot API, monitoring tools, or browser helpers.
- Best Xnapper Alternatives for Mac in 2026Compare Xnapper alternatives for Mac: repeated capture, scrolling capture and recording, free backgrounds, cross-platform guides, and the built-in app.