Skip to content

18 Advanced Tips And Tricks

Advanced Chrome DevTools — Tips, Tricks & Power Workflows

Section titled “Advanced Chrome DevTools — Tips, Tricks & Power Workflows”

  1. Command Menu Mastery
  2. Master Keyboard Shortcuts
  3. Console Power Tricks
  4. Elements Panel Power Tricks
  5. Network Panel Power Tricks
  6. Sources Panel Power Tricks
  7. 10 Useful DevTools Snippets
  8. chrome:// URLs Every Developer Should Know
  9. DevTools Protocol (CDP)
  10. Framework-Specific Debugging
  11. Remote Debugging Node.js
  12. DevTools Experiments Worth Enabling
  13. Performance Quick Wins Checklist

Open with Ctrl+Shift+P (Windows/Linux) or Cmd+Shift+P (Mac). This is the fastest way to reach any DevTools feature without hunting through panels.

PrefixScopeExample
>Run a DevTools command> Show Coverage
(no prefix)Open a file by namemain.js
:Jump to a line in the current file:247
@Jump to a symbol (function, class)@handleSubmit
!Run a saved snippet!audit-links
#CategoryCommandWhat it does
1AppearanceSwitch to dark themeToggles dark/light DevTools UI
2AppearanceDock to bottom / left / rightChanges DevTools dock position
3PanelShow CoverageOpens JS/CSS coverage panel
4PanelShow RenderingOpens rendering overlays panel
5PanelShow LayersOpens 3D layer composite view
6PanelShow AnimationsOpens animation inspector
7PanelShow ChangesShows all DevTools-authored changes
8PanelShow SecurityOpens security/cert panel
9PanelShow WebAudioOpens Web Audio API graph
10PanelShow MediaOpens HTML5 media panel
11CaptureCapture screenshotFull-page screenshot
12CaptureCapture node screenshotScreenshot of selected DOM node
13CaptureCapture full size screenshotCaptures the entire scrollable page
14NetworkDisable cacheToggles cache off for network requests
15NetworkEnable network throttlingApplies a speed profile to network
16DebuggerDisable JavaScriptTurns off JS execution entirely
17DebuggerResume with all pauses blockedSkips all remaining breakpoints
18DebuggerStep into next function callSame as F11 in Sources
19SensorsSet locationSpoofs GPS coordinates
20SensorsShow SensorsOpens touch/orientation/location panel
21PerformanceStart/Stop CPU throttlingSlows CPU for slow-device simulation
22PerformanceShow Performance monitorLive CPU/heap/FPS metrics
23CSSForce CSS stateForce :hover, :focus, etc.
24CSSView page sourceOpens raw HTML in a new tab
25AccessibilityShow AccessibilityOpens the a11y tree panel
26StorageClear site dataClears cookies, storage, cache at once
27WorkspaceAdd folder to workspaceMounts a local folder for editing
28SnippetsCreate new snippetNew JS snippet in Sources
29SettingsOpen SettingsOpens DevTools settings (F1)
30SettingsRestore default settingsResets all DevTools preferences

