Menu

DevTools Debugging Chrome Developer Skills March 2026 ⏱ 15 min read

How to Use Browser Developer Tools: A Practical Guide

Master the Console, Elements, Network, Sources, Application, and Performance tabs with practical techniques that immediately improve how you debug, inspect, and optimise web applications.

Browser developer tools are built into every modern browser and are available the moment you open any web page. Yet most developers use only a fraction of what they offer, relying on the Console tab and a few console.log statements while the rest of the toolkit sits unused. This guide covers every tab with practical techniques that translate directly into faster debugging and better code.

Opening Developer Tools

Every major browser includes developer tools built in. No installation is required. The keyboard shortcuts differ slightly between browsers and operating systems:

Chrome
WindowsF12
WindowsCtrl+Shift+I
macOSCmd+Opt+I
Firefox
WindowsF12
WindowsCtrl+Shift+I
macOSCmd+Opt+I
Safari
macOSCmd+Opt+I
Enable first: Safari → Settings → Advanced → Show features for web developers
Edge
WindowsF12
WindowsCtrl+Shift+I
macOSCmd+Opt+I

There is also a faster way to open DevTools focused on a specific element: right-click any element on the page and select Inspect. This opens the Elements tab with that exact element already selected and highlighted, saving the time it takes to navigate to the element manually in the DOM tree.

The fastest way to open DevTools on any element is to right-click it and select Inspect. The Elements tab opens with that element already selected.

The Console Tab

Console
Your first stop for every JavaScript problem
Errors, warnings, log output, and a live JavaScript REPL

The Console tab displays every JavaScript error and warning the page generates, along with any output from console methods in your code. It is also a fully interactive JavaScript environment: you can type and execute any JavaScript directly against the live page, query and manipulate the DOM, and test code snippets without editing any source files.

Reading Error Messages Correctly

The most common Console mistake is skimming error messages rather than reading them. Every error contains three pieces of information that together usually identify the problem completely:

Console error output
TypeError: Cannot read properties of undefined (reading 'email') at getUserEmail (app.js:42:23) at handleLogin (app.js:89:15) at HTMLButtonElement.onclick (index.html:34:8)
What this error tells you
  • Type: TypeError — something is the wrong type
  • Message: tried to read .email from an undefined value
  • Location: line 42 of app.js, column 23
  • Call path: onclick called handleLogin called getUserEmail
What to do with it
  • Click the file:line link to jump to the exact location
  • Check what variable is undefined at that line
  • Read the stack bottom-to-top to understand how you got there
  • Add a console.log before line 42 to inspect the value

Console as a Live JavaScript Environment

The console is not just for reading output. You can type JavaScript directly into the prompt at the bottom and execute it against the live page. This is invaluable for testing selectors, inspecting variables, and trying fixes before writing them into source files:

console — live REPL examples
// Query DOM elements directly document.querySelector('.user-card') document.querySelectorAll('[data-role="admin"]') // Access JavaScript values in the page's scope window.userSession localStorage.getItem('auth_token') // Test a function with specific data validateEmail('test@example.com') formatCurrency(149.9, 'USD') // Manipulate the DOM live document.querySelector('h1').textContent = 'Testing' document.body.style.background = 'red' // $_ returns the last evaluated value document.querySelectorAll('button') $_.length // number of buttons on the page

Console Methods Reference

Most developers use only console.log. The full set of console methods covers significantly more debugging scenarios, and several are dramatically more readable for specific data types:

Method Best For When to Reach for It
console.log() General output, any value Checking variable values, confirming code paths were reached
console.table() Arrays of objects Inspecting API responses, user lists, order arrays: renders as a clean table with sortable columns
console.error() Caught errors Logging errors in catch blocks so they stand out visually from regular output
console.warn() Non-fatal warnings Flagging conditions that are abnormal but not breaking
console.group() / groupEnd() Related log sets Grouping logs from a single request or loop iteration under a collapsible label
console.time() / timeEnd() Elapsed time measurement Measuring how long a function, fetch call, or loop takes without a full performance profile
console.trace() Full call stack Understanding how execution arrived at a specific point when the call chain is unclear
console.assert() Conditional logging Log only when a condition is false: console.assert(arr.length > 0, 'Empty array!')
console.count() Execution counting Counting how many times a code path is hit without a manual counter variable
console.dir() Object properties Inspecting all properties of a DOM element or object as an expandable tree
💡 console.table() is the most underused console method

