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

Use page.screenshot() after navigating to a page. Puppeteer saves a PNG by default, or it can return image bytes/base64 when you omit path. Set fullPage: true for the entire scrollable document, clip for a rectangle, and type: 'jpeg' with quality for compressed JPEG output. The examples below target the current Puppeteer 25.12.0 API reference and include practical waiting, viewport, transparency and troubleshooting patterns.

Install Puppeteer and run the smallest working example

Install Puppeteer in a Node.js project, then launch Chromium, create a tab, navigate, capture and close the browser. Use an explicit waitUntil value when the page must finish loading before the image is taken.

npm install puppeteer
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();
}

page.screenshot() captures the current page. With no options, it writes a PNG when path is supplied. Always close the browser in a finally block so failures do not leave Chromium processes running.

Choose the capture area

Viewport screenshot

The default image is the visible viewport. Set its dimensions before navigation when you need repeatable output.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'viewport.png' });

Full-page screenshot

Set fullPage: true to capture the page’s complete scrollable document instead of only the viewport. Long pages can produce very tall images and consume substantial memory.

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

Capture a rectangular region

clip receives page coordinates and dimensions. The rectangle is measured in CSS pixels from the page’s top-left coordinate system.

await page.screenshot({
  path: 'crop.png',
  clip: { x: 40, y: 80, width: 640, height: 360 },
});

For a rectangle that moves with responsive layout, calculate its bounding box first rather than hard-coding coordinates:

const card = await page.locator('.pricing-card').boundingBox();
if (!card) throw new Error('Pricing card is not visible');
await page.screenshot({ path: 'pricing-card.png', clip: card });

Capture one element

Element screenshots are useful for a component, chart or card. Puppeteer’s locator API can scroll the element into view and capture its bounds.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const chart = page.locator('#revenue-chart');
await chart.wait();
await chart.screenshot({ path: 'chart.png' });

If the element is hidden, has zero dimensions or is covered by another layer, the capture can fail or produce an unexpected result. Wait for the selector and verify its bounding box before taking the image.

Save PNG or JPEG and control compression

Goal Options Result
Lossless default path: 'page.png' PNG; quality has no effect.
Smaller photographic image type: 'jpeg', quality: 82 JPEG with a 0–100 quality value.
Transparent background omitBackground: true Transparent areas where the page background is transparent.
Bytes in memory Omit path A binary Uint8Array.
Base64 string encoding: 'base64' Base64 image data as a string.
await page.screenshot({
  path: 'page.jpg',
  type: 'jpeg',
  quality: 82,
});

JPEG quality applies only to JPEG output. PNG remains lossless and ignores that setting.

Keep the page stable before capture

Wait for navigation

await page.goto('https://example.com', { waitUntil: 'networkidle2' });

networkidle2 waits until at most two network connections remain for a short period. Sites with analytics, advertisements or live updates may never become truly idle, so use a selector or a bounded delay when appropriate.

Wait for a specific component

await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('.dashboard-ready', { visible: true });
await page.screenshot({ path: 'dashboard.png' });

Wait for fonts, images or an application state

await page.evaluate(async () => {
  await document.fonts.ready;
  const images = Array.from(document.images);
  await Promise.all(images.map(image => {
    if (image.complete) return Promise.resolve();
    return new Promise(resolve => {
      image.addEventListener('load', resolve, { once: true });
      image.addEventListener('error', resolve, { once: true });
    });
  }));
});
await page.screenshot({ path: 'settled.png' });

This prevents a screenshot from catching fallback fonts or images that have not finished loading. For infinite-scroll pages, full-page capture does not automatically guarantee that every lazy image has been fetched; scroll or trigger the application’s own loading mechanism first.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

Return a buffer or base64 instead of writing a file

Binary bytes

const bytes = await page.screenshot();
console.log(bytes instanceof Uint8Array, bytes.length);

Use the returned Uint8Array for an upload, an HTTP response or an object-storage client. Convert it to a Node.js Buffer when a library expects one:

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

Base64

const base64 = await page.screenshot({ encoding: 'base64' });
const dataUri = `data:image/png;base64,${base64}`;

Base64 is convenient for JSON or a data URI, but it increases payload size compared with binary bytes.

Reusable complete script

This script combines a controlled viewport, full-page capture, a selector wait and guaranteed cleanup. Save it as screenshot.mjs and run node screenshot.mjs.