Shortcut (Mac)Shortcut (Win/Linux)Action
Cmd+Shift+PCtrl+Shift+POpen Command Menu
Cmd+] / Cmd+[Ctrl+] / Ctrl+[Move to next/previous panel tab
Cmd+Shift+CCtrl+Shift+CInspect element (focus Elements panel)
Cmd+Shift+ICtrl+Shift+IToggle DevTools open/close
Cmd+Shift+JCtrl+Shift+JOpen DevTools and focus Console
Cmd+KCtrl+LClear the Console
Cmd+FCtrl+FSearch within current panel
Cmd+G / Cmd+Shift+GF3 / Shift+F3Next/previous search result
EscEscToggle the Console drawer in any panel
F1F1Open DevTools Settings
??Open Settings (within DevTools)
Shortcut (Mac)Shortcut (Win/Linux)Action
/ / Navigate up/down DOM nodes
/ / Expand/collapse node
EnterEnterEdit the selected node’s attributes
HHToggle visibility (visibility: hidden)
DeleteDeleteRemove the selected node from DOM
Cmd+ZCtrl+ZUndo DOM change
Cmd+Shift+MCtrl+Shift+MToggle device toolbar
`Cmd+“`Ctrl+“Toggle between Styles and Computed sub-tabs
TabTabCycle through attributes on selected node
F2F2Edit node as HTML
Shortcut (Mac)Shortcut (Win/Linux)Action
/ / Navigate command history
Shift+EnterShift+EnterStart a new line (multi-line input)
Cmd+EnterCtrl+EnterExecute multi-line code block
Cmd+KCtrl+LClear console output
TabTabAccept autocomplete suggestion
EscapeEscapeClose autocomplete dropdown
Cmd+/Ctrl+/Toggle comment on selected line
Alt+Click (expand icon)Alt+ClickDeep-expand an object in the log
Shortcut (Mac)Shortcut (Win/Linux)Action
F8 or Cmd+\F8 or Ctrl+\Resume / Pause execution
F10 or Cmd+'F10 or Ctrl+'Step over next function call
F11 or Cmd+;F11 or Ctrl+;Step into next function call
Shift+F11Shift+F11Step out of current function
Cmd+BCtrl+BToggle breakpoint on current line
Cmd+PCtrl+PGo to file
Cmd+Shift+OCtrl+Shift+OGo to function/symbol in file
Cmd+Shift+ECtrl+Shift+ERun snippet
Cmd+SCtrl+SSave changes (if workspace mounted)
Cmd+Alt+FCtrl+Shift+FSearch across all sources files
Shortcut (Mac)Shortcut (Win/Linux)Action
Cmd+RCtrl+RReload page and start recording
Cmd+Shift+RCtrl+Shift+RHard reload (bypass cache)
Cmd+ECtrl+EToggle recording on/off
//Focus filter input
Cmd+FCtrl+FSearch across all response bodies
EscEscClose request detail pane
Shortcut (Mac)Shortcut (Win/Linux)Action
Cmd+ECtrl+EStart/stop recording
Cmd+Shift+ECtrl+Shift+ERecord and reload page
W / SW / SZoom in/out on timeline
A / DA / DPan left/right on timeline
Shift+Click+DragShift+Click+DragSelect a time range

Right-click any logged object in the Console and choose “Store as global variable”. DevTools assigns it to $temp1, $temp2, etc. — temporary references that persist until you clear the console. Useful when you receive a deeply nested object in a callback and want to explore it interactively.

// After "Store as global variable" on a response object:
$temp1.data.users.filter(u => u.active)

The copy() function writes any value to your system clipboard as a string.

// Copy all href values from the page as a newline-separated list
copy($$('a').map(a => a.href).join('\n'))
// Copy a complex object as formatted JSON
copy(JSON.stringify(someDeepObject, null, 2))
// Copy all CSS class names in use on the page
copy([...new Set([...$$('[class]')].flatMap(el => [...el.classList]))].sort().join('\n'))

$() is an alias for document.querySelector(). $$() is an alias for document.querySelectorAll() but returns an Array (not a NodeList), so array methods work directly.

// Find all broken images (not yet loaded or src missing)
$$('img').filter(i => !i.complete)
// All forms with no action attribute
$$('form:not([action])')
// All elements with aria-label
$$('[aria-label]').map(el => ({ tag: el.tagName, label: el.getAttribute('aria-label') }))

$0 refers to the last element you inspected in the Elements panel. $1 is the one before that, up to $4.

// Get all computed styles for the currently selected element
getComputedStyle($0)
// Check if the inspected element has any event listeners (Chrome Extension required for full list)
getEventListeners($0) // Only available in DevTools context
// Measure its bounding box
$0.getBoundingClientRect()

$_ holds the result of the most recently evaluated expression.

[1, 2, 3, 4, 5].filter(n => n % 2 === 0)
// [2, 4]
$_.map(n => n * 10)
// [20, 40]

Click the eye icon (or Create live expression) in the Console toolbar to pin an expression that auto-evaluates every 250 ms. This eliminates console.log spam for values you want to monitor continuously.

Good candidates:

  • document.activeElement — track focus changes
  • window.scrollY — monitor scroll position
  • performance.memory.usedJSHeapSize — watch memory grow
  • document.querySelectorAll('.loading').length — track async loading state

Press Shift+Enter to add a new line without executing. Press Cmd/Ctrl+Enter when ready to run. This lets you write multi-statement blocks, define functions, and run async code inline.

// Multi-line async IIFE in console
async function fetchUsers() {
const res = await fetch('/api/users')
const data = await res.json()
console.table(data.slice(0, 10))
}
await fetchUsers()

The DevTools Console supports top-level await natively — no wrapper function needed.

const res = await fetch('https://api.github.com/users/github')
const user = await res.json()
console.table(user)
// Way more readable than console.log for structured data
console.table(performance.getEntriesByType('resource').map(r => ({
name: r.name.split('/').pop(),
type: r.initiatorType,
duration: Math.round(r.duration) + 'ms',
size: r.transferSize
})))
console.group('User Session')
console.log('ID:', userId)
console.group('Permissions')
permissions.forEach(p => console.log('', p))
console.groupEnd()
console.groupEnd()
// Intercept all fetch calls to log their URLs
const _fetch = window.fetch
window.fetch = function(...args) {
console.log('[fetch]', args[0])
return _fetch.apply(this, args)
}

With a node selected, press H to apply visibility: hidden without removing it from the DOM. Layout is preserved but the element becomes invisible. Press H again to restore. Useful for isolating layout shifts or checking what lies beneath an element.

Press Delete (or Backspace on some systems) to remove the selected node entirely. The change is temporary — a page reload restores it. Use Cmd+Z to undo within the same DevTools session.

Right-click any collapsed node and choose Expand recursively to open the entire subtree at once. Invaluable for exploring deeply nested server-rendered HTML without clicking every caret.

Right-click any element in the Elements panel and choose Capture node screenshot. DevTools scrolls to the element, captures a PNG of exactly its rendered bounding box, and saves it. No browser extensions or screen-capture tools needed.

In the Styles sub-panel, click :hov to force :hover, :active, :focus, :focus-within, :focus-visible, and :visited — individually or in any combination. This is far faster than trying to mouse-hover an element while reading styles.

Right-click any node in the Elements panel and choose Scroll into view. The page scrolls so the element is visible in the viewport. Essential when debugging off-screen elements or sticky/fixed components.

Right-click any node → CopyCopy full XPath. This produces an absolute XPath like /html/body/div[3]/section/article[2]/h2. Useful for feeding into Selenium/Playwright locators or browser automation scripts.

Right-click → CopyCopy JS path generates a document.querySelector(...) call that uniquely selects that node. Paste it into the Console and it returns the element.

Press F2 or right-click → Edit as HTML to get a full, editable HTML textarea for the node and all its children. Commit changes by clicking outside the editor. This is the fastest way to test markup changes without touching your source files.

Right-click a node → Break on to set a mutation breakpoint:

  • Subtree modifications — pauses whenever any descendant changes
  • Attribute modifications — pauses when attributes on this node change
  • Node removal — pauses when this exact node is removed from the DOM

These pause execution in the Sources panel with a full call stack showing exactly which JavaScript made the change.


The filter bar accepts plain text (substring match on URL) but also these keywords:

FilterExampleWhat it matches
domain:domain:api.example.comRequests to that domain only
has-response-header:has-response-header:cache-controlResponses with that header
method:method:POSTRequests by HTTP method
mime-type:mime-type:application/jsonResponses by MIME type
larger-than:larger-than:100kResponses over 100 KB
status-code:status-code:404Specific HTTP status code
resource-type:resource-type:fetchxhr, fetch, image, script, stylesheet, font, etc.
- (negate)-domain:cdn.example.comExclude matches
is:is:runningis:running, is:from-cache, is:service-worker-intercepted

Combine filters: method:POST -domain:analytics.com status-code:200

Right-click any request row → Block request URL (blocks that exact URL) or Block request domain (blocks the entire domain). The request will fail with net::ERR_BLOCKED_BY_DEVTOOLS. Use this to test:

  • What your app does when a CDN is down
  • Fallback behavior when an API endpoint fails
  • Whether your error boundaries / error UI actually renders

Manage blocked patterns via Settings (gear icon) → Blocked URLs or the Network request blocking drawer panel.

A HAR (HTTP Archive) file records every network request/response. You can drag a .har file onto the Network panel to replay the entire session — no live server needed. Perfect for:

  • Debugging a bug reported by a user (“send me your HAR file”)
  • Comparing production vs. staging network behavior
  • Performance regression analysis across builds

Export via the download icon in the Network panel toolbar.

Right-click any request → CopyCopy as cURL. This produces a shell command that recreates the exact request — method, headers, cookies, request body — that you can paste into a terminal. Invaluable for:

  • Reproducing an authenticated API call outside the browser
  • Sharing a request with a backend engineer
  • Importing into Postman or Insomnia (both accept cURL paste)
Terminal window
# Example output:
curl 'https://api.example.com/graphql' \
-H 'authorization: Bearer eyJhbGciOi...' \
-H 'content-type: application/json' \
--data-raw '{"query":"{ me { id name } }"}'

Check Preserve log in the Network toolbar to prevent the request list from clearing on navigation. Essential when debugging redirect chains, form submissions, or OAuth flows that involve a redirect back to your app.

Click any request and open the Initiator tab in the detail pane. For fetch/XHR, DevTools shows the full JavaScript call stack that triggered the request — click any frame to jump to that source line. This is the fastest way to find “what code made this request.”

Throttle Individual Requests with Override

Section titled “Throttle Individual Requests with Override”

Use Local Overrides (Sources panel → Overrides tab) to serve a custom response for any URL. Enable overrides → right-click a response in Network → Override content → edit the response body. The overridden response is served on subsequent requests until you disable it.


Right-click the line number gutter → Add logpoint and enter any JavaScript expression. On each execution, the expression is evaluated and logged to the Console — no console.log statement needed in your source, no rebuild required. Logpoints persist across page reloads within the DevTools session.

// In the logpoint dialog, enter:
'User:', user.id, 'Cart total:', cart.items.reduce((s, i) => s + i.price, 0)

Right-click the gutter → Add conditional breakpoint. Enter a JS expression; execution only pauses when the expression is truthy. This is the single most underused DevTools feature for loops and event handlers.

// Only pause when the failing user ID is processed
user.id === 'usr_abc123'
// Only pause when the array is unexpectedly empty
items.length === 0
// Only pause on the 50th iteration
i === 49

While paused in the debugger, right-click any frame in the Call Stack panel and choose Restart frame. DevTools re-enters that function from its beginning — all local variables are reset, the DOM and heap state are not. Use this to replay a specific function without reloading the page, which is especially useful when the bug only appears after a long setup sequence.

Right-click the line number of any line inside a library or third-party file and choose Never pause here. The debugger will skip that line when evaluating “pause on exception” — no more breaking inside minified React internals when your own code throws.

In DevTools Settings → Ignore List, add URL patterns (supports wildcards) to blackbox entire files. When a script is blackboxed:

  • The debugger steps through it as if it doesn’t exist
  • Stack traces collapse blackboxed frames
  • “Pause on exceptions” skips exceptions thrown inside blackboxed scripts

Default patterns include node_modules, webpack, and common CDN bundles.

  1. Sources panel → Overrides tab → + Select folder for overrides
  2. Choose (or create) a local directory
  3. Grant DevTools permission to access it
  4. Edit any file in the Sources panel and press Cmd+S
  5. The edit is saved locally and served on every subsequent page load

This lets you test code changes on any production site without a local dev server.

In the Sources panel, click the Node.js icon (top-left toolbar) to enable Auto-attach. Any node process started with --inspect in a terminal will automatically appear in DevTools — no manual chrome://inspect navigation needed.


Save these in Sources → Snippets for instant reuse. Run any snippet with Cmd+Enter or via the Command Menu (!snippet-name).

// Finds all <img> elements that failed to load
;(function findBrokenImages() {
const broken = $$('img').filter(img => {
// naturalWidth is 0 for broken images and images that haven't loaded
return img.complete && img.naturalWidth === 0
})
if (broken.length === 0) {
console.log('%c No broken images found.', 'color: green; font-weight: bold')
} else {
console.group(`%c ${broken.length} broken image(s) found`, 'color: red; font-weight: bold')
broken.forEach((img, i) => {
console.group(`Image ${i + 1}`)
console.log('src:', img.src)
console.log('alt:', img.alt || '(none)')
console.log('element:', img)
console.groupEnd()
})
console.groupEnd()
}
})()

Snippet 2: List All Event Listeners on Document

Section titled “Snippet 2: List All Event Listeners on Document”
// Dumps all event listeners registered on document and window
// Note: getEventListeners() is a DevTools-only API
;(function listDocumentListeners() {
const targets = [
{ name: 'window', el: window },
{ name: 'document', el: document },
{ name: 'document.body', el: document.body },
]
targets.forEach(({ name, el }) => {
const listeners = getEventListeners(el)
const types = Object.keys(listeners)
if (types.length === 0) {
console.log(`${name}: no listeners`)
return
}
console.group(`${name} (${types.length} event types)`)
types.forEach(type => {
console.group(`${type} (${listeners[type].length})`)
listeners[type].forEach(({ listener, once, passive, capture }) => {
console.log({ listener, once, passive, capture })
})
console.groupEnd()
})
console.groupEnd()
})
})()

Snippet 3: Find All Elements with Inline Styles

Section titled “Snippet 3: Find All Elements with Inline Styles”
// Finds every element using inline style attributes — common code-smell
;(function findInlineStyles() {
const elements = $$('[style]')
if (elements.length === 0) {
console.log('%c No inline styles found.', 'color: green')
return
}
console.group(`${elements.length} element(s) with inline styles`)
const report = elements.map(el => ({
tag: el.tagName.toLowerCase(),
id: el.id || null,
class: el.className || null,
style: el.getAttribute('style'),
element: el
}))
console.table(report.map(r => ({ tag: r.tag, id: r.id, class: r.class, style: r.style })))
console.groupEnd()
})()

Snippet 4: Find the Largest DOM Elements by Child Count

Section titled “Snippet 4: Find the Largest DOM Elements by Child Count”
// Reports the top 10 elements with the most DOM descendants
;(function findLargestDOMElements() {
const all = $$('*')
const withCounts = all.map(el => ({
element: el,
tag: el.tagName.toLowerCase(),
id: el.id ? `#${el.id}` : '',
class: el.classList.length ? `.${[...el.classList].join('.')}` : '',
childCount: el.querySelectorAll('*').length,
directChildren: el.childElementCount
}))
withCounts.sort((a, b) => b.childCount - a.childCount)
const top10 = withCounts.slice(0, 10)
console.group('Top 10 DOM elements by total descendant count')
console.table(top10.map(({ tag, id, class: cls, childCount, directChildren }) => ({
selector: `${tag}${id}${cls}`,
'total descendants': childCount,
'direct children': directChildren
})))
top10.forEach(({ element }) => console.log(element))
console.groupEnd()
})()

Snippet 5: Highlight All Focusable Elements

Section titled “Snippet 5: Highlight All Focusable Elements”
// Overlays a visible highlight on every keyboard-focusable element
;(function highlightFocusableElements() {
const focusableSelectors = [
'a[href]',
'button:not([disabled])',
'input:not([disabled])',
'select:not([disabled])',
'textarea:not([disabled])',
'[tabindex]:not([tabindex="-1"])',
'[contenteditable="true"]',
'details > summary',
'audio[controls]',
'video[controls]'
].join(',')
const focusable = $$(focusableSelectors)
let count = 0
focusable.forEach((el, i) => {
const original = el.style.outline
el.style.outline = '3px solid deeppink'
el.style.outlineOffset = '2px'
el.dataset._focusHighlight = original
count++
})
console.log(`Highlighted ${count} focusable elements. Run clearHighlights() to remove.`)
window.clearHighlights = function() {
$$('[data-_focus-highlight]').forEach(el => {
el.style.outline = el.dataset._focusHighlight
el.style.outlineOffset = ''
delete el.dataset._focusHighlight
})
console.log('Highlights cleared.')
}
})()

Snippet 6: List All CSS Custom Properties on :root

Section titled “Snippet 6: List All CSS Custom Properties on :root”
// Dumps every CSS variable (custom property) defined on :root
;(function listCSSVariables() {
const rootStyles = getComputedStyle(document.documentElement)
const variables = []
for (let i = 0; i < rootStyles.length; i++) {
const prop = rootStyles[i]
if (prop.startsWith('--')) {
variables.push({
property: prop,
value: rootStyles.getPropertyValue(prop).trim()
})
}
}
if (variables.length === 0) {
console.log('No CSS custom properties found on :root')
return
}
variables.sort((a, b) => a.property.localeCompare(b.property))
console.group(`${variables.length} CSS custom properties on :root`)
console.table(variables)
copy(variables.map(v => `${v.property}: ${v.value};`).join('\n'))
console.log('(Values copied to clipboard)')
console.groupEnd()
})()
Section titled “Snippet 7: Find External Links Missing target=“_blank””
// Finds external links that open in the same tab (potentially bad UX)
;(function findExternalLinksWithoutTargetBlank() {
const currentHost = location.hostname
const external = $$('a[href]').filter(a => {
try {
const url = new URL(a.href)
return (
url.hostname &&
url.hostname !== currentHost &&
a.target !== '_blank'
)
} catch {
return false
}
})
if (external.length === 0) {
console.log('%c All external links have target="_blank"', 'color: green')
return
}
console.group(`${external.length} external link(s) missing target="_blank"`)
external.forEach(a => {
console.log(`href: ${a.href} | text: "${a.textContent.trim().slice(0, 60)}"`, a)
})
console.groupEnd()
})()
// Uses performance marks to measure how long a re-paint takes
// after mutating an element's style
;(async function measurePaintTime() {
const selector = prompt('Enter a CSS selector to measure paint time for:', 'body')
const el = document.querySelector(selector)
if (!el) {
console.error('Element not found:', selector)
return
}
const markStart = 'paint-measure-start'
const markEnd = 'paint-measure-end'
performance.mark(markStart)
// Trigger a style recalc + paint
el.style.opacity = el.style.opacity === '0.99' ? '1' : '0.99'
// Wait for the next animation frame (after layout) + one more for paint
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))
performance.mark(markEnd)
const measure = performance.measure('paint-duration', markStart, markEnd)
console.log(`Paint time for "${selector}": ${measure.duration.toFixed(3)} ms`)
// Restore
el.style.opacity = ''
performance.clearMarks(markStart)
performance.clearMarks(markEnd)
performance.clearMeasures('paint-duration')
})()