When your API returns an array of objects, console.table(response.data) renders it as a clean, sortable table instead of a collapsed object in the console. You can click column headers to sort, making it easy to scan for anomalies, missing fields, or unexpected values. This single method eliminates the need to manually expand nested objects in most API debugging scenarios.

The Elements Tab

Elements
Inspect and edit the live DOM and CSS
Every HTML element, every applied style, every computed value

The Elements tab shows the live Document Object Model of the current page. It reflects the actual state of the DOM as modified by JavaScript, not just the original HTML source. This distinction is critical: what you see in Elements is what the browser has actually rendered, which may differ significantly from the raw HTML file.

Editing HTML
  • Double-click any element to edit its text content directly
  • Double-click an attribute name or value to edit it
  • Right-click an element to add, edit, or delete attributes and child nodes
  • Drag elements to reorder them in the DOM
  • Press Delete to remove an element from the DOM temporarily
Editing CSS in the Styles panel
  • Click any CSS value to edit it inline
  • Click the + button to add a new CSS declaration
  • Toggle rules on and off with the checkbox next to each rule
  • Click the colour swatch to open a colour picker
  • The :hov button forces hover, focus, and active states
The Computed tab
  • Shows the final resolved value of every CSS property after all cascade and inheritance
  • Click any property to see which CSS rule is setting it and where that rule comes from
  • Useful when a style is being overridden and you cannot find the overriding rule
Accessibility tree
  • Shows how the page is represented to screen readers
  • Use to verify ARIA labels, roles, and keyboard navigation
  • Available in the Accessibility sub-panel of Elements
⚠ Changes in Elements are not saved to files

Every edit you make in the Elements tab is applied to the live DOM in memory. When you refresh the page, all changes are lost. Use the Elements tab to experiment and validate changes, then transfer the confirmed changes to your actual source files. Some browsers offer a Workspaces or Local Overrides feature that can map DevTools edits to local files, but this should be set up intentionally.

The Network Tab

Network
Every HTTP request, response, and timing your page makes
Essential for debugging API calls, auth failures, and performance

The Network tab records every HTTP request the page makes. API calls, image loads, CSS files, JavaScript bundles, font requests: everything is logged with its full request and response details. This is where you go when an API call is failing, when you need to see what data your application is actually sending and receiving, or when you are investigating why a page is loading slowly.

Reading a Network Request

Click any request in the Network tab to open its detail panel. The sub-tabs inside show different aspects of the request-response cycle:

Network request detail panel — tabs and contents
Headers Payload Response Preview Timing Initiator
HeadersRequest and response headers including Authorization, Content-Type, and any custom headers. Check this when auth is failing or when CORS errors appear.
PayloadThe request body for POST, PUT, and PATCH requests. Shows exactly what your code sent to the server. Check this before assuming the server has a bug.
ResponseThe raw response body exactly as the server returned it. Copy this to validate with the JSON Formatter.
PreviewThe response rendered as an expandable tree. Useful for quick inspection but sometimes differs from the actual raw response text.
TimingBreakdown of time spent in each phase: DNS lookup, TCP connection, SSL handshake, waiting (TTFB), and content download. Use to find where time is being lost.
InitiatorWhich code triggered this request. The stack trace shows the chain of function calls that led to the fetch.
Filtering requests
  • Use the filter bar to show only XHR/Fetch requests (API calls)
  • Filter by Doc, Script, Img, or Media to isolate specific asset types
  • Type a URL fragment in the filter box to find a specific endpoint
  • Hold Shift to select multiple filter types simultaneously
Time-saving network tricks
  • Right-click any request: Copy as cURL reproduces the exact request in your terminal
  • Right-click: Copy as fetch generates a JavaScript fetch() call you can run in console
  • Tick Preserve log to keep requests across page navigations
  • Tick Disable cache to always fetch fresh resources during development
💡 Paste API response bodies into the JSON Formatter

The Network tab Preview panel sometimes reformats JSON in ways that are hard to read for deeply nested structures. Copy the response body from the Response tab and paste it into the JSON Formatter for a clean, fully indented, colour-coded view. This is especially useful when debugging APIs that return complex nested responses, or when the Preview tab is not rendering the response cleanly due to non-standard Content-Type headers.

