ScreenshotNeo

BlogEngineering

What captureBeyondViewport Does in Chrome DevTools Protocol

Learn exactly how captureBeyondViewport works, when Chromium uses it for full-page screenshots, how clips and browser versions change the result, and how to troubleshoot it.

By the ScreenshotNeo team29 September 20269 min read

What captureBeyondViewport Does in Chrome DevTools Protocol

Short answer: captureBeyondViewport is an optional Boolean parameter of the Chrome DevTools Protocol method Page.captureScreenshot. When set to true, it asks Chromium to capture content outside the visible viewport. The documented default is false. The flag does not resize the browser window and does not, by itself, promise a full-page image in every CDP implementation.

In the Chromium implementation covered by the protocol sources, the full-page path is selected only when fromSurface is true, captureBeyondViewport is true, and the caller has not supplied a clip. Chromium then measures the document, builds a clip covering those dimensions, and captures it. That behavior is implementation and version specific, so treat the protocol reference as the contract and verify the browser build you operate.

What the parameter means

The official Page.captureScreenshot reference describes the field as: “Capture the screenshot beyond the viewport. Defaults to false.” It is a Boolean switch. It is not a width, height, zoom, or window-management option.

CDP returns screenshot bytes as a base64-encoded data field. The same method accepts an image format of png, jpeg, or webp; PNG is the default. For JPEG, quality is an integer from 0 to 100. These output settings are independent of whether the capture extends beyond the viewport.

Does true mean “full page”?

Sometimes, in Chromium. The flag’s protocol description is deliberately narrower: it requests capture outside the viewport. In the cited Chromium PageHandler implementation, a full-page branch runs when all three conditions hold:

A capture pipeline can prepare the page before producing the final image.
A capture pipeline can prepare the page before producing the final image.
  1. fromSurface is true (the implementation defaults it to true when omitted).
  2. captureBeyondViewport is true.
  3. No initial clip was provided.

Chromium then asks the main frame for full-page dimensions, creates a clip beginning at x=0 and y=0 with scale 1, and performs the screenshot with beyond-viewport capture enabled. This is the strongest way to explain “full page” for the cited Chromium revision. It should not be presented as a universal promise for every CDP server, browser fork, or future revision.

The same implementation checks the resulting dimensions and returns an error when either dimension reaches the revision-specific guard described in the source (128 × 1024 pixels). This is source-level behavior, not a portable CDP limit. Browser updates can change it.

How clip changes the result

clip requests a particular rectangle. It contains x, y, width, height, and an optional scale. A clip is useful for a component, a known region, or a crop that must remain stable between runs.

The flag, a full-page capture and an explicit clip represent different capture scopes.
The flag, a full-page capture and an explicit clip represent different capture scopes.

The cited Chromium full-page branch requires that the caller did not provide a clip. Therefore, do not assume that captureBeyondViewport: true overrides an explicit clip. If you send both, the requested region can prevent the automatic full-page path. Decide which behavior you want:

Goal Recommended request
Visible viewport only Omit the flag or set it to false; omit clip.
Chromium full-page path Set fromSurface: true, captureBeyondViewport: true, and omit clip.
Exact rectangle Provide clip; do not rely on automatic full-page behavior.
Element screenshot Measure the element, then send its bounds as clip.

Raw CDP example with Node.js

The following script connects to a Chrome instance exposing the DevTools endpoint, navigates to a page, waits for it to load, calls Page.captureScreenshot, decodes the returned base64 data, and writes a PNG. Install the CDP client with npm install chrome-remote-interface.

const CDP = require('chrome-remote-interface');
const fs = require('node:fs');

(async () => {
  const client = await CDP({ port: 9222 });
  const { Page, Runtime } = client;

  try {
    await Page.enable();
    await Page.navigate({ url: 'https://example.com' });
    await Page.loadEventFired();

    // The full-page branch described in Chromium requires these values
    // and no clip.
    const result = await Page.captureScreenshot({
      format: 'png',
      fromSurface: true,
      captureBeyondViewport: true
    });

    fs.writeFileSync('example-full.png', Buffer.from(result.data, 'base64'));
  } finally {
    await client.close();
  }
})();