Snippet 9: Dump All localStorage as Formatted JSON

Section titled “Snippet 9: Dump All localStorage as Formatted JSON”
// Prints all localStorage entries with pretty-printed JSON values
;(function dumpLocalStorage() {
const count = localStorage.length
if (count === 0) {
console.log('localStorage is empty.')
return
}
const entries = {}
for (let i = 0; i < count; i++) {
const key = localStorage.key(i)
const raw = localStorage.getItem(key)
try {
entries[key] = JSON.parse(raw)
} catch {
entries[key] = raw // Not JSON, store as string
}
}
console.group(`localStorage (${count} entries)`)
Object.entries(entries).forEach(([key, value]) => {
if (typeof value === 'object' && value !== null) {
console.group(key)
console.log(value)
console.groupEnd()
} else {
console.log(`${key}:`, value)
}
})
console.groupEnd()
copy(JSON.stringify(entries, null, 2))
console.log('(Copied to clipboard as JSON)')
})()

Snippet 10: Count Elements Matching Each Selector from a List

Section titled “Snippet 10: Count Elements Matching Each Selector from a List”
// Audit which CSS selectors have matches — useful for design system audits,
// deprecation tracking, or verifying component adoption
;(function countSelectorMatches() {
const selectors = [
// Modify this list for your audit
'h1', 'h2', 'h3',
'button', 'a[href]', 'input', 'select', 'textarea',
'[role="button"]', '[aria-label]', '[aria-describedby]',
'.btn', '.btn-primary', '.btn-secondary',
'img:not([alt])',
'table', 'form',
'[data-testid]',
':focus-visible',
'iframe',
'[onclick]',
'script[src]',
'link[rel="stylesheet"]'
]
const results = selectors.map(selector => {
let count = 0
let error = null
try {
count = document.querySelectorAll(selector).length
} catch (e) {
error = e.message
}
return { selector, count, error }
})
console.group('Selector audit results')
console.table(results)
console.groupEnd()
const nonZero = results.filter(r => r.count > 0)
console.log(`${nonZero.length} of ${selectors.length} selectors matched at least one element.`)
})()

