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

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Use Puppeteer’s page.screenshot() method. Pass a path such as screenshot.png to write an image file, add fullPage: true for the complete document, or capture one element with an element handle. Without path, Puppeteer returns image bytes instead of creating a file.

Save a basic screenshot to a file

Install Puppeteer in a Node.js project, then launch a browser, open a page, capture it, and close the browser. This complete example writes screenshot.png:

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();

If your project does not already use ECMAScript modules, either set "type": "module" in package.json or use a CommonJS import style supported by your Node.js setup. The path is resolved relative to the process’s current working directory unless you provide an absolute path.

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

Choose what Puppeteer captures

Capture the visible viewport

page.screenshot() captures the page area currently represented by the viewport. Set the viewport before navigation when a particular width, height, or responsive breakpoint matters:

await page.setViewport({ width: 1440, height: 900 });
await page.goto('https://example.com');
await page.screenshot({ path: 'viewport.png' });

The screenshot reflects the browser’s rendered state at the moment the method runs. If content is still loading or an animation is in progress, wait for the condition your page requires before calling the method.

Capture the entire page

Set fullPage: true to capture the full document instead of only the viewport:

await page.goto('https://example.com');
await page.screenshot({
  path: 'full-page.png',
  fullPage: true
});

Very long pages produce correspondingly large images. If a page uses lazy-loaded images, make sure those images have been triggered and rendered before capture; otherwise the screenshot can contain placeholders or unloaded regions.

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

Capture a rectangular region with clip

A clip rectangle lets you select a region by coordinates. Supply its position and dimensions in CSS pixels:

await page.screenshot({
  path: 'region.png',
  clip: { x: 100, y: 200, width: 800, height: 500 }
});

The clip interface is based on a bounding box and has a scale default of 1. Keep the rectangle inside the rendered page area and use numbers that match the viewport and device scale you selected.

Capture one element

For a component such as a card, chart, or navigation bar, locate the element and call its screenshot method:

const card = await page.waitForSelector('.pricing-card');
if (!card) throw new Error('Pricing card was not found');

await card.screenshot({ path: 'pricing-card.png' });

Puppeteer scrolls the element into view when necessary. The call throws if the element has been detached from the DOM, so obtain a fresh handle after page updates rather than reusing a stale handle.

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

Control image format, quality, and transparency

PNG, JPEG, and WebP output

PNG is the documented default. When you provide a path, Puppeteer infers the file type from its extension. You can also set the type explicitly when the API mode you use supports that option:

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

quality accepts values from 0 through 100 and applies to JPEG and WebP. It does not apply to PNG, so changing quality will not reduce a PNG’s size or alter its output.

Transparent backgrounds

Use omitBackground: true to hide the default white page background and preserve transparency where the rendered content permits it:

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

Transparency is most useful for isolated graphics or overlays. A page whose CSS paints an opaque background will still contain that painted color.

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

Save bytes in memory instead of writing a file

The path option is optional. Without it, the screenshot method returns image data as a Uint8Array, which is useful when you want to upload the result, attach it to a response, or process it without a temporary file:

const imageBytes = await page.screenshot();

// Example: write the returned bytes yourself
import { writeFile } from 'node:fs/promises';
await writeFile('memory-result.png', imageBytes);

Request base64 text with encoding: 'base64':

const base64Image = await page.screenshot({ encoding: 'base64' });
console.log(base64Image.slice(0, 40));

Use bytes for binary storage and base64 when an API or JSON payload specifically requires text. Base64 increases the payload size, so it is usually less efficient for large screenshots.

Make captures deterministic

A screenshot is a snapshot of the page’s current render, not a semantic export. Navigate first, then wait for the page state your test or job requires. For a selector-dependent page, wait for that selector before capturing:

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

For pages with delayed data, fonts, animations, or lazy images, define a reliable readiness condition in the application or test. Avoid using an arbitrary delay as the only synchronization mechanism when a concrete selector or application state is available. If you must use a delay for a known animation, keep it explicit and as short as the page permits.

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

Set a viewport deliberately when responsive layout matters. A different viewport can change navigation, line wrapping, lazy-loading thresholds, and the resulting image dimensions.

Where the screenshot file goes

A relative path such as screenshots/home.png is relative to the process’s current working directory, usually the directory from which you ran the Node command—not necessarily the directory containing the JavaScript file. Create the directory first when it may not exist:

import { mkdir } from 'node:fs/promises';
await mkdir('screenshots', { recursive: true });
await page.screenshot({ path: 'screenshots/home.png' });