import puppeteer from 'puppeteer';

const url = process.argv[2] ?? 'https://example.com';
const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1365, height: 900, deviceScaleFactor: 1 });
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45_000 });
  await page.waitForNetworkIdle({ idleTime: 500, timeout: 15_000 }).catch(() => {});
  await page.screenshot({ path: 'page.png', fullPage: true });
  console.log('Saved page.png');
} finally {
  await browser.close();
}

The bounded network-idle wait is deliberately allowed to time out: a page with persistent connections can still be ready to capture. Replace it with a required selector when your application exposes a reliable ready state.

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

Options that matter in production

  • path: destination filename. Omit it for in-memory output.
  • type: PNG is the default; use JPEG when lossy compression is acceptable.
  • quality: 0–100 for JPEG only.
  • encoding: binary output by default, or 'base64' for a string.
  • fullPage: expands the capture to the scrollable page.
  • clip: captures a specified rectangle.
  • omitBackground: removes the default background to permit transparency.
  • captureBeyondViewport and fromSurface: advanced controls for how Chromium reads pixels; use the defaults unless a rendering issue requires changing them.

Retina-style output comes from deviceScaleFactor on the viewport. A factor of 2 doubles pixel dimensions and increases file size, so choose it intentionally.

Troubleshooting Puppeteer screenshots

“Navigation timeout exceeded”

The page did not meet the navigation condition before the timeout. Increase timeout, use waitUntil: 'domcontentloaded', or wait for a known application selector instead of network idle. Do not disable timeouts without an outer job limit.

Blank or partially rendered image

Capture after the content exists, await fonts and images, and check that the viewport is not zero-sized. For client-rendered applications, wait for a ready marker emitted after data binding.

Element screenshot says the node is not visible

Confirm the selector, call locator.wait(), inspect boundingBox(), and remove CSS states such as display:none or collapsed accordions before capture.

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

Lazy images are missing

Scroll through the document or invoke the site’s load-more behavior before fullPage capture. Then wait for image completion. A full-page request alone is not a guarantee that application-level lazy loading has run.

File is unexpectedly huge

Use JPEG with an appropriate quality for photographs, reduce the viewport or device scale factor, capture only the needed element, and avoid full-page images when a viewport image meets the requirement.

Chromium will not launch in a container

Check that the Puppeteer browser was installed and that the runtime has the libraries and sandbox permissions Chromium requires. In restricted environments, use the platform’s documented sandbox configuration rather than copying unsafe flags blindly.

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

Performance, reliability and cost considerations

  • Reuse one browser process and create or close pages per job instead of launching Chromium for every image.
  • Set navigation and capture timeouts, and record the URL, viewport, options and error so failed jobs can be retried safely.
  • Prefer a selector-based readiness condition for dynamic sites; network-idle heuristics are affected by telemetry and long polling.
  • Limit concurrency to the CPU and memory available. Several full-page, high-scale captures can exhaust memory.
  • Use deterministic viewport, timezone, locale and reduced-motion settings when comparing visual output over time.
  • Respect authentication, robots policies and access controls for sites you capture. Never place credentials in a public script or screenshot URL.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP or PDF, while the service handles browser startup and capture options for you. Before the capture it accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets; each behavior can be turned off.

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

Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and the response identifies the 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.

Best Value
The SQL Programming Language: .
  • Used Book in Good Condition

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

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)

Node.js

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(`HTTP ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));

See the ScreenshotNeo documentation for parameters. It supports full-page and element capture, dark mode, device presets or custom viewports, retina scale, PDF settings, custom CSS and JavaScript, clicks, selector or delay waits, network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which eases migration.

The Free plan includes 1,000 shots each month with no card. Paid plans start at $5 for 3,000 shots; Growth is $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000 and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is included on every plan. Create a free ScreenshotNeo account to try it without a card.

Frequently Asked Questions

Which Puppeteer option captures the entire page?

Use fullPage: true in page.screenshot(); it requests the full scrollable document rather than the current viewport.

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.

Can Puppeteer screenshot a page without saving a file?

Yes. Omit path to receive a Uint8Array, or set encoding: 'base64' to receive a base64 string.

Does JPEG quality affect PNG screenshots?

No. The 0–100 quality setting applies to JPEG output and does not apply to PNG.

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.