chrome:// URLs Every Developer Should Know

Section titled “chrome:// URLs Every Developer Should Know”
URLWhat It DoesWhen to Use It
chrome://inspectList all inspectable targets: browser tabs, Node.js processes, extensions, service workersRemote debugging, Node.js DevTools attach, debugging WebViews
chrome://net-internals/#dnsView and flush the DNS cacheWhen DNS changes aren’t propagating in-browser; chrome://net-internals/#dnsClear host cache
chrome://net-internals/#socketsView active socket pools and flush themDiagnosing connection-reuse issues, forcing new TCP/TLS handshake
chrome://net-internals/#eventsReal-time event log for all network activityDeep network debugging below the Network panel level
chrome://tracingChromium’s own Perfetto-based trace recorder — records compositor, renderer, GPU threadsDiagnosing jank, compositing bugs, or performance issues not visible in the DevTools Performance panel
chrome://flagsEnable/disable experimental Chrome featuresTesting upcoming browser APIs before they ship, enabling DevTools experiments globally
chrome://versionFull Chrome version, OS, JS engine (V8) version, and command-line flags activeBug reports, verifying which Chrome channel and V8 version you’re on
chrome://extensionsExtension management pageDisabling extensions during debugging to rule out interference
chrome://quota-internalsStorage quota usage per origin: IndexedDB, Cache API, Service Worker storageDiagnosing “storage full” errors; checking what storage each origin has consumed
chrome://dnsCurrently cached DNS entriesSee which domains are cached and their TTLs
chrome://net-internals/#hstsView and manage HSTS/HPKP policiesDebugging HTTPS redirect loops, clearing stuck HSTS entries for localhost
chrome://serviceworker-internalsFull list of registered service workers with start/stop/inspect controlsDebugging service worker lifecycle, cache strategies, and push notifications
chrome://webrtc-internalsLive stats for all active WebRTC peer connectionsDebugging video/audio calls, ICE negotiation, and media codec selection
chrome://crashesCrash report historyDiagnosing renderer crashes and GPU process crashes
chrome://gpuFull GPU feature status, driver versions, compositor modeDiagnosing hardware acceleration issues, verifying WebGL/WebGPU availability

