How to Capture a Screenshot Clip with Puppeteer
Use Puppeteer’s clip option to capture a fixed page region, or screenshot a DOM element when its bounds should define the crop.
Use page.screenshot() with a clip rectangle to capture a fixed region of a page. The rectangle uses page coordinates: x and y locate its top-left corner, while width and height set its size.
await page.screenshot({
path: 'clip.png',
clip: { x: 40, y: 80, width: 640, height: 360 },
});
Use ElementHandle.screenshot() when you want an element’s current bounds to determine the crop. It scrolls the element into view when needed. Puppeteer’s ScreenshotOptions, ScreenshotClip, and screenshots guide document these APIs.
1. Capture a fixed rectangular region
This runnable ES module opens a page, captures a region, saves it as a PNG, and closes the browser even if navigation or capture fails.
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: 'clip.png',
clip: { x: 40, y: 80, width: 640, height: 360 },
});
} finally {
await browser.close();
}
Save the example as capture.mjs, install Puppeteer in your project, then run it with Node.js. Puppeteer downloads a compatible browser during installation unless your environment is configured to use a separate browser installation.
The clip option is a rectangle with x, y, width, and height. Its optional scale defaults to 1. When a clip is supplied, captureBeyondViewport defaults to true, so the rectangle can extend outside the current viewport. See the ScreenshotClip reference and ScreenshotOptions reference.
Choose the coordinates deliberately
xandymark the clip’s top-left position in page coordinates.widthandheightdetermine the rectangle’s dimensions. They must describe a usable, non-zero region.- Measure coordinates after the page has reached the layout you want to capture. Responsive breakpoints, fonts, animations, and late-loading content can change element positions.
- Use
scalewhen you need to adjust the clip’s output scale. It defaults to1; account for the resulting image dimensions in downstream processing.
2. Capture an element instead of calculating a rectangle
If the crop should match an element’s bounds, select the element and call its screenshot method. This is usually easier to maintain than hard-coding coordinates when the page layout can change.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
const element = await page.waitForSelector('.target');
if (!element) throw new Error('Could not find .target');
await element.screenshot({ path: 'element.png' });
} finally {
await browser.close();
}
ElementHandle.screenshot() scrolls the element into view if necessary. It throws if the element has been detached from the DOM, so avoid keeping a handle across actions that replace or rerender the target. See the ElementHandle.screenshot() API.
3. Save the result or use the returned image data
Set path to write an image file. Without path, page.screenshot() returns image bytes as a Uint8Array by default. Set encoding: 'base64' if you need a base64 string instead. The return behavior is documented in the Page.screenshot() reference.
const bytes = await page.screenshot({
clip: { x: 40, y: 80, width: 640, height: 360 },
});
// Example: write the returned Uint8Array to a file.
import { writeFile } from 'node:fs/promises';
await writeFile('clip.png', bytes);
For base64 output, specify encoding: 'base64' and handle the returned string as base64 data. Keep the chosen output format and file extension consistent when you process or distribute the image.
4. Decide between a clip and an element screenshot
| Approach | Use it when | What defines the crop |
|---|---|---|
page.screenshot({ clip }) |
You need a fixed rectangle or a region that is not represented by one element. | The explicit page-coordinate rectangle. |
element.screenshot() |
You need to capture one DOM element and want its current bounds. | The element’s bounds; Puppeteer scrolls it into view if needed. |
For a full-page screenshot rather than a crop, use the full-page screenshot option described in Puppeteer’s screenshots guide. Screenshot options may not all be supported across every protocol. Puppeteer’s WebDriver BiDi compatibility guide lists supported screenshot parameters and cautions that support varies; check the protocol documentation if you use BiDi or depend on a less common option.
5. Troubleshoot common capture problems
| Symptom | Likely cause | What to try |
|---|---|---|
| The image is offset or captures the wrong region. | The clip coordinates were measured for a different viewport, layout, or page state. | Set the viewport before navigation or measurement, wait for the target layout, then recalculate x and y. |
| The image is smaller or larger than expected. | The rectangle dimensions or optional clip scale do not match the intended output. | Check width, height, and scale; compare the resulting image dimensions with your target. |
| The element screenshot times out waiting for a selector. | The selector is wrong, the page has not rendered the element, or the target is conditional. | Confirm the selector in the rendered page, wait for the relevant state, and handle a missing element explicitly. |
| Element capture fails because the node was detached. | A framework rerender or navigation replaced the selected node. | Wait for the new render and select the element again immediately before capture. |
| The captured content is blank or incomplete. | Navigation completed before client-side rendering, fonts, or images were ready. | Wait for the page condition your content needs, such as a selector becoming visible, before taking the screenshot. |
| A screenshot call appears to block page cleanup or concurrent work. | Page lifecycle operations may wait for an in-progress screenshot. | Account for capture completion when closing or opening pages in a shared BrowserContext. The Page API documents that newPage() and close() wait for an in-progress screenshot, while bringToFront() does not. |
6. Performance, reliability, and cost
Clipping limits the captured region, but it does not eliminate the cost of launching a browser, loading the page, or waiting for its content. Reuse a browser process for multiple captures when appropriate, close pages and browsers when finished, and choose a readiness condition that reflects the content you need. Waiting for every network request can be slow on pages with long-lived connections; waiting for a specific selector can be more predictable for a particular capture.
For repeatable crops, keep viewport size, page state, and clip coordinates consistent. For changing layouts, prefer an element screenshot so the target’s current bounds determine the crop. If captures run concurrently, manage page lifecycle and screenshot completion deliberately. Puppeteer’s API does not establish a universal capture time or price: those depend on the page, runtime, and infrastructure you provide.
7. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Make one GET request with a URL to get an image or PDF, without setting up Puppeteer and a browser for this capture. See the ScreenshotNeo API documentation for options and configuration.
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 request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
- Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers report the page verdict and billing status.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - The Free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.
Sign up for 1,000 free screenshots a month, with no card.
8. FAQ
Can a Puppeteer clip extend beyond the viewport?
Yes. When clip is supplied, captureBeyondViewport defaults to true.
Does Puppeteer save the screenshot automatically?
Only when you provide a path. Otherwise, page.screenshot() returns image data.
Should I use a clip or an element screenshot for a card?
Use an element screenshot if the card’s DOM bounds are the intended crop. Use a clip if you need an exact fixed rectangle independent of an element.
Do all screenshot options work with WebDriver BiDi?
Do not assume that they do. Consult Puppeteer’s BiDi compatibility guide for the parameters it supports.


