| name | chrome-devtools |
| description | Use when the user asks to "take a screenshot of a website", "navigate to a URL", "fill a form in the browser", "interact with Chrome", or when a chrome automation task is needed. |
| user-invocable | true |
Chrome DevTools CLI
A CLI that talks directly to your running Chrome via the DevTools Protocol.
Prerequisites
Chrome must have remote debugging enabled:
- Open Chrome
- Go to
chrome://inspect/#remote-debugging
- Enable the remote debugging server
The CLI auto-connects — no URL needed. A daemon is spawned on first invocation and reused across commands (5-minute idle timeout).
⚠️ Critical: How Page Targeting Works
Targets are NOT arbitrary strings. You cannot use --target main, --target page1, or any made-up name.
Targets are friendly word-pair names (like warm-squid, pink-hen) that the CLI derives from Chrome's internal target IDs. You get them from command output — never invent them.
Names are stable for the lifetime of a tab — navigating within the same tab keeps the same target name; closing and reopening the tab gives a new name.
The Correct Workflow
Step 1: Run list-pages to see what's open and get target names
chrome-devtools list-pages
Output:
[0] (warm-squid) Your Repositories — https://github.com/aeroxy
[1] (pink-hen) Gmail — https://mail.google.com
[2] (hazy-vole) Example — https://example.com
Step 2: Use the friendly name from the output in subsequent commands
chrome-devtools --target warm-squid navigate https://example.com
chrome-devtools --target pink-hen screenshot --output screenshot.png
Alternative: Use --page <index> for numeric indexing (0-based)
chrome-devtools --page 0 navigate https://example.com
If both --target and --page are omitted, the command runs on page 0 (leftmost tab). This is fine for single-tab workflows but should generally be avoided — always pin to a known page.
Core Capabilities
- Navigation:
navigate, navigate --back, navigate --forward, navigate --reload
- Page management:
list-pages, new-page, close-page, select-page
- Extraction:
screenshot, snapshot (accessibility tree), evaluate (JavaScript), read-page (page content as markdown), run-script (run local JS file), adapter (run site adapter)
- Interaction:
click, fill, type-text, press-key, hover, click-at
- Emulation:
emulate (viewport, mobile, geolocation, URL blocking)
- Inspection:
console (logs), network (requests), sw-logs (extension service workers)
- Third-party tools:
list-3p-tools, execute-3p-tool (tools exposed by window.__dtmcp)
- Synchronization:
wait-for (wait for text on page)
- Daemon control:
kill-daemon
Standard Patterns
Pattern 1: Navigate and Interact
navigate and new-page print the target name at the end — capture it to pin subsequent commands.
chrome-devtools list-pages
chrome-devtools --target warm-squid navigate https://example.com
chrome-devtools --target warm-squid screenshot --output page.png
chrome-devtools --target warm-squid evaluate "document.title"
chrome-devtools new-page https://github.com
chrome-devtools --target icy-goat snapshot
Note: The [navigated to: ...] and [target:...] lines go to stderr, not stdout. The stdout contains only the main command output ("Navigated to …", "Opened: …").
Pattern 2: Emulation (Viewport & Geolocation)
Overrides are per-tab: each page keeps its own viewport/geolocation/URL-blocks, persisting across navigation within that tab and isolated from other tabs (until cleared, the tab closes, or the daemon exits). emulate with no flags shows the active tab's state.
chrome-devtools --target warm-squid emulate --viewport 1920x1080 --geolocation 40.71,-74.00
chrome-devtools --target warm-squid emulate --viewport 375x812 --mobile --device-scale-factor 3
chrome-devtools --target warm-squid navigate https://example.com --viewport 375x812 --mobile
chrome-devtools new-page https://example.com --viewport 375x812
chrome-devtools --target warm-squid emulate
chrome-devtools --target warm-squid emulate --clear-all
chrome-devtools --target warm-squid emulate --clear-viewport
chrome-devtools --target warm-squid emulate --clear-geolocation
Pattern 3: URL Blocking (Network Debugging)
Block URL patterns using simple * wildcards: *.png (all PNG files), cdn.example.com/* (a domain path), *analytics* (any URL containing "analytics"). Patterns persist in the daemon until cleared.
Scope: blocking applies to subresources the page loads (images, scripts, fetch/XHR, stylesheets, CDN, trackers). It does not block the top-level navigation document itself — e.g. --block-url "*example.com*" then navigate https://example.com still loads the page, but any example.com subresources are blocked. This is a Chrome Network.setBlockedURLs limitation, not a CLI bug.
chrome-devtools --target warm-squid emulate --block-url "*.png"
chrome-devtools --target warm-squid emulate --block-url "*.ico" --block-url "*.svg"
chrome-devtools --target warm-squid --block-url "*.png" navigate https://example.com
chrome-devtools --target warm-squid emulate
chrome-devtools --target warm-squid emulate --unblock-url "*.png"
chrome-devtools --target warm-squid emulate --clear-blocks
chrome-devtools --target warm-squid emulate --clear-all
Pattern 4: Form Interaction
Two ways to fill inputs — choose based on what the site expects:
fill sets the value directly via element.value = .... Fast, no key events. Works for text inputs, textareas, <select>, checkboxes, radio buttons. Often breaks React/Vue apps because these frameworks rely on real input events.
type-text dispatches individual keyboard events. Slower but triggers all the input, compositionstart/end, etc. events that frameworks listen for. Use this when fill seems to "not work" on interactive frameworks.
chrome-devtools --target warm-squid click "button.submit"
chrome-devtools --target warm-squid click-at 100 200
chrome-devtools --target warm-squid fill "input.search" "search query"
chrome-devtools --target warm-squid type-text "search query" --submit-key Enter
chrome-devtools --target warm-squid press-key Enter
chrome-devtools --target warm-squid press-key Control+A
chrome-devtools --target warm-squid hover ".menu-item"
chrome-devtools --target warm-squid wait-for "Results" --timeout 10000
Pattern 5: Console & Network Inspection
The daemon maintains a persistent session for the current page that continuously collects network and console events across commands.
console and network return accumulated events and clear the buffer (drain). A second call immediately after returns empty unless new events arrived. Use this for inspecting what happened; use --duration for live monitoring.
chrome-devtools --target warm-squid navigate https://example.com
chrome-devtools --target warm-squid network
chrome-devtools --target warm-squid console
chrome-devtools --target warm-squid console --type error --type warning
chrome-devtools --target warm-squid network --type Fetch --type XHR
chrome-devtools --target warm-squid console --duration 5000
chrome-devtools --target warm-squid network --duration 3000
chrome-devtools --target warm-squid console --duration 0
Valid --type values for network: Document, Script, Stylesheet, Image, Media, Font, WebSocket, Manifest, XHR, Fetch, Other.
Pattern 6: JavaScript Evaluation
evaluate runs a JavaScript expression and returns the result. It automatically awaits promises and serializes the return value (objects become JSON, primitives come back as plain text).
chrome-devtools --target warm-squid evaluate "document.title"
chrome-devtools --target warm-squid evaluate "fetch('/api/user').then(r => r.json())"
chrome-devtools --target warm-squid --json evaluate "performance.navigation"
chrome-devtools --target warm-squid evaluate "alert('hi')" --dialog-action accept
chrome-devtools --target warm-squid evaluate "confirm('sure?')" --dialog-action dismiss
chrome-devtools --target warm-squid evaluate "prompt('name')" --dialog-action "my-answer"
chrome-devtools --target warm-squid evaluate "JSON.stringify(performance.timing)" -o /tmp/perf.json
Avoid evaluate for DOM traversal. Use snapshot to read page structure and click/fill to interact.
Pattern 7: Output Formats
All commands default to human-readable text output. Use --json or --toon (compact, LLM-friendly) for structured output.
chrome-devtools list-pages
chrome-devtools list-pages --json
chrome-devtools list-pages --toon
chrome-devtools --target warm-squid snapshot --toon
chrome-devtools --target warm-squid network --toon --type Fetch
--json and --toon are mutually exclusive.
Pattern 8: Snapshot (Accessibility Tree)
Use snapshot instead of evaluate document.querySelector(...) for understanding page structure.
chrome-devtools --target warm-squid snapshot
chrome-devtools --target warm-squid snapshot --output /tmp/ax-tree.txt
chrome-devtools --target warm-squid snapshot --toon
Pattern 9: Screenshots
chrome-devtools --target warm-squid screenshot --output page.png
chrome-devtools --target warm-squid screenshot --full-page --output full-page.png
chrome-devtools --target warm-squid screenshot --output /tmp/whatever.jpg
Pattern 10: Extension Service Worker Logs
Browser-level command — no --target needed.
chrome-devtools sw-logs --duration 2000
chrome-devtools sw-logs --duration 2000 --extension-id abcdef123456
Pattern 11: Third-party Developer Tools
For pages that expose tools via window.__dtmcp.
chrome-devtools --target warm-squid list-3p-tools
chrome-devtools --target warm-squid execute-3p-tool "<tool-name>" '<json-params>'
Pattern 12: Reading Page Content as Markdown
Extract the main article content of a page as clean markdown. Uses Readability to identify the article body and converts it to LLM-friendly markdown with metadata (title, byline, excerpt, URL). Non-article pages (SPAs, dashboards) fall back to converting the full page.
chrome-devtools --target warm-squid read-page
chrome-devtools --target warm-squid read-page --output /tmp/article.md
chrome-devtools --target warm-squid read-page --json
When to use read-page vs snapshot:
read-page — you want the page's textual content as readable markdown (articles, docs, wiki pages). Best for summarization, extraction, or feeding content to an LLM.
snapshot — you need the full accessibility tree with element IDs, roles, and interactive elements. Best for understanding page structure and finding elements to click/fill.
Pattern 13: Local JS Scripting (run-script)
Evaluate a local JavaScript file inside the page context. Dynamic arguments can be passed as raw positional values at the end of the command or via -a/--arg keys, and are automatically typed and injected into the execution context as ctx.args. Supports comment-based @url auto-navigation.
See the dedicated Custom Scripting Guide for full documentation on script creation, argument parsing, and auto-navigation.
chrome-devtools --target warm-squid run-script skill/chrome-devtools/examples/search_hn.js -- "Rust"
Pattern 14: Custom Domain-Aware Adapters (adapter)
Run site-specific adapter actions. If the browser is not currently on a matching domain (as defined by @domain comments in the JSDoc header), the CLI auto-navigates to that domain first.
See the dedicated Custom Scripting Guide for full documentation on custom adapters, domain protection, and argument parsing.
chrome-devtools --target warm-squid adapter skill/chrome-devtools/examples/hn_adapter.js search -- "Rust"
Pattern 15: Memory Leak Debugging (Heap Snapshots)
Take two heap snapshots around a suspected leak, diff them per class, then
drill into individual node IDs. compare-heapsnapshots and
inspect-heapsnapshot-node are fully offline (they parse local files — no
Chrome connection needed).
chrome-devtools --target warm-squid take-heapsnapshot --output /tmp/base.heapsnapshot
chrome-devtools --target warm-squid take-heapsnapshot --output /tmp/current.heapsnapshot
chrome-devtools compare-heapsnapshots --base /tmp/base.heapsnapshot --current /tmp/current.heapsnapshot
chrome-devtools compare-heapsnapshots --base /tmp/base.heapsnapshot --current /tmp/current.heapsnapshot --class-index 0
chrome-devtools inspect-heapsnapshot-node --file-path /tmp/current.heapsnapshot --node-id 12345
⚠️ Both snapshots must come from the same Chrome session. The diff matches
nodes by V8 heap object ID, which is only stable within a single browser
session. Comparing snapshots taken across a Chrome restart (or from different
profiles/machines) produces a meaningless result where nearly everything is
reported as both added and removed — the CLI prints a warning on stderr when
it detects this.
Pattern 16: Headless Chrome (No Login, No Human Approval)
When the flow under test doesn't need the user's cookies/credentials, spawn a
throwaway headless Chrome instead of attaching to the user's browser. Because
the instance is launched with remote debugging already enabled, no consent
prompt ever appears — the whole flow runs unattended.
PROFILE=$(mktemp -d)
chrome-devtools kill-daemon --force
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--headless=new --remote-debugging-port=0 \
--user-data-dir="$PROFILE" \
--no-first-run --no-default-browser-check \
about:blank &
CHROME_PID=$!
cleanup() {
chrome-devtools kill-daemon --force
kill "$CHROME_PID" 2>/dev/null
for _ in $(seq 1 20); do
kill -0 "$CHROME_PID" 2>/dev/null || break
sleep 0.25
done
if kill -0 "$CHROME_PID" 2>/dev/null; then
kill -9 "$CHROME_PID" 2>/dev/null
fi
wait 2>/dev/null
-rf
}
cleanup EXIT
_ $( 1 60);
[ -f ] &&
-0 2>/dev/null || { >&2; 1; }
0.5
[ -f ] || { >&2; 1; }
chrome-devtools --user-data-dir navigate https://example.com
chrome-devtools --user-data-dir evaluate
chrome-devtools --user-data-dir screenshot --output /tmp/shot.png
Linux path: google-chrome or chromium on $PATH replaces the macOS
.app binary path.
⚠️ One daemon per user, bound to one Chrome. The daemon connects to
whichever Chrome the first command resolved, and later commands reuse it even
if their flags point elsewhere. Always kill-daemon --force when switching
between the user's Chrome and a headless instance — in both directions
(step 1 and the EXIT trap above).
Complete Command Reference
Navigation
chrome-devtools list-pages
chrome-devtools navigate <url> [--viewport WxH] [--mobile] [--device-scale-factor N] [--geolocation lat,lon]
chrome-devtools navigate <url> --extra-headers '{"Authorization":"Bearer ..."}'
chrome-devtools navigate --back
chrome-devtools navigate --forward
chrome-devtools navigate --reload
chrome-devtools new-page <url> [--viewport WxH] [--mobile]
chrome-devtools close-page [index_or_target_name]
chrome-devtools select-page [index_or_target_name]
Inspection
chrome-devtools --target <name> screenshot [--output <path>] [--full-page]
chrome-devtools --target <name> snapshot
chrome-devtools --target <name> read-page [--output <path>]
chrome-devtools --target <name> evaluate "<js-expression>" [--dialog-action accept|dismiss|text]
chrome-devtools --target <name> network [--duration <ms>] [--type <resource>]
chrome-devtools --target <name> console [--duration <ms>] [--type <level>]
chrome-devtools sw-logs [--duration <ms>] [--extension-id <id>]
Memory (heap snapshots)
chrome-devtools --target <name> take-heapsnapshot --output <path.heapsnapshot>
chrome-devtools compare-heapsnapshots --base <path> --current <path> [--class-index N]
chrome-devtools inspect-heapsnapshot-node --file-path <path> --node-id <id>
Interaction
chrome-devtools --target <name> click "<css-selector>"
chrome-devtools --target <name> click-at <x> <y>
chrome-devtools --target <name> fill "<css-selector>" "<value>"
chrome-devtools --target <name> type-text "<text>" [--submit-key <key>]
chrome-devtools --target <name> press-key <key>
chrome-devtools --target <name> hover "<css-selector>"
chrome-devtools --target <name> wait-for "<text>" [--timeout <ms>]
Emulation
chrome-devtools --target <name> emulate [--viewport WxH] [--mobile] [--geolocation lat,lon]
chrome-devtools --target <name> emulate
chrome-devtools --target <name> emulate --block-url "<pattern>" [--block-url ...]
chrome-devtools --target <name> emulate --unblock-url "<pattern>"
chrome-devtools --target <name> emulate --clear-blocks
chrome-devtools --target <name> emulate --clear-viewport
chrome-devtools --target <name> emulate --clear-geolocation
chrome-devtools --target <name> emulate --clear-all
Third-party Tools
chrome-devtools --target <name> list-3p-tools
chrome-devtools --target <name> execute-3p-tool <name> '<json-params>'
Custom Scripting & Adapters
chrome-devtools --target <name> run-script <file-path> [--arg key=value] [--output <path>] [--track-navigation]
chrome-devtools --target <name> adapter <file-path> <function-name> [--arg key=value] [--output <path>] [--track-navigation]
Daemon
chrome-devtools kill-daemon
chrome-devtools kill-daemon --force
Failure Handling: "Failed to connect to Chrome" / a command hangs
Chrome's remote-debugging connection requires a one-time human approval dialog
in Chrome. If a command hangs or fails with a connection/timeout error, the most
likely cause is that this dialog is open and waiting for the human — not a bug
you can fix by retrying.
If a command hangs for a long time or errors with "Failed to connect to Chrome"
or "Timed out ... connecting to Chrome":
- Retry at most once (the human may have already approved it just now).
- If it fails again, STOP. Do not keep retrying — the human is very likely
away from the keyboard and no amount of retrying will approve the dialog for
them.
- Tell the user directly that Chrome is waiting for them to approve the
remote-debugging connection dialog, and wait for their response.
Never run kill-daemon as a way to "fix" a connection problem. It does not
help — it destroys the daemon's already-approved connection (if one exists) and
guarantees the next attempt needs a fresh human approval, making things worse.
For this reason, kill-daemon refuses to run non-interactively without
--force. As an agent, only pass --force if the user has explicitly asked you
to kill the daemon for some other reason (e.g. it's stuck on unrelated JS
execution) — never as a reaction to a connection failure.
Critical Gotchas
✗ WRONG: Using invented target names
chrome-devtools --target main navigate https://example.com
chrome-devtools --target page1 screenshot
chrome-devtools --target "my-page" evaluate "..."
✓ CORRECT: Get target from command output
chrome-devtools list-pages
chrome-devtools --target warm-squid navigate https://github.com
✗ WRONG: Running commands without first finding a target
chrome-devtools screenshot --output page.png
✓ CORRECT: Always identify the page first
chrome-devtools list-pages
chrome-devtools --target warm-squid screenshot --output page.png
✗ WRONG: Using evaluate for DOM traversal or interaction
chrome-devtools --target warm-squid evaluate "document.querySelector('...').click()"
✓ CORRECT: Use snapshot for structure, click/fill for interaction
chrome-devtools --target warm-squid snapshot
chrome-devtools --target warm-squid click "button.submit"
✗ WRONG: Expecting fill to update React/Vue forms
chrome-devtools --target warm-squid fill "input" "value"
✓ CORRECT: Use type-text for stateful frameworks
chrome-devtools --target warm-squid type-text "value"
✗ WRONG: Expecting console/network to remember events after drain
chrome-devtools --target warm-squid console
chrome-devtools --target warm-squid console
✓ CORRECT: Each drain is a fresh window
Run console / network right after the action that produces events, OR use --duration to collect for a window of time.
✗ WRONG: Retrying in a loop or running kill-daemon on connection failure
chrome-devtools list-pages
chrome-devtools kill-daemon --force
chrome-devtools list-pages
✓ CORRECT: Retry once, then stop and ask the human
chrome-devtools list-pages
chrome-devtools list-pages
Output Format Summary
| Flag | Description |
|---|
| (none) | Human-readable text (default) |
--json | Pretty-printed JSON |
--toon | TOON — compact tabular format (fewer tokens for LLM agents) |
--json and --toon are mutually exclusive.
Stdout vs Stderr
- stdout contains the primary command output (table, text, JSON, TOON).
- stderr contains informational lines (
[target:...], [navigated to: ...]) and errors.
When parsing command output programmatically, read only stdout to get the main result.