The Chrome DevTools Protocol (CDP) is the JSON-over-WebSocket API that powers DevTools itself. Every action you take in DevTools — setting a breakpoint, inspecting an element, recording a trace — is a CDP message under the hood. The full spec is at chromedevtools.github.io/devtools-protocol.

Both automation libraries open a WebSocket connection to Chrome’s CDP endpoint and send commands programmatically. Puppeteer wraps CDP almost 1:1 (many methods are thin wrappers). Playwright adds a higher-level abstraction but drops to CDP for low-level operations.

// Puppeteer: access the raw CDP session
const page = await browser.newPage()
const client = await page.createCDPSession()
// Enable network domain
await client.send('Network.enable')
// Intercept every request
client.on('Network.requestWillBeSent', event => {
console.log(event.request.method, event.request.url)
})
// Block all image requests at the CDP level
await client.send('Network.setBlockedURLs', {
urls: ['*.jpg', '*.png', '*.gif', '*.webp']
})

Accessing CDP via the chrome.debugger Extension API

Section titled “Accessing CDP via the chrome.debugger Extension API”

Browser extensions with the debugger permission can attach to any tab and send CDP commands directly.

// manifest.json: "permissions": ["debugger"]
// In your extension's background service worker:
const tabId = 123 // target tab
chrome.debugger.attach({ tabId }, '1.3', () => {
// Enable the Network domain
chrome.debugger.sendCommand({ tabId }, 'Network.enable', {}, () => {
// Listen for responses
chrome.debugger.onEvent.addListener((source, method, params) => {
if (method === 'Network.responseReceived') {
console.log('Response:', params.response.status, params.response.url)
}
})
})
})
// Always detach when done
chrome.debugger.detach({ tabId })

