ScreenshotNeo

BlogComparisons

Puppeteer Screenshot vs. Chrome DevTools `captureBeyondViewport`

Understand how Puppeteer’s `fullPage` and `captureBeyondViewport` options differ from Chrome DevTools Protocol’s screenshot parameter, with runnable examples.

By the ScreenshotNeo team4 October 20268 min read

Puppeteer and Chrome DevTools Protocol (CDP) both expose a captureBeyondViewport setting, but it does not mean the same thing as a guaranteed full-page screenshot. In Puppeteer, use page.screenshot({ fullPage: true }) when you want the documented full-page behavior. Use captureBeyondViewport to control whether a capture can extend beyond the visible viewport. In CDP, Page.captureScreenshot has a similarly named parameter, but the protocol documentation does not define it as equivalent to Puppeteer’s fullPage.

This distinction matters most when you use a clip rectangle or call CDP directly. The APIs document their options and defaults, but do not promise identical behavior across all Puppeteer and Chrome versions or all page layouts. Check results against the versions and pages your application actually captures.

Which option should you use?

Goal Use What the docs establish
Capture the entire page with Puppeteer page.screenshot({ fullPage: true }) Puppeteer documents fullPage as taking a full-page screenshot when true.
Capture beyond the viewport with Puppeteer captureBeyondViewport It captures beyond the viewport. Its documented default is conditional: false without a clip and true with a clip.
Capture beyond the viewport through CDP Page.captureScreenshot with captureBeyondViewport: true CDP documents this parameter with a default of false. It also accepts a clip rectangle.
Capture a particular element in Puppeteer elementHandle.screenshot() Puppeteer provides a separate element helper and tries to scroll a hidden element into view by default.

The CDP command reference does not list a fullPage parameter. Do not assume that setting CDP’s captureBeyondViewport produces the same result as Puppeteer’s full-page option.

What the options mean

Puppeteer: fullPage

fullPage: true is the Puppeteer option documented for taking a full-page screenshot. It is the clearest choice when your requirement is the entire page rather than a particular viewport or region.

Puppeteer: captureBeyondViewport

This option controls capture beyond the viewport. Puppeteer documents a conditional default: it is false when there is no clip, and true when a clip is supplied. Set it explicitly when that behavior matters, especially when changing clipping options.

CDP: Page.captureScreenshot

CDP exposes the lower-level Page.captureScreenshot command. Its captureBeyondViewport parameter defaults to false, and the command accepts a clip rectangle. The cited protocol documentation describes capturing beyond the viewport; it does not say that this parameter is a full-page mode.

Runnable Puppeteer examples

Install Puppeteer in a Node.js project with npm install puppeteer. These examples use an example target URL; replace it with a page you are allowed to capture.

Capture a full page

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'networkidle2' });
    await page.screenshot({ path: 'full-page.png', fullPage: true });
  } finally {
    await browser.close();
  }
})();

This uses fullPage: true for the documented full-page intent. The example waits for network activity to become idle, but a site with persistent requests may not reach that state; choose a navigation or explicit wait strategy appropriate to the page.

Capture a clipped region and set the viewport-overflow behavior explicitly

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    await page.screenshot({
      path: 'region.png',
      clip: { x: 0, y: 0, width: 900, height: 1200 },
      captureBeyondViewport: true,
    });
  } finally {
    await browser.close();
  }
})();

A clip describes the region to capture. Setting captureBeyondViewport explicitly avoids relying on Puppeteer’s conditional default. The exact output for unusual viewport, clip, and page combinations should be checked with your pinned versions.

Capture an element

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    const element = await page.waitForSelector('main');
    if (!element) throw new Error('Could not find main element');
    await element.screenshot({ path: 'main.png' });
  } finally {
    await browser.close();
  }
})();

ElementHandle.screenshot() is a separate API from both page-level options. Puppeteer says it attempts to scroll a hidden element into view by default.

Call Chrome DevTools Protocol directly

Use CDP when you need the protocol command itself or already manage a CDP session. The following Node.js example uses Puppeteer to open a page and send Page.captureScreenshot directly. It decodes the returned base64 image data and writes a PNG file.

const fs = require('node:fs');
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    const cdp = await page.createCDPSession();
    const result = await cdp.send('Page.captureScreenshot', {
      format: 'png',
      captureBeyondViewport: true,
      clip: { x: 0, y: 0, width: 900, height: 1200, scale: 1 },
    });
    fs.writeFileSync('cdp-region.png', Buffer.from(result.data, 'base64'));
    await cdp.detach();
  } finally {
    await browser.close();
  }
})();

This is a clipped capture beyond the viewport, not a claim that CDP’s flag is equivalent to Puppeteer’s full-page option. CDP’s documented default for captureBeyondViewport is false, so the example sets it explicitly.

cURL, Python, and Node.js without browser setup

