ScreenshotNeo

BlogHow-to

How to Send Commands with Puppeteer CDPSession

Create a Puppeteer CDP session, send Chrome DevTools Protocol commands, handle events and results, and troubleshoot common issues with runnable examples.

By the ScreenshotNeo team4 October 20266 min read

To send a Chrome DevTools Protocol (CDP) command in Puppeteer, create a session from a Page with page.createCDPSession(), then call and await client.send('Domain.command', params). Enable a protocol domain before using its events or commands when required, and detach the session when you are done.

Minimal runnable example

This ES module launches Chrome, creates a page-attached CDP session, evaluates a simple expression with the Runtime domain, prints the returned value, and closes the browser even if a command fails.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  const client = await page.createCDPSession();

  try {
    await client.send('Runtime.enable');
    const response = await client.send('Runtime.evaluate', {
      expression: '2 + 2',
      returnByValue: true,
    });
    console.log(response.result.value); // 4
  } finally {
    await client.detach();
  }
} finally {
  await browser.close();
}

Install Puppeteer in a project with npm install puppeteer, save the code as an .mjs file, and run it with Node.js. The Runtime.evaluate parameters and response fields shown are an example; check the protocol definitions for the command and browser version you use.

Create a session and send a command

  1. Launch Puppeteer and obtain a Page, for example with await browser.newPage().
  2. Call const client = await page.createCDPSession(). The method returns a Promise.
  3. Send the command using its protocol method string and any required parameters: await client.send('Domain.command', params).
  4. Use the resolved response if the command returns one. Many commands can be sent without parameters; omit the second argument in that case.
  5. Call await client.detach() when the session is no longer needed.

Current Puppeteer documentation describes Page.createCDPSession() as the page-level way to create a CDP session. client.send() accepts a method, optional parameters, and optional command options, and returns a Promise for the protocol response. In TypeScript, the method string, parameter object, and return type are connected through Puppeteer’s protocol mapping, which can help catch misspelled methods and incorrectly shaped parameters.

Choose the protocol method and parameters

CDP commands are grouped into domains, such as Runtime, Page, and Network. The command name and parameter object must match the protocol supported by the Chrome version behind Puppeteer. Consult the protocol definition for the command you need and verify its availability for your browser; Puppeteer’s TypeScript mapping does not guarantee that every command behaves identically across Chrome releases.

// No parameters
await client.send('Runtime.enable');

// Parameters are the second argument
const response = await client.send('Runtime.evaluate', {
  expression: 'document.title',
  returnByValue: true,
});

When a command has optional parameters, pass only the fields the protocol documents. When it returns a result, inspect the documented response shape rather than assuming all commands return the same fields. CDP is asynchronous: await each command whose completion or response matters, and handle rejected Promises.

Listen for CDP events

To receive events, enable the relevant domain, register a listener, then perform the action that can emit the event. This example follows the Animation event pattern in Puppeteer’s CDPSession documentation.

const client = await page.createCDPSession();

try {
  await client.send('Animation.enable');

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

  // Perform an action on the page that starts an animation here.
  // The listener can then receive Animation.animationCreated events.
} finally {
  await client.detach();
}

Register the listener before triggering the action so an early event is not missed. Event names and payloads are protocol-specific. Remove a listener if you need to stop handling an event while keeping the session alive; detach the session when all CDP work for that page is complete.

Session lifecycle and compatibility

Detach when finished

Use await client.detach() in a finally block when practical, so a thrown command does not leave the session attached. After detaching, the session no longer emits events and cannot send messages. Do not reuse that client; create a new session if you need to resume CDP work.

Prefer the current Page method

Use page.createCDPSession() in current code. Puppeteer marks Page.target() obsolete and recommends the direct Page method for creating a CDP session. Avoid copying older examples that create a session through page.target().

Check the installed versions

The API signatures and protocol support depend on the Puppeteer and browser versions in your project. The current API reference documents a typed send() method with an optional parameters object and command options. If a command fails despite compiling, check that the browser actually supports it and that the request uses the protocol’s expected parameter names and values.

Common errors and fixes

Symptom Likely cause Fix
client.send is not a function The value is not a CDPSession, or session creation was not awaited. Use const client = await page.createCDPSession() and confirm that page is a Puppeteer Page.
Unknown method or protocol error The method string is misspelled, the domain was not enabled where required, or the browser does not support that command. Check the exact method and domain in the protocol documentation for the Chrome version in use. Enable the domain when the command requires it.
Invalid parameters or missing fields The second argument does not match the command’s protocol schema. Check the command’s parameter definition. Pass the documented field names and types, and omit fields that are optional and not needed.
Code continues before the command finishes The Promise returned by send() was not awaited or returned. Use await client.send(...) inside an async function, or return the Promise to the caller.
No event arrives The domain may not be enabled, the listener may have been added after the event, or the action may not emit that event. Enable the domain, add the listener before triggering the action, and verify the event name and triggering conditions.
Commands fail after cleanup The CDP session was detached or its page/browser was closed. Keep the session attached for the full operation. Create a fresh session if the old one has been detached.

Reliability, performance, and cost

CDP commands are asynchronous messages to the browser, so await operations in the order required by your workflow. For example, enable a domain before relying on its events. Keep listeners and sessions scoped to the work that needs them, and detach during cleanup. A page or browser closing also ends the useful lifetime of its attached session.

Use the least protocol work needed: enable only domains you use, avoid repeatedly issuing commands when one response can be reused, and process event streams without accumulating unbounded state. CDP provides browser control, not a fixed execution-time guarantee; actual timing depends on the command, page, and browser environment.

Puppeteer and CDP have no per-command price in the API described here. Your costs come from running the machine or service that launches the browser, plus any infrastructure you choose. Factor in browser startup, memory, concurrency, retries, and timeouts when operating captures at scale. No benchmark or universal cost estimate applies to all deployments.

Or skip the browser setup

If your goal is to get a website screenshot rather than control Chrome through CDP, ScreenshotNeo provides a one-request screenshot API and an MCP server for AI agents. Its API accepts a URL and returns an image or PDF; see the ScreenshotNeo API documentation for options.

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}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));

Cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, with no card.

FAQ

Can I use a CDPSession with TypeScript?

Yes. Puppeteer’s protocol mapping types the method name and its parameters and return value for the installed package’s mapping.

Does every CDP command need an enable call?

No. Enabling is relevant to domains that require it for their events or operations. Follow the protocol documentation for the specific command.

Can I keep using a session after calling detach?

No. A detached session cannot send messages or emit events. Create another session if you need to continue.