Start Chrome with --remote-debugging-port=9222:

Terminal window
# Mac
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-debug \
--headless=new

Then list available targets:

Terminal window
curl http://localhost:9222/json

Connect with any WebSocket client:

import WebSocket from 'ws'
const ws = new WebSocket('ws://localhost:9222/json/version')
ws.on('open', () => {
// Enable Runtime domain
ws.send(JSON.stringify({ id: 1, method: 'Runtime.enable' }))
})
ws.on('message', data => {
const msg = JSON.parse(data)
if (msg.method === 'Runtime.consoleAPICalled') {
const text = msg.params.args.map(a => a.value).join(' ')
console.log('[page console]', text)
}
})

React DevTools extension adds two panels: Components (browse the component tree, inspect props/state/context) and Profiler (record renders and measure component render times).

$r global: After clicking any component in the Components panel, $r in the Console refers to that component instance — its props, state, hooks, and _fiber internals are accessible.

// After selecting a component in React DevTools:
$r.props // current props
$r.state // class component state (undefined for function components)
// For function components, hooks are in the fiber:
$r._debugHookTypes // names of hooks in order
// Force re-render of the selected component (class component):
$r.forceUpdate()

Detecting the React version:

// In Console:
__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers
// Returns a Map of renderer ID → renderer object; renderer.version is the React version