The Sources Tab and Breakpoints

Sources
All loaded files and the full breakpoint debugger
Pause execution, inspect state, and step through code line by line

The Sources tab shows every file the page has loaded: JavaScript files, CSS files, HTML, source maps, and service workers. More importantly, it is where you set breakpoints to pause JavaScript execution at any line and inspect the complete state of the application at that exact moment. Breakpoints are significantly more powerful than console.log because you see everything at once rather than what you anticipated logging.

Setting a Standard Breakpoint

  1. Open the Sources tab. Use Cmd+P (Chrome) or Ctrl+P to search for a JavaScript file by name.
  2. Click the line number where you want execution to pause. A blue marker appears on the line.
  3. Trigger the code path that runs that line: click a button, submit a form, or reload the page.
  4. The browser pauses execution. Hover over any variable in the code to see its current value in a tooltip.
  5. Check the Scope panel on the right to see all local and closure variables and their current values at this exact moment.
  6. Use F10 to step over the current line (execute it without entering any function calls), F11 to step into a function call, and F8 to resume normal execution.

Conditional Breakpoints and Logpoints

Standard breakpoints pause every time a line is reached. When a bug only occurs for specific data or a specific user, pausing on every iteration wastes significant time. Two alternatives solve this:

Conditional breakpoints
  • Right-click a line number: select Add conditional breakpoint
  • Enter a JavaScript expression: user.id === 42 or items.length === 0
  • The debugger only pauses when the expression evaluates to true
  • Skips thousands of normal iterations to land exactly on the failing case
  • The expression has full access to all in-scope variables
Logpoints
  • Right-click a line number: select Add logpoint
  • Enter an expression to log: user.id, user.email
  • The value is printed to the Console on every pass without pausing
  • Identical to adding console.log but without modifying source files
  • Works on production builds where you cannot edit files
JavaScript — debugger statement in code
// The debugger keyword triggers a breakpoint from code function processOrder(order) { debugger; // DevTools pauses here when open const total = calculateTotal(order.items); return { ...order, total }; } // Conditional debugger in code function processItems(items) { items.forEach(item => { if (item.price < 0) debugger; // Only pauses for negative prices applyDiscount(item); }); }

The Application Tab

Application
Everything the browser stores for the current site
Cookies, localStorage, sessionStorage, IndexedDB, and service workers

The Application tab gives you full visibility and control over everything stored in the browser for the current origin. This is where you inspect and debug authentication tokens, cached data, service worker state, and all browser storage mechanisms.

Cookies
  • View all cookies for the current domain with their names, values, expiry dates, and security flags
  • Double-click any cookie value to edit it directly
  • Click the delete icon to remove a specific cookie
  • Check the HttpOnly, Secure, and SameSite columns for security auditing
  • The Clear All button removes every cookie for the domain
localStorage and sessionStorage
  • View all key-value pairs stored by the site
  • Double-click any value to edit it in place
  • Right-click a row to delete a specific key
  • sessionStorage data is cleared when the tab is closed
  • Useful for inspecting and manually modifying auth tokens during development
IndexedDB and Cache Storage
  • Inspect structured data stored by service workers and PWAs
  • Browse IndexedDB databases, object stores, and individual records
  • Inspect what is cached by service workers in Cache Storage
  • Delete cached responses to force fresh fetches during development
Service Workers
  • View the registration status of all service workers for the origin
  • Use the Update button to force a service worker update immediately
  • Use Unregister to remove a service worker for a clean state
  • The Offline checkbox simulates offline mode to test PWA behaviour

The Performance Tab

Performance
Record and analyse CPU time, rendering, and paint
Find the exact JavaScript functions and rendering operations consuming the most time

The Performance tab records a timeline of everything that happens during a page load or user interaction: JavaScript execution, style recalculation, layout, paint, and composite operations. It tells you exactly where time is being spent, which functions are consuming CPU, and which rendering operations are causing jank.

Recording a profile
  • Click the record button (circle) in the Performance tab
  • Perform the action you want to profile: click a button, scroll, load the page
  • Click the stop button. The timeline populates immediately.
  • Use Ctrl+Shift+E (or Cmd+Shift+E) to start a page-load recording automatically
  • Keep profiles short (2-5 seconds) for readable timelines
