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:
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
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:
- 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
- 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 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 |
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
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.
- 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
- 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
- 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
- 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
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
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:
- 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
- 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
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
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
- Open the Sources tab. Use Cmd+P (Chrome) or Ctrl+P to search for a JavaScript file by name.
- Click the line number where you want execution to pause. A blue marker appears on the line.
- Trigger the code path that runs that line: click a button, submit a form, or reload the page.
- The browser pauses execution. Hover over any variable in the code to see its current value in a tooltip.
- Check the Scope panel on the right to see all local and closure variables and their current values at this exact moment.
- 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:
- 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
- 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
The Application Tab
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.
- 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
- 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
- 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
- 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
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.
- 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
- 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:
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:
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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
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.
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.
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.
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.
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.
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.
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.