Finding which component owns a DOM element:

// Select a DOM element in Elements panel, then in Console:
const fiber = $0._reactFiber || $0[Object.keys($0).find(k => k.startsWith('__reactFiber'))]
let node = fiber
while (node) {
if (typeof node.type === 'function') {
console.log(node.type.displayName || node.type.name)
}
node = node.return
}

Vue DevTools extension adds a Vue panel with the component tree, props, data, computed values, and a timeline for Vuex/Pinia mutations.

$vm0 global: Selecting a component in Vue DevTools sets $vm0 in the Console to that component’s Vue instance.

// After selecting a component in Vue DevTools:
$vm0.$data // reactive data
$vm0.$props // props received from parent
$vm0.$el // root DOM element
$vm0.$emit('click') // programmatically emit an event
// Vue 3: access the internal instance
import { getCurrentInstance } from 'vue'
// In a component setup(), getCurrentInstance() returns the internal instance

Finding a Vue instance from a DOM node:

// Vue 2:
const vnode = $0.__vue__
// Vue 3:
const vnode = $0.__vueParentComponent

Angular exposes an ng global in development mode:

// Get the component instance for the currently selected DOM element ($0):
const comp = ng.getComponent($0)
comp // the component instance
comp.title // read a property
comp.title = 'New Title' // write a property
ng.applyChanges(comp) // trigger change detection to update the view
// Get all directives on an element:
ng.getDirectives($0)
// Get the injector:
ng.getInjector($0)
// Access a service from the root injector:
const router = ng.getInjector(document.body).get(ng.coreTokens.Router)
router.navigate(['/home'])
// Manually trigger global change detection:
ng.applyChanges(ng.getRootComponents($0)[0])
// Detect which framework is running on the page:
;(function detectFramework() {
const checks = {
React: () => !!(window.__REACT_DEVTOOLS_GLOBAL_HOOK__?.renderers?.size),
'React (no devtools)': () => !!document.querySelector('[data-reactroot], [data-reactid]'),
Vue: () => !!(window.__VUE__ || window.__VUE_DEVTOOLS_GLOBAL_HOOK__),
Angular: () => typeof window.ng !== 'undefined',
Svelte: () => !!document.querySelector('[class^="svelte-"]'),
Ember: () => typeof window.Ember !== 'undefined',
jQuery: () => typeof window.jQuery !== 'undefined',
Next: () => !!(window.__NEXT_DATA__),
Nuxt: () => !!(window.__nuxt__ || window.$nuxt),
}
Object.entries(checks).forEach(([name, test]) => {
if (test()) console.log(`Detected: ${name}`)
})
})()

9229/...
node --inspect app.js

Then open chrome://inspect in Chrome. Your Node process appears under Remote Target. Click inspect to open a DevTools window connected to the Node process — full Sources, Console, Memory, and Profiler panels are available.

Terminal window
node --inspect-brk app.js

The process starts and immediately pauses before executing any user code. The DevTools Sources panel opens at the first line of your entry file. Use this when you need to debug initialization code (DB connection setup, config loading, etc.) that runs before you could otherwise set a breakpoint.

If the Node process is on a different machine (or inside Docker):

Terminal window
# On the remote machine:
node --inspect=0.0.0.0:9229 app.js
# On your local machine, forward the port via SSH:
ssh -L 9229:localhost:9229 user@remote-host
# Then open chrome://inspect → Configure → add localhost:9229

Both VS Code’s debugger and Chrome DevTools can be connected to the same Node process at the same time via CDP — they share the debugger session. Set up a launch.json in VS Code:

{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach to Node",
"port": 9229,
"restart": true,
"skipFiles": ["<node_internals>/**"]
}
]
}

With "restart": true, VS Code automatically re-attaches when the process restarts (e.g., with nodemon).

Terminal window
nodemon --inspect app.js

Each time nodemon restarts the process, a new CDP endpoint is created. In chrome://inspect, the target will reappear automatically after restart. In VS Code with "restart": true, it re-attaches automatically.

Terminal window
node --inspect app.js

In the connected DevTools, go to the Profiler panel → Start → run your workload → Stop. The flame chart shows CPU time spent in each function, identical to browser profiling. Use this to find hot functions in server-side rendering, API handlers, or data processing pipelines.