Reading the profile
  • The flame chart shows function call stacks over time. Wider bars take longer.
  • The Bottom-Up tab shows functions sorted by total CPU time consumed
  • The Call Tree tab shows the hierarchy of calls contributing to each function
  • Red triangles in the FPS bar indicate dropped frames (jank)
  • Long yellow bars indicate JavaScript blocking the main thread

Quick Power Techniques That Save the Most Time

These techniques are available in Chrome and Edge DevTools and solve specific debugging scenarios that come up regularly. Each one takes under a minute to learn and provides immediate time savings:

Power techniques — Chrome and Edge DevTools
The techniques most developers have never used
📱
Device simulation
Click the device icon in the toolbar (or Ctrl+Shift+M) to simulate any mobile screen size, pixel density, and touch events. Test responsive layouts without a physical device.
🐌
Network throttling
In the Network tab, change the throttle dropdown from "No throttling" to Slow 3G or Fast 3G. Reveals loading performance issues that are invisible on fast connections.
🚫
Block requests
Right-click any request in the Network tab and select Block request URL. The browser blocks that URL on all subsequent page loads. Tests how your site behaves when a third-party service is unavailable.
📋
Copy as cURL
Right-click any request in the Network tab and select Copy as cURL. Produces a terminal command that reproduces the exact request with all headers and auth. Invaluable for reproducing API issues outside the browser.
🎯
Force CSS states
In the Elements tab Styles panel, click the :hov button to force :hover, :focus, :active, :visited, and :focus-within states on any element. Inspect and edit styles for interactive states without holding the mouse.
🔍
Search across all files
Press Ctrl+Shift+F (Cmd+Shift+F on Mac) to open a global search across all loaded files. Find every use of a function name, a class name, or any string across the entire page's codebase.
💡
Disable JavaScript
Press Ctrl+Shift+P to open the command palette, type "Disable JavaScript". The page reloads without running any JavaScript. Useful for testing progressive enhancement and server-rendered content.
🎨
CSS overview
In the command palette (Ctrl+Shift+P), type "CSS overview". Generates a summary of all colours, fonts, media queries, and unused CSS declarations on the page. Useful for design audits.

7-Step Debugging Workflow Using DevTools

When a bug appears, this is the systematic sequence that uses DevTools most effectively. Following the steps in order prevents the guessing cycle that wastes most debugging time:

  1. Open the Console tab first and read every error completely. Do not skim. Read the error type, the message, the file name, and the line number. Click the file:line link to jump directly to the location. Most bugs are solvable from the error message alone if you read it carefully rather than immediately searching for the error text online.
  2. Check the Network tab for any failed requests. Failed requests appear highlighted in red. Click each one and check the Status code, the Payload (what was sent), and the Response (what came back). A 401 means auth failed. A 400 means the request body is malformed. A 500 means the server has an error. The status code determines the next step before you read anything else.
  3. For API responses, copy the body to the JSON Formatter. Copy the response body from the Network tab Response panel and paste it into the JSON Formatter. The formatted view reveals the actual structure, including nullable fields, unexpected types, missing expected fields, and nesting levels that are easy to miss in the compact Network tab view.
  4. Add a breakpoint at the first line where the bad value is used. Now that you know which value is wrong from inspecting the response, add a breakpoint at the line in your JavaScript that first reads that value. When execution pauses, hover over every variable the line depends on to see their exact values at that moment.
  5. Use the Scope panel to trace the value backward. While paused at the breakpoint, expand the Scope panel to see all variables in the current scope and all parent scopes. If the variable you are looking for has an unexpected value, look at where it was set. Use the Call Stack panel to step backward through the execution path to find where the value became wrong.
  6. For production-only bugs, compare responses with the Text Diff Checker. When a bug only appears in production, the cause is almost always a data difference. Copy the production API response and the development API response, then paste both into the Text Diff Checker side by side. Every difference between them is highlighted, and the difference is almost always the bug.
  7. Verify the fix with the same conditions that caused the bug. After making the fix, reload the page, clear the console, and reproduce the exact user action or data condition that triggered the original bug. Confirm the error no longer appears in the Console tab and the Network tab shows the expected response status codes. Do not declare a bug fixed without re-running the original conditions.

Frequently Asked Questions About Browser DevTools

Are Chrome DevTools different from Firefox DevTools?

