Driver FixRecommendedSound, Wi-Fi or graphics acting up? Check drivers firstFind missing or outdated drivers fast.Check DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
JavaScript

How to Save a Puppeteer Screenshot to a File (PNG, JPEG, WebP, and PDF Workflows)

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use Puppeteer’s Page.screenshot() method and pass a path, such as await page.screenshot({ path: 'screenshot.png' }). The file is written relative to the Node.js process’s current working directory unless you provide an absolute path. The complete setup, format options, full-page and element captures, troubleshooting, and a no-browser alternative are below.

Minimal working script

Install Puppeteer, launch a browser, navigate to a URL, save the image, and always close the browser. This follows the sequence in the official Screenshots guide and the Page reference.

  1. Create a project and install Puppeteer: mkdir shot-demo && cd shot-demo, then npm init -y and npm install puppeteer.
  2. Save this as capture.mjs:
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.screenshot({ path: 'screenshot.png' });
} finally {
  await browser.close();
}
  1. Run node capture.mjs. You should find screenshot.png in the directory from which you ran the command.

Puppeteer documents Page.screenshot() as the primary page-capture method. Supplying path writes the image directly to disk; omitting it leaves the image in memory instead. See the Page.screenshot() API.

Where the file is written

A relative path is resolved against process.cwd(), the process’s current working directory, not necessarily the folder containing your script. This distinction matters in Docker containers, CI jobs, npm scripts, and services started by a process manager. Use an absolute path when the destination must be deterministic.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import path from 'node:path';

const output = path.resolve(process.cwd(), 'artifacts', 'home.webp');
await page.screenshot({ path: output, type: 'webp' });

Create the destination directory before capturing; Puppeteer does not create missing parent directories for you.

import fs from 'node:fs/promises';

await fs.mkdir('artifacts', { recursive: true });
await page.screenshot({ path: 'artifacts/home.png' });

The extension determines the image type when you use path. The ScreenshotOptions reference documents PNG as the default.

Choose the capture scope and output

Goal Option or method What it does
Visible viewport page.screenshot({ path }) Captures the currently visible viewport.
Entire document fullPage: true Extends the capture to the full scrollable page.
Rectangular region clip: { x, y, width, height } Captures only the specified viewport coordinates.
One DOM element elementHandle.screenshot({ path }) Scrolls the element into view and captures its bounds.
Transparent background omitBackground: true Hides the default page background where transparency is supported.
Image format type: 'png' | 'jpeg' | 'webp' Selects the encoded output format; matching the filename extension avoids confusion.
JPEG/WebP compression quality: 0–100 Controls quality for formats that support it; it does not apply to PNG.

Full-page capture

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

Very long pages can require more memory and time than a viewport shot, especially when images are lazy-loaded or animated. Wait for the page state you need before calling the method.

Capture a clipped region

await page.screenshot({
  path: 'hero.webp',
  type: 'webp',
  clip: { x: 0, y: 0, width: 1280, height: 720 }
});

The clip rectangle uses CSS pixels in the page’s viewport. Set the viewport explicitly when reproducible dimensions matter:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.setViewportSize?.({ width: 1280, height: 720 });

If your Puppeteer version does not expose that helper, set the viewport when creating the page with await page.setViewport({ width: 1280, height: 720 }).

Capture one element

const card = await page.waitForSelector('.pricing-card');
if (!card) throw new Error('Pricing card not found');
await card.screenshot({ path: 'pricing-card.png' });

ElementHandle.screenshot() scrolls the element into view if necessary. It throws when the element has been detached from the DOM; re-query the selector after page updates. Details are in the ElementHandle screenshot API.

Control timing so the saved image is complete

page.goto() finishing only means the selected navigation condition was reached. Modern pages may still render fonts, images, charts, or client-side data afterward. Combine a navigation condition with a specific readiness check.

await page.goto('https://example.com/dashboard', { waitUntil: 'networkidle2' });
await page.waitForSelector('[data-ready="true"]');
await page.screenshot({ path: 'dashboard.png', fullPage: true });

For a known animation or delayed widget, use a deliberate delay:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await new Promise(resolve => setTimeout(resolve, 1000));
await page.screenshot({ path: 'after-delay.png' });

Prefer a selector that proves the content is ready over an arbitrary sleep. You can also disable motion with CSS when visual stability is more important than animation fidelity:

await page.addStyleTag({
  content: '* { animation: none !important; transition: none !important; }'
});

Return image data instead of writing immediately