Access via DevTools Settings (F1) → Experiments tab, or via chrome://flags for some options.

ExperimentDescriptionWhy Enable It
CSS Grid EditorVisual editor for grid-template in the Styles pane — click to add/remove rows and columnsFar faster than hand-editing grid syntax; shows a live interactive grid overlay
Local overrides for request headersApply custom response headers from Local Overrides, not just bodyTest CORS policies, CSP variations, and caching headers without a proxy
CSS Overview panelAnalyzes the page and generates a report: color palette, font inventory, unused declarationsOne-click CSS audit; great for design system consistency review
Automatic contrast ratio suggestionsIn color picker, automatically suggests WCAG AA/AAA-compliant alternativesInstant a11y feedback while adjusting colors
Source map scopesShows original variable names in the Scope panel when a source map is availableMakes debugging minified/transpiled code feel like debugging source
Enable network panel with fetch priorityAdds a Priority column to the Network panel showing browser fetch priority (Highest/High/Medium/Low)Understand which resources the browser deprioritizes; tune fetchpriority attribute
Improved breakpoints UIUnified breakpoints sidebar showing logpoints, conditional breakpoints, and DOM breakpoints in one listEasier management when you have many breakpoints across files
Authored/Deployed grouping in SourcesGroups the file tree by “Authored” (original source via source maps) vs “Deployed” (actual network files)Cleaner navigation in projects with build steps — you only see the files you wrote

A prioritized set of 15 actions that reliably improve Core Web Vitals and perceived performance. Run a baseline Lighthouse audit first, then work through these in order.

  • 1. Enable text compression — Verify responses have Content-Encoding: gzip or br. In Network panel, compare the Size column (transferred) to the Content column (decompressed). Text assets (JS, CSS, HTML, JSON, SVG) should compress to 60–80% of original size.

  • 2. Remove render-blocking resources — In Lighthouse, check the “Eliminate render-blocking resources” item. Move non-critical CSS to <link rel="preload"> or inline critical CSS. Defer all non-critical JavaScript with async or defer.

  • 3. Audit unused JavaScript — Open the Coverage panel (Command Menu → “Show Coverage”), reload the page, and look for JS files where >50% of bytes are unused. Code-split or lazy-load those modules.

  • 4. Audit unused CSS — Same Coverage panel. Remove dead selectors or scope them to the routes that actually use them.

  • 5. Size images correctly — In the Network panel, filter by resource-type:image. Check the Dimensions of each image in its preview. If the rendered size (in Elements) is far smaller than the intrinsic size, add srcset/sizes or a responsive image pipeline.

  • 6. Serve images in next-gen formats — PNG/JPG → WebP or AVIF. Check the MIME type column in the Network panel; image/webp or image/avif should appear for most images.

  • 7. Implement lazy loading — Add loading="lazy" to all <img> and <iframe> elements that are below the fold. Verify in Network panel that those requests are deferred until scroll.

  • 8. Preload LCP resource — In the Performance panel, identify the Largest Contentful Paint element. If it is a hero image or web font, add <link rel="preload" as="image" href="..."> (or as="font") in <head>. Verify it appears in the Waterfall with high priority.

  • 9. Reduce main-thread blocking time (TBT/FID) — In Performance, look for Long Tasks (red triangles). Break up tasks longer than 50 ms using requestIdleCallback, setTimeout(..., 0), or the Scheduler API.

  • 10. Avoid layout thrashing — In Performance, look for Recalculate Style → Layout in rapid sequence (purple + green). This indicates JS is reading layout properties (like offsetHeight) and writing styles in alternating loops. Batch reads and writes using requestAnimationFrame.

  • 11. Reduce CLS (Cumulative Layout Shift) — In Performance, look for Layout Shift records. Common causes: images without width/height attributes, ads injecting content, web fonts causing FOUT. Always set explicit dimensions on media elements.

  • 12. Enable caching for static assets — In Network panel headers, static assets (JS bundles, images, fonts) should return Cache-Control: max-age=31536000, immutable. HTML documents should return Cache-Control: no-cache (with ETag for revalidation).

  • 13. Preconnect to critical third-party origins — Identify critical cross-origin resources (fonts, analytics, API). Add <link rel="preconnect" href="https://fonts.googleapis.com">. Verify in the Network waterfall that the DNS+TCP+TLS time for first requests to that origin is eliminated.

  • 14. Minimize third-party impact — In Lighthouse’s “Reduce the impact of third-party code” section, identify blocking third parties. Delay non-critical third-party scripts (analytics, chat widgets) until after the page is interactive using requestIdleCallback.

  • 15. Check memory leaks in long sessions — In the Memory panel, take a heap snapshot, interact with the page for several minutes, take another snapshot, then use the Comparison view to find objects that grew unexpectedly. Common culprits: event listeners not removed on component unmount, global caches growing without eviction, retained DOM nodes.


Last updated: 2026-07-28. All shortcuts verified against Chrome 126+. Snippets tested in Chrome DevTools Console.


← Web Devtools