Start Chrome with a debugging port before running it, for example:

google-chrome --headless --remote-debugging-port=9222 --disable-gpu

In production, add your own navigation readiness condition. loadEventFired only means the load event arrived; JavaScript-rendered content, web fonts, images, and lazy sections may still be changing.

Capturing a fixed clip

const result = await Page.captureScreenshot({
  format: 'jpeg',
  quality: 85,
  fromSurface: true,
  captureBeyondViewport: true,
  clip: { x: 0, y: 500, width: 1200, height: 800, scale: 1 }
});

Here the clip is the controlling region. The beyond-viewport flag does not turn this into a document-wide capture.

Equivalent request shape in Python

CDP uses HTTP for discovery and a WebSocket for commands. This example uses requests and websocket-client. Install them with pip install requests websocket-client. Chrome must be running with --remote-debugging-port=9222.

import base64
import json
import requests
import websocket

version = requests.get('http://127.0.0.1:9222/json/version', timeout=10).json()
ws = websocket.create_connection(version['webSocketDebuggerUrl'], timeout=30)
next_id = 0

def call(method, params=None):
    global next_id
    next_id += 1
    ws.send(json.dumps({'id': next_id, 'method': method, 'params': params or {}}))
    while True:
        message = json.loads(ws.recv())
        if message.get('id') == next_id:
            if 'error' in message:
                raise RuntimeError(message['error'])
            return message.get('result', {})

try:
    call('Page.enable')
    call('Page.navigate', {'url': 'https://example.com'})
    result = call('Page.captureScreenshot', {
        'format': 'png',
        'fromSurface': True,
        'captureBeyondViewport': True
    })
    with open('example-full.png', 'wb') as image:
        image.write(base64.b64decode(result['data']))
finally:
    ws.close()

For a real application, wait for a page-specific condition instead of assuming that the first navigation response means the page is visually complete. You can use Runtime.evaluate to check a selector, or poll a state exposed by the page.

Using cURL with the DevTools endpoint

cURL cannot complete the WebSocket command sequence by itself, but it is useful for checking that Chrome is reachable and discovering targets:

curl http://127.0.0.1:9222/json/version
curl http://127.0.0.1:9222/json/list

The returned webSocketDebuggerUrl is the endpoint a CDP client must use. A WebSocket-capable library is required to send Page.captureScreenshot and receive its base64 response.

fromSurface

The cited Chromium full-page condition requires surface capture. Set fromSurface: true explicitly when you depend on that path. Leaving defaults implicit makes upgrades harder to diagnose.

format and quality

Use PNG for lossless output and crisp text. Use JPEG when file size matters and photographic content tolerates compression. Use WebP when your downstream system accepts it. quality applies to JPEG and ranges from 0 to 100; it does not change page dimensions or scrolling behavior.

Viewport and device scale

The viewport still determines layout. A full-page capture extends the image through the document, but it does not make a responsive page use a desktop layout. Set the viewport before navigation when you need deterministic breakpoints. Device scale settings affect raster density and output size; keep them stable between runs.

Timing and lazy content

Full-page capture can expose sections that were never visible. Pages often load those sections lazily, so scroll or trigger the application’s loading behavior before capture. Wait for a selector, network idle, image completion, or a page-specific “ready” flag. A screenshot taken too early can be valid CDP output while still missing content.

Experimental status and version handling

The pinned Chromium protocol definition marks captureBeyondViewport experimental. The live protocol documentation is rolling, and Chromium’s protocol definitions are tied to browser revisions. Check the protocol exposed by the browser build you actually deploy instead of assuming that the current tot page exactly matches an older bundled Chromium.

At startup, record the browser version from /json/version. In CI, keep the browser revision pinned when screenshot diffs matter. If a browser update changes full-page output, first compare protocol support, viewport metrics, device scale, and page timing before changing application code.

Common errors and fixes