path is the simplest disk workflow, but the API can return image data. According to the API reference, the default return value is a Promise<Uint8Array>. With encoding: 'base64', it is a string.

const bytes = await page.screenshot({ type: 'png' });
await fs.writeFile('from-bytes.png', bytes);

const base64 = await page.screenshot({ encoding: 'base64', type: 'jpeg', quality: 80 });
await fs.writeFile('image.txt', base64, 'ascii');

Use returned bytes when you need to upload to object storage, attach an HTTP response, hash the image, or process it without a temporary file. Use path when a conventional artifact on disk is the desired result.

PDF output is a separate operation

A screenshot produces an image. For a paginated document, use Puppeteer’s PDF functionality rather than expecting Page.screenshot() to create a PDF. Keep the output extension aligned with the operation so downstream tools do not misidentify the file.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Reliable scripts in CI and production

  • Wrap the browser lifetime in try/finally so a navigation or screenshot exception does not leave Chromium processes running.
  • Use absolute output paths or log process.cwd() when an artifact cannot be found.
  • Set explicit viewport dimensions, color scheme, locale, and timezone when pixel comparisons must be repeatable.
  • Wait for a content-specific selector and fonts or images that affect the result; do not assume network idle means every visual is final.
  • Give each concurrent page its own output filename. Do not let workers overwrite the same path.
  • Remember that screenshot operations can affect scheduling within a browser context. The Page API remarks note that creating or closing a page in the same context waits for an in-progress screenshot, while bringToFront() does not wait for existing screenshot operations.

Troubleshooting common failures

Symptom Likely cause Fix
No file appears The relative path points to a different working directory, or the parent directory is missing. Print process.cwd(), use an absolute path, and create the directory with fs.mkdir(..., { recursive: true }).
ENOENT while saving The destination directory does not exist. Create it before page.screenshot(); Puppeteer writes the file but does not create all parent folders.
Element screenshot says the handle is detached The framework re-rendered and replaced the element. Call waitForSelector again immediately before elementHandle.screenshot().
Blank or partially rendered image Capture happened before client-side content, fonts, or images were ready. Wait for a readiness selector, required requests, or a short targeted delay; then capture.
Full-page image is unexpectedly short The page’s content was still loading or a scroll-triggered section had not rendered. Wait for the final content marker and, if needed, scroll through the page before the full-page capture.
Wrong format or quality setting The filename extension and type disagree, or quality was applied to PNG. Use matching values such as path: 'shot.webp', type: 'webp'; quality affects JPEG/WebP, not PNG.
Chromium fails to launch in a restricted runner The execution environment lacks required sandbox permissions or browser dependencies. Install the dependencies recommended for your operating system and configure the runner’s Chromium sandbox policy according to its security requirements; do not disable protections blindly.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If you need a URL turned into an image or PDF without maintaining Chromium code, ScreenshotNeo is the alternative to try first: it produces clean shots, bills only clean captures, and its paid entry plan is $5 for 3,000 shots.

One GET request is enough. The ScreenshotNeo API documentation has the complete parameter reference.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and every response reports its result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Beyond the basic URL call, the service supports full-page screenshots with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper size/margins/landscape/page ranges, HTML/CSS-to-image, custom JavaScript and CSS, pre-capture clicks, hidden selectors, waits for selectors/delays/network idle, blocking ads/trackers/requests/resource types, custom headers/cookies/user agents/Authorization, timezone and geolocation, transparent backgrounds, resizing, user-selected cache TTLs, signed links for public <img> tags, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, an OpenAPI specification, and compatibility with parameter names used by other screenshot APIs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Plan Allowance Price
Free 1,000 shots/month Free, no card
Starter 3,000 shots $5
Growth 15,000 shots $15
Pro 60,000 shots $39
Scale 250,000 shots $99
Business 1,000,000 shots $249

Yearly billing provides two months free, and every feature is included on every plan. Start with 1,000 free screenshots a month—no card required.

Frequently Asked Questions

Can Puppeteer save a screenshot without a filename extension?

It can write to a path, but using an extension such as .png, .jpeg, or .webp makes the inferred format and downstream file handling unambiguous.

Does taking a screenshot change the page?

The capture itself reads the rendered page. However, page creation and page closing in the same browser context can wait for an in-progress screenshot, so coordinate those operations when running concurrent jobs.

What should I use for a single component in a test?

Wait for the component’s selector, obtain its current element handle, and call elementHandle.screenshot({ path }); this captures the element rather than the whole viewport.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.