← Back to Index

Lesson 1

What Is the Chrome DevTools Protocol?

The Big Idea

You already use Chrome DevTools every day. You click "Inspect," browse the DOM, set breakpoints, profile network requests. But here's what you might not have considered: the DevTools GUI is just a client.

Behind that familiar UI is a protocol - a structured conversation between a client (DevTools, or any program) and the browser engine. That protocol is CDP: the Chrome DevTools Protocol.

When you click "Inspect Element" in DevTools, what actually happens is DevTools sends a JSON message over a WebSocket to the browser's rendering engine, asking it to highlight a node. The engine responds with data. Every single thing DevTools can do, it does via CDP. There is no secret back channel.[1]

Key insight: If DevTools can do it, you can do it programmatically - because you have access to the exact same protocol DevTools uses.

The Architecture (Mental Model)

Here's the full picture. Burn this into memory - it's the single diagram you need for everything that follows:

CDP Architecture: Chromium browser containing targets (tabs, service workers) connected to a CDP Server on port 9222, which communicates over WebSocket JSON with clients (DevTools GUI, Puppeteer/Playwright, your script)

The key actors:

What about the connection inside the browser? The link between the CDP server and the targets (tabs, workers) is not the same protocol. It's Chromium's internal IPC system (Mojo). The CDP server acts as a translation layer: it takes your JSON commands, routes them via internal IPC to the correct renderer process, and translates the results back into CDP JSON. You never interact with this layer - it's a black box. The protocol boundary stops at the CDP server.

The Message Format

CDP uses a pattern similar to JSON-RPC. There are exactly three kinds of messages:[2]

1. Command (client → browser)

You send a command. It has an id (so you can match it to the response), a method (the thing you want to do), and optional params:

{
  "id": 1,
  "method": "Page.navigate",
  "params": {
    "url": "https://example.com"
  }
}

2. Response (browser → client)

The browser replies with the same id and either a result or an error:

{
  "id": 1,
  "result": {
    "frameId": "ABC123",
    "loaderId": "DEF456"
  }
}

3. Event (browser → client, no id)

The browser pushes events asynchronously. No id - these are fire-and-forget notifications:

{
  "method": "Network.requestWillBeSent",
  "params": {
    "requestId": "req-789",
    "request": {
      "url": "https://example.com/api/data",
      "method": "GET"
    }
  }
}
Pattern recognition: Has an id? It's a command/response pair. No id? It's an event. That's the entire protocol framing.

Domains: How the Protocol Is Organised

CDP groups its capabilities into domains - roughly 50 of them across two protocol files. Each domain is a namespace containing related commands and events. You'll recognise many instantly from your DevTools experience.[1]

Stable Domains

These are the production-ready, documented APIs:

Experimental Domains

These are usable but may change without notice between Chrome versions:

Each domain has:

Most domains require explicit enablement. For example, you won't receive Network events until you send Network.enable. This keeps the protocol quiet by default - you only get what you ask for.

For the full list of every command, event, and type across all domains, see the CDP Complete Reference.

The Transport: How You Connect

The connection flow:

  1. Launch Chrome with remote debugging:

    You need to run the browser's executable directly from the command line, passing the --remote-debugging-port flag. The port number (9222 here) is not a fixed default - it's whatever you specify. 9222 is simply a community convention. If you omit the flag entirely, no debugging server starts. You can use any available port, which is useful for running multiple browser instances with CDP simultaneously.

    # macOS - Chrome
    /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
      --remote-debugging-port=9222
    
    # macOS - Brave
    /Applications/Brave\ Browser.app/Contents/MacOS/Brave\ Browser \
      --remote-debugging-port=9222
    Windows & Linux equivalents
    # Windows - Chrome
    "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222
    
    # Windows - Brave
    "C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe" --remote-debugging-port=9222
    
    # Linux - Chrome
    google-chrome --remote-debugging-port=9222
    
    # Linux - Brave
    brave-browser --remote-debugging-port=9222
  2. Discover targets via HTTP:
    GET http://localhost:9222/json/list

    This server is run by the browser instance you just launched - when Chrome/Brave starts with --remote-debugging-port, it opens an HTTP+WebSocket server as part of its own process. You're talking directly to that browser. It responds with a JSON array of its open targets - each with a webSocketDebuggerUrl.

  3. Connect via WebSocket to a specific target's URL:
    ws://localhost:9222/devtools/page/ABC123DEF456
  4. Send commands, receive responses and events - all as JSON over that single WebSocket connection.
