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

Use Puppeteer’s ElementHandle.screenshot() to capture one rendered DOM element. Query the element with page.$(), verify that it exists, wait for your application’s content to be ready, then call element.screenshot() with a file path or output options. Puppeteer scrolls the element into view automatically; it throws if the handle is detached from the DOM.

Capture one element with Puppeteer

The following CommonJS script launches Chromium, opens a page, finds an element, saves it as a PNG, and cleans up both the handle and browser. It uses the current Puppeteer API documented for the 25.x line; pin the version used by your project so behavior and types remain reproducible.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

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

    const element = await page.$('#target');
    if (!element) {
      throw new Error('Target element not found: #target');
    }

    try {
      await element.screenshot({ path: 'element.png' });
      console.log('Saved element.png');
    } finally {
      await element.dispose();
    }
  } finally {
    await browser.close();
  }
})();

ElementHandle.screenshot() captures the rendered element and scrolls it into view when necessary. The path option writes the bytes to disk; a relative path is resolved from the process’s current working directory. If the selector matches nothing, page.$() returns null, so checking the result gives a useful error instead of a less specific failure later.

Install Puppeteer and run the script with:

npm install puppeteer
node element-shot.js

The method is documented at pptr.dev/api/puppeteer.elementhandle.screenshot. The related handle lifecycle is described in the ElementHandle class reference.

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

Make the capture deterministic

Element capture only guarantees the mechanics of finding, scrolling, and rasterizing the element. It does not know when your application has finished fetching data, decoding images, loading web fonts, or ending an animation. Wait for the condition that matters to your page before taking the shot.

Wait for the element and its content

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

await page.waitForSelector('[data-testid="sales-card"]', {
  visible: true,
  timeout: 30_000
});

// Prefer an application-specific readiness signal.
await page.waitForFunction(() => {
  const card = document.querySelector('[data-testid="sales-card"]');
  return card?.getAttribute('data-rendered') === 'true';
});

const card = await page.$('[data-testid="sales-card"]');
if (!card) throw new Error('Sales card disappeared before capture');
await card.screenshot({ path: 'sales-card.png' });
await card.dispose();

A selector wait confirms that a node exists, not that its data is correct. An application-owned attribute, a completed network request, or a deliberate short delay after a known animation is usually more reliable than an arbitrary global timeout. If images affect the result, wait for their complete state and for naturalWidth to be nonzero, or expose a page-level “ready” flag.

Handle dynamic rerenders

React, Vue, and other frameworks can replace a node while your script is holding its handle. A detached handle causes the documented screenshot error; Puppeteer does not promise an automatic retry. Query close to the capture, and retry by reacquiring the element only when your application expects a transient rerender.

async function captureWithReacquire(page, selector, path) {
  for (let attempt = 1; attempt <= 2; attempt++) {
    const handle = await page.$(selector);
    if (!handle) throw new Error(`No element matches ${selector}`);
    try {
      await handle.screenshot({ path });
      return;
    } catch (error) {
      if (attempt === 2 || !String(error.message).toLowerCase().includes('detached')) {
        throw error;
      }
    } finally {
      await handle.dispose();
    }
  }
}

Choose the output format and destination

The method returns a Uint8Array by default when no path is supplied. Request base64 with encoding: 'base64'. Supplying path saves the image and still lets you select format and quality through screenshot options.

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

Save PNG, JPEG, or WebP

await element.screenshot({ path: 'card.png', type: 'png' });
await element.screenshot({ path: 'card.jpg', type: 'jpeg', quality: 82 });
await element.screenshot({ path: 'card.webp', type: 'webp', quality: 82 });

Puppeteer infers the image type from the filename extension when possible. PNG is lossless and is the practical choice for text, diagrams, and transparency. JPEG and WebP can reduce file size when lossy compression is acceptable. The quality value ranges from 0 to 100 and does not apply to PNG. These options are defined in the ScreenshotOptions interface.

Keep the image in memory

const bytes = await element.screenshot();
await storageClient.put('card.png', bytes, { contentType: 'image/png' });

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

Use the byte result for object storage or an HTTP response. Base64 is convenient for JSON or data URIs but increases payload size, so avoid it for large images when binary transfer is available.

Transparent backgrounds

await element.screenshot({
  path: 'logo.png',
  omitBackground: true
});

omitBackground: true hides Puppeteer’s default white background. Transparency still depends on the element and its ancestors actually having transparent backgrounds; a solid CSS background remains visible.

Element scope versus page scope

Use ElementHandle.screenshot() when the desired region is one DOM element. Use Page.screenshot() for the viewport or the complete document. The page method supports the same core output settings and adds page-level controls.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Goal API Important options
One card, chart, component, or image element.screenshot() path, type, quality, omitBackground
Current viewport page.screenshot() viewport dimensions, format, clipping
Entire document page.screenshot({ fullPage: true }) fullPage, format, quality
Fixed rectangle page.screenshot({ clip }) clip, captureBeyondViewport