The core tabs and features are largely the same: both have Console, Elements, Network, Sources (called Debugger in Firefox), Application (called Storage in Firefox), and Performance tabs with similar functionality. The interfaces differ in layout and some feature names, but the concepts transfer directly. Chrome DevTools is generally considered the most feature-rich and is the de facto standard for web development. Firefox DevTools has a notably good CSS grid inspector and layout visualisation tools. Edge DevTools are nearly identical to Chrome DevTools since Edge uses the same Chromium engine. For most debugging tasks, the browser you prefer to develop in works well.

How do I debug JavaScript in a mobile browser?

For Android devices with Chrome, enable USB debugging in Android developer settings, connect the device via USB, then navigate to chrome://inspect in desktop Chrome. Your Android Chrome tabs appear under Remote Targets and you can click Inspect to open a full desktop DevTools window connected to the mobile browser. For iOS with Safari, enable Web Inspector in iOS settings under Safari, connect via USB, and open the Develop menu in desktop Safari (which must have developer tools enabled). For most responsive design testing, the device simulation mode in desktop Chrome DevTools (Ctrl+Shift+M) is faster than connecting a physical device.

Why does my JavaScript look minified and unreadable in the Sources tab?

Production JavaScript is almost always minified: whitespace removed, variable names shortened, multiple files bundled into one. The Sources tab shows this minified code by default. To make it readable, click the { } button at the bottom of the Sources panel (called "Pretty-print"). This reformats the minified code with proper indentation. For the best debugging experience, your build tool should generate source maps, which DevTools uses to show you the original unminified source with original file names and line numbers, even when running the minified production build.

What is the difference between local storage, session storage, and cookies?

All three store data in the browser, but with different lifetimes and access rules. localStorage persists indefinitely across sessions: data survives the browser being closed and reopened. It is accessible via JavaScript but not sent to the server automatically. sessionStorage is cleared when the browser tab or window is closed. It is isolated per tab. Cookies are sent to the server on every HTTP request to the matching domain, which is how session authentication works. Cookies have configurable expiry dates, can be restricted to secure connections with the Secure flag, and can be made inaccessible to JavaScript with the HttpOnly flag. In the Application tab, you can inspect and modify all three types to debug auth, session, and caching behaviour.

How do I prevent the Network tab from clearing when I navigate to a new page?

Check the Preserve log checkbox at the top of the Network tab. With Preserve log enabled, all requests are retained across page navigations and form submissions. This is essential when debugging redirects, form submissions that navigate away, or any multi-step flow where you need to see requests from earlier pages. Without Preserve log, the Network tab clears on every navigation and you lose the request history from the previous page.

Can I use DevTools to inspect HTTPS requests and see encrypted traffic?

Yes. Browser DevTools intercepts requests at the browser level, after the HTTPS layer has been decrypted by the browser. This means you see all request and response contents in plain text regardless of whether the connection uses HTTPS. The encryption happens between the browser and the server: DevTools runs inside the browser and sees the decrypted data. This is different from a network packet capture tool like Wireshark, which would only see encrypted bytes. For inspecting traffic from non-browser applications, you would need a proxy tool like Charles or mitmproxy that sits between the application and the server.

Free browser-based developer tools

Tools that pair with your DevTools workflow

Format and validate JSON from Network tab responses, compare API responses between environments, convert data formats, and more. All free, all in your browser, no login required.

DevTools Are Already Open. Start Using All of Them.

Browser developer tools are one of the most powerful development environments available and they require no installation, no configuration, and no cost. The Console, Network, Sources, Elements, Application, and Performance tabs each solve a specific class of debugging problem, and mastering the full set rather than relying only on console.log transforms how quickly you can diagnose and fix issues.

The techniques that deliver the most immediate improvement are: reading error messages completely before doing anything else, using breakpoints instead of console.log for complex state inspection, checking the Network tab Payload before assuming a server bug, and using conditional breakpoints and logpoints for bugs that only occur on specific data. These four habits alone will cut debugging time significantly.

Keep the JSON Formatter open alongside DevTools whenever you are inspecting API responses. The Network tab Preview panel is convenient but limited: the JSON Formatter shows the full structure clearly, reveals nullable fields, and makes the data shape visible before you write any code that depends on it. For production bugs, the Text Diff Checker finds data differences between environments in seconds.