Brave note: Brave is Chromium-based and supports the same --remote-debugging-port flag. The CDP interface is identical. Brave's privacy shields (ad-blocking, fingerprint protection) run at a higher layer and don't interfere with the protocol itself - though they may affect what a page renders.
Puppeteer vs Playwright: Puppeteer is a direct wrapper around CDP - when you call page.click(), it translates into a sequence of CDP commands (DOM.querySelector, Input.dispatchMouseEvent, etc.). Everything you learn here maps directly. Playwright uses CDP as its Chromium backend, but for Firefox and WebKit it uses its own patched protocol adapters - so it's a cross-browser abstraction layer, not purely a CDP wrapper. On Chromium, you can still drop to raw CDP via page.createCDPSession() for anything Playwright's high-level API doesn't expose.
What about DevTools extensions (Redux DevTools, Angular DevTools, etc.)? These do not use CDP directly. They use the Chrome Extensions API (chrome.devtools.panels, chrome.devtools.inspectedWindow.eval()) to add panels and communicate with the page. Extensions can access CDP via chrome.debugger.attach() if they need deeper protocol access, but most don't need to. The distinction:
  • CDP directly (WebSocket) - external tools, automation, AI agents, CI pipelines
  • Chrome Extensions API - adding custom panels/sidebars to the DevTools UI
  • Extensions + CDP (chrome.debugger) - extensions that need protocol-level access (e.g., network interception)

Targets and Sessions

One subtlety worth getting right early:

The hierarchy:

From a single browser instance launched with --remote-debugging-port=9222, you can connect to multiple targets in two ways:

  1. Separate WebSocket connections - each tab has its own webSocketDebuggerUrl (from /json/list). Connect to each independently. Each connection is a session.
  2. One connection, multiple sessions - connect to the browser-level WebSocket endpoint, then use Target.attachToTarget to create child sessions to individual tabs over that single connection. This is how Puppeteer manages many tabs efficiently.

The browser-level endpoint (URL available via /json/version) gives you a root session for browser-wide commands (Browser.getVersion, Target.createTarget) and the ability to spawn child sessions attached to individual targets.[3]

How This Maps to What You Already Know

Next time you open DevTools, try this mental exercise:

You do this in DevTools… Under the hood, CDP sends…
Click "Elements" tab DOM.getDocument
Set a breakpoint Debugger.setBreakpointByUrl
Throttle network to "Slow 3G" Network.emulateNetworkConditions
Run code in Console Runtime.evaluate
Take a screenshot Page.captureScreenshot

There's a built-in tool to prove this to yourself: the Protocol Monitor. Open DevTools, then open DevTools-on-DevTools (Cmd+Shift+I while DevTools is focused), and enable the Protocol Monitor panel. You'll see every CDP message flying back and forth in real time.[4]

Check Your Understanding

Q1: What distinguishes a CDP event from a command response?

Events use a different WebSocket endpoint
Events are sent as XML instead of JSON
Events have no id field
Events arrive over HTTP, not WebSocket

Q2: What is the DevTools GUI, in CDP terms?

The CDP server that Chrome runs
A CDP client, like Puppeteer or custom scripts
A special browser extension with extra API access
A proprietary interface unrelated to CDP

Q3: Why do most CDP domains need explicit enablement?

To verify the client has security permissions
Because they depend on browser extensions to work
To keep the protocol quiet - you only receive events you opt into
Because each domain runs in a separate browser process

Primary Source

Read next: The official Chrome DevTools Protocol documentation. Browse a few domains - Page, Network, Runtime - and notice the pattern: each lists commands (with parameters and return types) and events (with parameters). Don't memorise anything; just confirm that the structure matches the mental model above.