ScreenshotNeo

BlogHow-to

How to Send CDP Commands with Puppeteer

Create a Puppeteer CDP session with page.createCDPSession(), send protocol commands with client.send(), handle events, and detach cleanly.

By the ScreenshotNeo team4 October 20267 min read

To send a Chrome DevTools Protocol (CDP) command with Puppeteer, create a CDP session for the page and call send() with the protocol method name and its parameters:

const client = await page.createCDPSession();
const result = await client.send('Runtime.evaluate', {
  expression: '1 + 1',
});
console.log(result);
await client.detach();

page.createCDPSession() returns a promise for a session attached to that page. client.send() returns a promise for the command result. Use the method name and parameter names supported by the Chrome version you run. Puppeteer’s protocol APIs can change, so check the documentation for your installed version. See the Page.createCDPSession(), CDPSession.send(), and CDPSession references.

1. Install Puppeteer and send your first CDP command

This runnable example launches a browser, opens a page, creates a session, sends Runtime.evaluate, prints the result, and closes the browser. Run it in a project with Node.js installed:

npm install puppeteer

Save the following as cdp-example.js and run node cdp-example.js:

const puppeteer = require('puppeteer');

async function main() {
  const browser = await puppeteer.launch({ headless: true });
  let client;

  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    client = await page.createCDPSession();
    const result = await client.send('Runtime.evaluate', {
      expression: 'document.title',
      returnByValue: true,
    });

    console.log(result.result.value);
  } finally {
    if (client) await client.detach();
    await browser.close();
  }
}

main().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

The result of Runtime.evaluate is a protocol response object. With returnByValue: true, the evaluated value is available at result.result.value for serializable values such as strings and numbers. For other commands, inspect that command’s protocol response shape.

2. Create a CDP session for the right target

Start from the Puppeteer Page whose browser target should receive the command:

const page = await browser.newPage();
const client = await page.createCDPSession();

Then use the session for commands and, when needed, protocol events:

const response = await client.send('Domain.commandName', {
  parameterName: 'value',
});

client.on('Domain.eventName', event => {
  console.log('Protocol event:', event);
});

Domain.commandName, the parameter names, and the event name above are placeholders. Replace them with names from the protocol definition for the browser you are controlling. Some protocol domains require an enable command before their events are delivered; check that domain’s protocol documentation and send its enable command when required.

Older examples may use page.target().createCDPSession(). Puppeteer marks Page.target() obsolete and directs users to Page.createCDPSession() for creating the page session. See the Page.target() reference.

3. Send commands, read results, and handle failures

Await send() so your code can use the command response and catch protocol or transport failures:

try {
  const response = await client.send('Runtime.evaluate', {
    expression: 'location.href',
    returnByValue: true,
  });
  console.log(response.result.value);
} catch (error) {
  console.error('CDP command failed:', error);
}

A command is identified by a string such as Runtime.evaluate. The second argument is the command’s parameter object; omit it when that command takes no parameters. The returned value is the protocol method’s mapped result. Consult the Puppeteer reference and the protocol definition for the command you intend to use rather than guessing argument names.

Keep evaluation semantics in mind

Runtime.evaluate evaluates JavaScript in the target runtime. Its response is not simply the raw value: it includes a remote object description under result. Setting returnByValue: true asks for a serializable value to be returned by value. For expressions that throw, inspect the response for exception details as well as handling rejected promises. Avoid assuming that every evaluated result is JSON-serializable.

4. Listen for CDP events

Subscribe with client.on(eventName, handler). For an event domain that requires setup, send its enable command before relying on events. This example follows Puppeteer’s documented Animation-domain flow:

const client = await page.createCDPSession();

client.on('Animation.animationCreated', event => {
  console.log('Animation created:', event.id);
});

await client.send('Animation.enable');
const playback = await client.send('Animation.getPlaybackRate');
console.log('Playback rate:', playback.playbackRate);
await client.send('Animation.setPlaybackRate', {
  playbackRate: playback.playbackRate,
});