For page screenshots, fullPage defaults to false. The documented captureBeyondViewport default is false when no clip is provided and true when a clip is provided. A clip is useful when you need coordinates rather than a semantic DOM target, but coordinates are more sensitive to responsive layout changes.

Within a BrowserContext, Puppeteer waits for a screenshot to finish before creating or closing pages. page.bringToFront() does not wait for existing screenshot operations, so coordinate concurrent work deliberately. See the Page.screenshot() documentation.

TypeScript and typed element handles

The handle API accepts a generic element type, which lets TypeScript narrow DOM properties when you evaluate against the node.

import puppeteer, { ElementHandle } from 'puppeteer';

const canvas = await page.$<HTMLCanvasElement>('#chart');
if (!canvas) throw new Error('Chart canvas not found');

const dimensions = await canvas.evaluate(el => ({
  width: el.width,
  height: el.height
}));
await canvas.screenshot({ path: 'chart.png' });
await canvas.dispose();

Use a type such as HTMLDivElement or HTMLCanvasElement when it improves checks in your codebase; it does not change the pixels Puppeteer captures.

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

Troubleshooting common failures

“Target element not found”

  • Cause: The selector is wrong, the page has not navigated to the expected route, or the element is inside an iframe.
  • Fix: Verify the URL and selector, wait with page.waitForSelector(), and query the correct frame rather than the top-level page.

Detached-element error

  • Cause: A framework replaced or removed the node after you obtained the handle.
  • Fix: Wait for the application’s stable state, reacquire the handle immediately before capture, and retry only a bounded number of times.

Image is blank or incomplete

  • Cause: Data, fonts, lazy images, or transitions were still loading.
  • Fix: Wait for a page-specific ready signal, confirm image decoding, disable or finish animations in test CSS, and use a suitable navigation wait condition.

Unexpected format or file location

  • Cause: The extension, explicit type, or current working directory differs from what you assumed.
  • Fix: Set type explicitly, use an absolute path when needed, and inspect the process working directory.

Transparent output still looks white

  • Cause: An ancestor or the target itself has a white CSS background.
  • Fix: Remove that CSS background and keep omitBackground: true.

Performance, reliability, and operational practices

  • Reuse a browser instance for a batch of captures, but create isolated pages or contexts for unrelated sessions.
  • Close pages and dispose handles that remain in use; navigation and destruction of a parent context automatically dispose associated handles.
  • Capture after the smallest reliable readiness condition instead of waiting for an unnecessarily long global timeout.
  • Use PNG only where lossless pixels or alpha matter; choose JPEG or WebP with a tested quality setting for bandwidth-sensitive workflows.
  • Keep selectors stable with attributes such as data-testid rather than styling classes that change during redesigns.
  • Record the URL, selector, viewport, Puppeteer version, output type, and failure message so a bad capture can be reproduced.
  • Do not assume a successful API call means the application rendered correctly: validate dimensions, file type, and—when important—pixel or content expectations in your pipeline.
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 provides a website screenshot API and MCP server when you want one request instead of managing Chromium. Its clean-shot pipeline accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.

For a direct element capture, pass the target selector and other options supported by the API:

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

See the full parameter list and authentication details in the ScreenshotNeo documentation. The same endpoint supports full-page captures, dark mode, device presets, retina scale, custom CSS and JavaScript, clicks, waits, blocked requests, cookies, headers, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs, webhooks, bulk capture of up to 100 URLs per call, usage reporting, and PDF output.

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={
        "access_key": "YOUR_API_KEY",
        "url": "https://stripe.com",
        "selector": "#target",
    },
    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',
  selector: '#target'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo HTTP ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo includes an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Plans are $15 for 15,000 (Growth), $39 for 60,000 (Pro), $99 for 250,000 (Scale), and $249 for 1,000,000 (Business); yearly billing gives two months free, and every feature is included on every plan. Create a free ScreenshotNeo account to start with the 1,000-shot allowance.

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

FAQ

Does an element screenshot include content outside the element?

No. The capture is scoped to the rendered element’s bounds. Use a page screenshot with clip or fullPage when you need a larger region.

Can I capture an element that is off-screen?

Yes. Puppeteer scrolls the element into view before capturing it.

What happens if the element is removed during capture?

The documented behavior is an error for a detached element. Reacquire the handle after the page reaches a stable state.

Which Puppeteer version should production code use?

Pin and record the version installed by your project. The official references reviewed display 25.12.0 for the screenshot method and 25.10.0 for the ElementHandle class, so examples should not imply that every release has identical documentation labels.

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.

Frequently Asked Questions

Does an element screenshot include content outside the element?

No. The capture is scoped to the rendered element’s bounds. Use a page screenshot with clip or fullPage when you need a larger region.

Can I capture an element that is off-screen?

Yes. Puppeteer scrolls the element into view before capturing it.

What happens if the element is removed during capture?

The documented behavior is an error for a detached element. Reacquire the handle after the page reaches a stable state.

Which Puppeteer version should production code use?

Pin and record the version installed by your project. The official references reviewed display 25.12.0 for the screenshot method and 25.10.0 for the ElementHandle class, so examples should not imply that every release has identical documentation labels.

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.

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.