If your actual goal is to get a page image from an API rather than control Puppeteer or CDP, ScreenshotNeo provides a website screenshot API and MCP server. These calls request a WebP screenshot of the example page. See the ScreenshotNeo API documentation for request options.

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(`Screenshot request failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', bytes);

Or skip the browser setup

ScreenshotNeo takes a screenshot with one GET request. Before capture, it accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots.

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

Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. AI agents can take screenshots through the MCP server. Start with 1,000 free screenshots a month, with no card.

Choosing between the approaches

  1. Need a full document capture in Puppeteer? Start with fullPage: true.
  2. Need a clipped region that extends outside the viewport? Set captureBeyondViewport explicitly and define a clip.
  3. Need CDP directly? Use Page.captureScreenshot; set captureBeyondViewport to true if the region should extend beyond the viewport.
  4. Need one element? Use Puppeteer’s ElementHandle.screenshot().
  5. Need screenshots without maintaining browser setup? Use an API call such as ScreenshotNeo’s, or its MCP tools for an agent workflow.

Edge cases and limits of the available guarantees

  • Clips change the Puppeteer default. With no clip, the documented default for captureBeyondViewport is false; with a clip, it is true. Make it explicit when adding or removing a clip.
  • CDP and Puppeteer expose different option surfaces. CDP’s cited command entry does not list fullPage. Treat the APIs as distinct rather than translating options by name.
  • Lazy-loaded content may not be present yet. The cited references do not promise that lazy content is loaded before capture. If the page loads content on scroll, arrange for it to load before capturing and verify the resulting image.
  • Version combinations matter. The references do not offer an exhaustive Puppeteer/Chrome compatibility matrix. Pin versions and check captures after browser upgrades.
  • Page dimensions and unusual layouts. The references do not establish universal maximum dimensions or exhaustive behavior for every viewport and clip geometry. Test pages with very long documents or unusual rendering.

Performance, reliability, and cost

A local Puppeteer or CDP capture requires a running browser and page navigation, so account for browser startup, page loading, and your own retry and timeout policy in application design. The cited references do not provide benchmark figures, maximum page sizes, or timing guarantees; measure with representative pages and the versions you deploy. Avoid treating a single readiness signal as proof that every image or dynamically inserted element has rendered.

For reliability, handle navigation and selector failures, close the browser in a finally block, and keep the screenshot operation bounded by an application timeout. If using CDP, detach the session when finished. For costs, local Puppeteer/CDP have no per-shot API charge stated in these references, but you operate the compute and browser environment. ScreenshotNeo’s published plan amounts are: Free, 1,000 shots/month; Starter, $5 for 3,000; Growth, $15 for 15,000; Pro, $39 for 60,000; Scale, $99 for 250,000; Business, $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Only clean shots are billed; response headers identify the verdict and billing status.

Troubleshooting

Symptom Likely cause What to do
The image covers only the visible viewport The code used a normal viewport capture or expected captureBeyondViewport to mean full page. For Puppeteer’s documented full-page behavior, set fullPage: true. For CDP, define the intended clip and set captureBeyondViewport: true when needed.
A clipped Puppeteer screenshot changes after adding a clip Puppeteer documents a different default for captureBeyondViewport when a clip is present. Set the option explicitly and compare against the intended clip.
CDP capture does not extend past the viewport The CDP parameter defaults to false or was omitted. Pass captureBeyondViewport: true and verify the clip and target Chrome behavior.
An element screenshot fails because the selector was not found The page had not rendered the target element, or the selector does not match. Wait for the selector, verify it against the page, and handle the missing-element case as in the example.
The screenshot is missing images or dynamically loaded content The page may load that content after navigation or only after scrolling; the cited docs do not guarantee lazy-content completeness. Wait for the relevant content or trigger its loading before capture, then inspect the output for the target page.
Behavior differs after a browser upgrade The references do not provide a complete version-by-version behavior matrix. Pin the Puppeteer and Chrome versions and run a capture check on representative pages during upgrades.

Frequently asked questions

Does captureBeyondViewport: true always take a full-page screenshot?

No such universal guarantee appears in the cited documentation. Puppeteer documents fullPage: true for its full-page intent; CDP documents a beyond-viewport parameter.

Should I set both Puppeteer options?

Use the option that expresses your requirement. For a full-page screenshot, set fullPage: true. For a clipped region extending beyond the viewport, set captureBeyondViewport explicitly. The docs do not establish that setting both is necessary.

Can Puppeteer capture just one element?

Yes. Use ElementHandle.screenshot(). Puppeteer says it attempts to scroll a hidden element into view by default.

Where are the official references?

See Puppeteer’s ScreenshotOptions, screenshot guide, ElementHandle.screenshot(), and the Chrome DevTools Protocol entry for Page.captureScreenshot. For API-based screenshots, visit ScreenshotNeo and its API docs.