Event payload fields and command parameters are protocol-defined. Check the relevant reference for the browser version in use. Register listeners before enabling the domain if you need to observe events that can arrive as soon as it is enabled. Remove listeners when they are no longer needed, or detach the session when the work is complete.

5. Detach and manage session lifecycle

Detach a session when you have finished using it:

await client.detach();

After detachment, the session no longer emits events and cannot send commands. Detach in a finally block when a session is scoped to one operation, as in the runnable example. If the browser or target closes first, commands may fail because their session is no longer usable. Do not continue to use a detached session; create a new one from the page if the page and browser are still available.

6. Common errors and fixes

Symptom Likely cause What to do
page.createCDPSession is not a function The value is not a Puppeteer Page, or the installed Puppeteer version/API differs from the example. Confirm how the page was obtained and check the API reference matching the installed Puppeteer version.
Unknown method or protocol error The method name is misspelled, the command is unsupported by the browser version, or its domain is unavailable. Verify the exact protocol method and browser support. Check that the command belongs to the protocol exposed by the browser you launched or connected to.
Invalid parameters A required field is missing, a field name is wrong, or a value has the wrong type. Compare the object passed to send() with the command definition. Protocol parameter spelling and types matter.
No events arrive The domain was not enabled, the listener was attached after the event, or the selected target is not the one generating it. Attach the listener, send the domain’s enable command when required, and verify the session is attached to the intended page.
Session detached or target closed The session was detached, or its page/browser closed before the command completed. Keep the page alive for the operation and do not reuse a detached session. Create a fresh session when appropriate.
Evaluation result has no expected value The expression returned a non-serializable object, raised an exception, or the code read the wrong response field. Inspect the full response, use returnByValue: true for serializable values, and check exception details.
Examples using page.target() The snippet uses the obsolete page-target pattern. Use await page.createCDPSession() in current code.

7. Performance, reliability, and cost considerations

  • Reuse a session for related commands. Create one session for a set of operations on a page instead of repeatedly creating and detaching sessions between adjacent commands.
  • Await commands that depend on one another. A protocol command returns a promise. Sequential awaits make ordering explicit; only run independent commands concurrently when their effects and protocol semantics allow it.
  • Handle browser and page lifecycle. Close pages or browsers after work, detach sessions that are no longer needed, and catch errors when targets can close while work is running.
  • Use protocol-specific timeouts and recovery at the application level. A CDP command does not make page navigation, resource loading, or the browser itself reliable. Decide how your application should handle slow pages, retries, and partial work.
  • Check compatibility before depending on a command. Puppeteer API references and browser protocol support vary by release. Pin and update dependencies deliberately, and consult the reference for the version you use.
  • Account for browser operations in your own infrastructure. The code runs a browser process, so resource use and cost depend on your runtime, concurrency, and capture workload. The research sources provide no benchmark or fixed cost figure for CDP commands.

8. Or skip the browser setup

If your goal is a website screenshot rather than custom protocol control, ScreenshotNeo provides a screenshot API and MCP server. It does not replace arbitrary CDP commands; it handles screenshot and PDF capture through one request. See the ScreenshotNeo API documentation.

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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo removes known consent banners, newsletter popups, and chat widgets before capture; those cleanup steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents use screenshot and PDF tools. The Free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Sign up for the free plan and try 1,000 screenshots a month with no card.

9. Frequently asked questions

What does CDP stand for?

CDP stands for Chrome DevTools Protocol, the protocol used for browser commands and events exposed through a Puppeteer CDPSession.

Can I use one CDP session for multiple commands?

Yes. Send related commands through the same attached session, and detach when you are finished with that work.

Does a CDP session replace Puppeteer’s normal Page methods?

No. Use Puppeteer’s page APIs for ordinary browser automation and a CDP session when you need a protocol command or event that fits your task.

Where can I check whether a command is supported?

Check the protocol definition and Puppeteer API documentation for the browser and Puppeteer versions in your project. The command surface may differ across versions.