Symptom Likely cause Fix
Only the visible viewport is captured The flag is false or omitted, fromSurface is false, or a clip was supplied. Set fromSurface: true and captureBeyondViewport: true; remove clip for the Chromium full-page path.
“Method not found” or unknown parameter The target browser or protocol version does not support the field. Inspect the deployed browser revision and its protocol definition; upgrade or use a viewport/scroll stitching fallback.
Content is missing below the fold Lazy loading or asynchronous rendering has not finished. Trigger loading and wait for a deterministic selector or readiness condition before capture.
Screenshot dimensions error The document exceeds a revision-specific Chromium guard. Capture sections with explicit clips, reduce scale, or stitch smaller captures. Do not assume the cited guard is universal.
Unexpected crop or offset An explicit clip, page zoom, transforms, or changing layout metrics altered the coordinate system. Remove the clip for full-page mode, set stable viewport and scale values, and inspect layout metrics.
Blank or partially painted image Capture happened before navigation, fonts, or rendering completed. Wait for page readiness and verify the target remains attached before calling the method.
WebSocket disconnects Chrome exited, the target closed, or the client timed out. Keep the browser process alive, select a fresh target, add bounded retries, and log the browser version and CDP error.

Performance and reliability guidance

  • Control page size. Full-page images grow with document height and device scale. Prefer WebP or JPEG for large photographic pages, and PNG for text-heavy archival output.
  • Use clips when you need only a component. Smaller captures reduce encoding time, memory, and transfer size.
  • Make readiness explicit. A fixed selector or application state is more repeatable than a large arbitrary sleep.
  • Retry only transient failures. Repeating a deterministic protocol error wastes resources; retry browser startup, target closure, and network navigation failures with a limit.
  • Keep evidence. Log URL, viewport, device scale, format, browser revision, and whether a clip was sent. These fields explain most screenshot diffs.
  • Watch memory. A very tall raster can pressure the browser process even when the protocol call is correct. Split extremely long documents into clips when operational limits matter.

Or skip the browser setup

ScreenshotNeo exposes a GET endpoint that handles the browser capture workflow for you. See the ScreenshotNeo API documentation for the available parameters. A basic request is:

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)
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}`);

ScreenshotNeo supports full-page capture, lazy-image loading, element selectors, custom viewports and device presets, dark mode, retina scale, custom CSS and JavaScript, click actions, selector hiding, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, caching, PDFs, signed links, asynchronous jobs, bulk capture, usage reporting, and an OpenAPI specification. Its parameter names are compatible with those used by many screenshot APIs, which can simplify migration.

Cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, timeouts, failed loads, and cache hits are never billed. Responses identify the result with X-Page-Verdict and X-Billed headers. ScreenshotNeo also provides 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 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Is captureBeyondViewport required for every full-page screenshot?

No. It is the switch used by the cited Chromium implementation’s automatic full-page path. Other tooling may implement full-page screenshots by resizing, scrolling and stitching, or by using a different browser API.

Can I combine it with a clip?

You can send both fields, but the cited Chromium full-page branch requires no initial clip. Use an explicit clip when the rectangle matters.

Does it scroll the page?

The parameter describes capture beyond the viewport; it is not a command to simulate user scrolling. Lazy content may still require separate preparation.

Which image format should I choose?

PNG preserves text without lossy compression. JPEG can be smaller for photos and accepts a quality value. WebP is useful when supported by your storage and delivery pipeline.

Why did a browser upgrade change my image?

The protocol reference and implementation can change with the Chromium revision. Compare the browser version, layout metrics, device scale, timing, and clip settings before treating the difference as an application regression.

Key points to remember

  • captureBeyondViewport is an optional Boolean on Page.captureScreenshot and defaults to false.
  • In the cited Chromium implementation, automatic full-page capture also needs fromSurface: true and no supplied clip.
  • The field is experimental in the pinned protocol definition; verify support in your target browser.
  • Format and JPEG quality control encoding, not whether the capture extends beyond the viewport.
  • For deterministic automation, make viewport, readiness, browser revision, and clipping explicit.