Every web developer uses Chrome DevTools.
We inspect elements, read console logs, watch network requests, throttle CPU, emulate mobile screens, record performance traces, check storage, debug JavaScript, and capture screenshots.
Most of that feels like a browser UI.
Under the hood, there is a protocol.
That protocol is Chrome DevTools Protocol, usually shortened to CDP.
CDP is the browser debugging API that lets tools instrument, inspect, debug, and profile Chrome, Chromium, and other Blink-based browsers. Chrome DevTools itself uses this protocol. Many automation and debugging tools also build on it directly or indirectly.
In this post, I explain CDP from a practical developer point of view: what it is, how it works, what domains are, how remote debugging exposes endpoints, how commands and events flow, and when I would use CDP directly instead of a higher-level tool.
The Short Version
CDP is a JSON message protocol over WebSocket.
A client connects to a debuggable browser or page target, sends commands, and receives responses and events.
For example:
{
"id": 1,
"method": "Page.enable"
}
Then:
{
"id": 2,
"method": "Page.navigate",
"params": {
"url": "https://example.com"
}
}
The browser responds to commands by matching the same id, and it also emits events:
{
"method": "Page.loadEventFired",
"params": {
"timestamp": 12345.67
}
}
That is the core model:
client -> command -> browser
client <- response <- browser
client <- event <- browser
Everything else is built around that idea.
Why CDP Exists
Chrome DevTools needs a way to talk to the browser engine.
When you open the Network panel, DevTools needs network events. When you set a breakpoint, DevTools needs debugger commands. When you inspect the DOM, DevTools needs DOM data. When you take a performance recording, DevTools needs tracing and performance data.
CDP is the structured API for that communication.
It exposes browser capabilities through domains such as:
PageRuntimeNetworkDOMCSSDebuggerTargetEmulationPerformanceTracingStorageInputAccessibility
Each domain defines commands and events.
For example:
| Domain | What it is commonly used for |
|---|---|
Page | Navigation, lifecycle events, screenshots, frame information |
Runtime | Evaluate JavaScript, inspect objects, observe execution contexts |
Network | Observe requests, responses, headers, bodies, failures, WebSocket frames |
DOM | Inspect and manipulate the document tree |
CSS | Inspect stylesheets, rules, computed styles, and style changes |
Debugger | Set breakpoints, pause, resume, step through JavaScript |
Target | Discover and attach to tabs, workers, iframes, and browser targets |
Emulation | Override viewport, device metrics, geolocation, CPU, media, timezone, and more |
Performance | Collect metrics |
Tracing | Capture detailed timeline traces |
Input | Dispatch mouse, keyboard, and touch input |
Accessibility | Inspect accessibility tree information |
The names are plain because CDP was built for tools, not for end-user ergonomics.
CDP Is Lower Level Than Puppeteer Or Playwright
If you have used Puppeteer, Playwright, Selenium, ChromeDriver, or Chrome DevTools MCP, you have probably benefited from CDP without writing CDP messages yourself.
The difference is level of abstraction.
| Layer | What you work with |
|---|---|
| CDP | Raw browser domains, commands, events, sessions, targets |
| Puppeteer | Pages, locators, screenshots, browser contexts, high-level automation APIs |
| Playwright | Cross-browser automation, reliable locators, assertions, test runner |
| Chrome DevTools MCP | Agent-facing browser tools exposed through MCP |
| Chrome DevTools UI | Human-facing panels and workflows |
CDP is powerful because it is close to the browser. It is also verbose because it is close to the browser.
For routine testing, I would usually use Playwright.
For browser scripting in Node, I would usually use Puppeteer.
For agent-assisted debugging, I would use Chrome DevTools MCP.
For custom browser tooling, protocol experiments, deep debugging, or automation that needs exact browser events, I would consider CDP directly.
How Remote Debugging Works
To talk CDP to Chrome, Chrome must expose a debugging endpoint.
The common local development flow is:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-cdp-profile
On Linux, it often looks like:
google-chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-cdp-profile
The --remote-debugging-port=9222 flag starts an HTTP server on that port. The --user-data-dir flag keeps the debug session separate from your normal Chrome profile.
Once Chrome is running, these endpoints become useful:
http://127.0.0.1:9222/json/version
http://127.0.0.1:9222/json
http://127.0.0.1:9222/json/list
http://127.0.0.1:9222/json/protocol
The official protocol documentation describes these endpoints:
| Endpoint | Purpose |
|---|---|
/json/version | Browser version metadata and the browser-level WebSocket URL |
/json or /json/list | List available targets such as pages |
/json/protocol | The protocol schema supported by the running browser |
/json/new?{url} | Open a new tab |
/json/activate/{targetId} | Bring a target tab to the foreground |
/json/close/{targetId} | Close a target tab |
The important value is webSocketDebuggerUrl.
It looks like this:
{
"webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/ABC123"
}
That WebSocket is where CDP messages flow.
Browser Targets And Page Targets
CDP has a target model.
A target can be a page, iframe, worker, service worker, shared worker, browser context, or the browser itself.
This matters because some commands belong at the page level, and some belong at the browser level.
For example:
Page.navigateis page-oriented.Runtime.evaluateruns in an execution context.Network.enablesubscribes to network events for a target.Browser.getVersionis browser-level.Target.getTargetsdiscovers debuggable targets.
When you call /json/list, you usually see page targets. Each page target has its own WebSocket URL.
When you call /json/version, you can get the browser-level WebSocket URL. The official docs call out that the browser target URL contains browser rather than page.
That distinction is important when building tools that manage multiple tabs or browser contexts.
Commands, Responses, And Events
CDP messages are JSON objects.
A command has:
idmethod- optional
params
Example:
{
"id": 10,
"method": "Runtime.evaluate",
"params": {
"expression": "document.title"
}
}
A response has the same id:
{
"id": 10,
"result": {
"result": {
"type": "string",
"value": "Example Domain"
}
}
}
An event has a method, but no command id:
{
"method": "Network.requestWillBeSent",
"params": {
"requestId": "1234.1",
"request": {
"url": "https://example.com/"
}
}
}
This distinction is simple but important:
- responses answer commands
- events describe things happening in the browser
Many workflows require both.
For example, a network logger first sends:
{
"id": 1,
"method": "Network.enable"
}
After that, the browser starts emitting Network.requestWillBeSent, Network.responseReceived, Network.loadingFinished, and Network.loadingFailed events.
You do not poll for each request. You subscribe to a domain and listen.
A Minimal CDP Session
Here is the shape of a minimal manual CDP session.
Start Chrome:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-cdp-profile
List targets:
curl http://127.0.0.1:9222/json/list
Pick the webSocketDebuggerUrl for the page.
Then connect with a WebSocket client and send:
{"id":1,"method":"Page.enable"}
Navigate:
{"id":2,"method":"Page.navigate","params":{"url":"https://example.com"}}
Evaluate JavaScript:
{"id":3,"method":"Runtime.evaluate","params":{"expression":"document.title"}}
Capture a screenshot:
{"id":4,"method":"Page.captureScreenshot","params":{"format":"png"}}
The screenshot response contains base64 image data. Higher-level tools usually hide that detail and write the image to a file for you.
That is one reason I rarely want raw CDP for everyday work. CDP is exact, but you have to handle the plumbing.
Protocol Domains I Use Most Often
Page
The Page domain covers navigation, screenshots, lifecycle events, frames, dialogs, and page-level state.
Common commands include:
Page.enablePage.navigatePage.reloadPage.captureScreenshotPage.printToPDFPage.addScriptToEvaluateOnNewDocument
Useful events include:
Page.domContentEventFiredPage.loadEventFiredPage.frameNavigatedPage.javascriptDialogOpening
When I think “tab-level browser behavior”, I usually look at Page.
Runtime
The Runtime domain lets a tool evaluate JavaScript and inspect values.
Common commands include:
Runtime.enableRuntime.evaluateRuntime.callFunctionOnRuntime.getPropertiesRuntime.releaseObject
Useful events include:
Runtime.consoleAPICalledRuntime.exceptionThrownRuntime.executionContextCreatedRuntime.executionContextDestroyed
This is how tools can run code inside the page and get structured results back.
Network
The Network domain exposes requests, responses, headers, timings, failures, caching, WebSocket frames, and more.
Common commands include:
Network.enableNetwork.disableNetwork.getResponseBodyNetwork.setExtraHTTPHeadersNetwork.emulateNetworkConditions
Useful events include:
Network.requestWillBeSentNetwork.responseReceivedNetwork.loadingFinishedNetwork.loadingFailedNetwork.webSocketFrameSentNetwork.webSocketFrameReceived
If I am debugging API calls, auth headers, CORS, failed assets, or request timing, this is the domain I care about.
DOM And CSS
The DOM and CSS domains power much of what developers see in the Elements panel.
They can inspect nodes, attributes, boxes, stylesheets, computed styles, and rule matches.
For most automation, I prefer higher-level locators. But if I am building a DevTools-like tool, these domains matter.
Target
The Target domain is how tools discover and attach to targets.
This matters for multi-tab flows, workers, service workers, browser contexts, iframes, and tools that need the browser-level connection.
Common commands include:
Target.getTargetsTarget.attachToTargetTarget.detachFromTargetTarget.createTargetTarget.closeTargetTarget.setAutoAttach
If your CDP script works for one tab but fails in a multi-target browser, the Target domain is often where the missing concept lives.
Emulation
The Emulation domain lets tools override runtime conditions.
Examples include:
- viewport size
- device metrics
- geolocation
- timezone
- media type
- CPU throttling
- touch support
- display features
This is how browser tools simulate mobile devices, slow machines, location-specific behavior, or other environment differences.
CDP Versions
CDP has multiple protocol views.
The official protocol viewer exposes:
- latest tip-of-tree: the newest protocol surface, but it can change and break
- v8-inspector: protocol used for debugging and profiling Node.js apps
- stable 1.3: an older stable subset tagged at Chrome 64
For real tooling, the most reliable source is often the browser itself:
http://127.0.0.1:9222/json/protocol
That returns the protocol schema supported by the running browser.
This matters because CDP changes with Chrome. If you build against tip-of-tree docs and run against an older browser, a command may not exist or a parameter may behave differently.
My rule is simple:
- use the docs to learn concepts
- use the running browser’s
/json/protocolfor exact compatibility - pin browser versions for repeatable automation
Protocol Monitor: Learning CDP From DevTools Itself
Chrome DevTools includes a Protocol Monitor panel.
The Protocol Monitor documentation says it can record CDP requests and responses made by DevTools, inspect messages, save messages, and send CDP commands.
This is one of the best ways to learn CDP.
When you click around DevTools, Protocol Monitor shows the CDP traffic behind those actions.
For example, if you open the Network panel, reload a page, inspect a request, and look at the Protocol Monitor, you can see which protocol domains and events are involved.
You can also send commands directly. A parameter-free command can be typed as:
Page.captureScreenshot
For commands with parameters, the docs show JSON like:
{
"cmd": "Page.captureScreenshot",
"args": {
"format": "jpeg"
}
}
The CDP editor can generate a structured parameter form based on protocol definitions.
That makes Protocol Monitor useful for two jobs:
- learning how DevTools itself talks to Chrome
- prototyping raw CDP commands before writing code
CDP And Security
CDP is powerful because it can control the browser.
That is also why it must be treated carefully.
If a tool can connect to a debug port, it can inspect pages, read storage, evaluate scripts, watch network requests, interact with UI, and access sensitive session state in that browser profile.
My practical rules:
- never expose the debug port to an untrusted network
- bind to localhost for local development
- use a separate
--user-data-dir - avoid using your normal browser profile
- close the debug browser when finished
- do not browse sensitive sites in a debuggable profile
- use test accounts for automation
This is not theoretical. CDP is designed for debugging and instrumentation. Treat it with the same care as a shell attached to your browser session.
CDP vs WebDriver
CDP and WebDriver overlap, but they are not the same.
WebDriver is a browser automation standard designed around user-like automation across browsers.
CDP is a Chrome/Blink debugging and instrumentation protocol.
The distinction matters:
| Question | CDP | WebDriver |
|---|---|---|
| Primary focus | Debugging, instrumentation, DevTools capabilities | Browser automation standard |
| Browser scope | Chrome, Chromium, Blink-based browsers, Node via v8-inspector | Cross-browser |
| API shape | Domains, commands, events over WebSocket | WebDriver commands through driver endpoints |
| Best for | DevTools-like tools, tracing, network inspection, low-level browser control | Cross-browser UI testing |
| Abstraction level | Low | Higher |
For tests that must run across multiple browsers, WebDriver or Playwright is usually a better starting point.
For Chrome-specific observability, performance traces, network internals, or DevTools-style tooling, CDP is often the right layer.
CDP vs Chrome DevTools MCP
Chrome DevTools MCP is built for agents.
CDP is built for tools.
That difference changes the API shape.
With CDP, you might send:
{"id":1,"method":"Network.enable"}
Then subscribe to low-level events, collect request IDs, fetch response bodies, correlate timings, and summarize results yourself.
With Chrome DevTools MCP, an agent can use a higher-level tool such as:
list_network_requests
get_network_request
The MCP layer returns something more usable for an AI workflow.
That is the value of abstraction. CDP gives raw capability. MCP packages useful browser workflows as agent tools.
If I am building the MCP server, I care about CDP.
If I am using an agent to debug an app, I usually want the MCP tools.
Where CDP Is Still The Right Tool
I would reach for direct CDP when:
- building a browser automation library
- building a DevTools-like interface
- collecting custom browser telemetry
- inspecting network events at a low level
- automating Chrome features not exposed by a higher-level library
- experimenting with new protocol domains
- debugging service workers, workers, targets, frames, or browser contexts
- integrating with a non-standard runtime that exposes a CDP-compatible endpoint
- learning how DevTools actually works
I would not start with direct CDP for a normal end-to-end test.
For that, I want Playwright or Puppeteer. They hide a lot of hard details: waiting, selectors, frames, file outputs, browser lifecycle, contexts, retries, and cross-platform behavior.
A Good Mental Model
Here is how I think about CDP:
Chrome DevTools UI is the dashboard.
CDP is the wire protocol behind the dashboard.
Puppeteer and Playwright are developer-friendly automation layers.
Chrome DevTools MCP is an agent-friendly tool layer.
The lower you go, the more control you get.
The lower you go, the more plumbing you own.
That is the tradeoff.
My Take
CDP is one of those technologies that most web developers use indirectly for years before noticing it.
Every time DevTools shows a network request, pauses on a breakpoint, prints a console error, captures a screenshot, or records a trace, there is a protocol-shaped world underneath.
I do not think every developer needs to write raw CDP clients. But understanding CDP helps explain why browser tooling works the way it does.
It also makes modern agent tooling easier to understand. Tools like Chrome DevTools MCP are not magic. They are packaging browser inspection and debugging capabilities into a form that agents can use.
For day-to-day work, I still prefer higher-level tools.
But when I need to know what the browser can really expose, CDP is the layer I look at.