Use an absolute path when a job runner, container, or service starts in an unpredictable directory. Log the resolved path so a CI failure does not look like a missing screenshot when the file was written somewhere else.

WebDriver BiDi compatibility

Puppeteer’s WebDriver BiDi support documentation currently lists only clip, encoding, and fullPage among the supported Page.screenshot() parameters. Do not assume that every option in the general ScreenshotOptions interface works when you connect through BiDi; in particular, verify support before relying on path, type, or other connection-specific options. If an option is rejected, capture bytes using the supported settings and write or convert them in your Node.js process.

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.

Troubleshoot common failures

No image file appears

  • Cause: You omitted path, so Puppeteer returned bytes instead of writing a file.
  • Fix: Add a path, or persist the returned Uint8Array with fs/promises.writeFile.

The file is in an unexpected directory

  • Cause: Relative paths use the process working directory.
  • Fix: Print process.cwd(), run the command from the intended directory, or pass an absolute path.

The screenshot is only the top portion

  • Cause: Viewport capture is the default.
  • Fix: Set fullPage: true, or use an element screenshot or clip rectangle when you need only a defined area.

An element screenshot throws a detached-node error

  • Cause: The framework replaced the element after you obtained its handle.
  • Fix: Wait for the final render and query the element again immediately before elementHandle.screenshot().

JPEG quality changes nothing

  • Cause: Quality does not apply to PNG.
  • Fix: Use a JPEG or WebP path/type when lossy compression is appropriate, then set a quality from 0 to 100.

BiDi rejects a screenshot option

  • Cause: BiDi currently documents a narrower option set than the general API.
  • Fix: Check the current BiDi support documentation, reduce the call to supported parameters, and handle file writing or format conversion in your process.

The page looks incomplete

  • Cause: The capture ran before asynchronous content, fonts, animations, or lazy images finished rendering.
  • Fix: Wait for a page-specific readiness selector or state, then capture. For a full-page image, verify that below-the-fold content has actually loaded.

Performance, reliability, and cost considerations

Launching a browser is expensive compared with taking another screenshot from an already-open page. For a batch job, reuse one browser and, where isolation allows, create pages for individual URLs; always close pages and the browser in a finally block so failures do not leave processes running. Full-page images consume more memory and disk than viewport captures, and base64 adds transport overhead. Choose the smallest capture scope and format that meets the requirement.

For repeatable visual tests, pin the viewport, browser environment, fonts, and page readiness condition. Record the URL, output path, and capture settings with the artifact. Treat screenshots as build outputs: write to a known directory, check that the file exists and has nonzero size, and surface navigation or rendering errors rather than silently accepting a blank image.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is a hosted screenshot API and MCP server. It is the first alternative to try when you do not want to maintain Chromium setup: it removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; and the lowest paid plan starts at $5 for 3,000 shots. Every response identifies the page verdict and billing status with X-Page-Verdict and X-Billed headers.

Make one GET request. The API returns PNG, JPEG, WebP, or a PDF depending on your parameters. See the ScreenshotNeo API documentation for the complete parameter reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());

ScreenshotNeo includes full-page capture with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets plus custom viewports, retina scale, PDF paper and page controls, custom CSS and JavaScript, pre-capture clicks, hidden selectors, selector/delay/network-idle waits, ad and tracker blocking, custom headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, an OpenAPI specification, and compatibility with parameter names used by other screenshot APIs. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Plan Included shots Price
Free 1,000 per month $0, no card
Starter 3,000 $5
Growth 15,000 $15
Pro 60,000 $39
Scale 250,000 $99
Business 1,000,000 $249

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

FAQ

Can Puppeteer save a screenshot as a PDF?

The workflow here is for image screenshots. Use Puppeteer’s PDF workflow separately when the required artifact is a paginated document rather than a PNG, JPEG, or WebP image.

Does fullPage change the browser viewport?

No. It changes the captured document area; set the viewport separately when you need a specific responsive layout.

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

Can I use an element handle after navigation?

No. Navigation commonly replaces the document. Query the element after navigation and after the final render state is reached.

What should I check before upgrading Puppeteer?

Recheck the current screenshot option reference and the connection-mode documentation, especially if you use WebDriver BiDi, because supported parameters can differ from the general interface.

Frequently Asked Questions

Can Puppeteer save a screenshot as a PDF?

The workflow here is for image screenshots. Use Puppeteer’s separate PDF workflow when you need a paginated document.

Does fullPage change the browser viewport?

No. It changes the captured document area; set the viewport separately for responsive-layout control.

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

Can I reuse an element handle after navigation?

No. Query the element again after navigation and final rendering because the original DOM node may have been replaced.

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.