Debugging Browser Extensions with DevTools: Inspecting Background Workers and Content Scripts

Extension debugging requires opening multiple DevTools windows. The popup, the background service worker, and the content scripts running in each tab are all separate execution contexts with separate console outputs and separate debugging surfaces. Developers who haven’t done extension debugging before often spend twenty minutes confused about why their console.log isn’t appearing — the answer is always that they’re looking in the wrong DevTools window.

The Extension Management Page

Start at chrome://extensions (or about:debugging for Firefox). Enable Developer Mode in the top-right corner of the Chrome extensions page if it isn’t already on. This exposes the debugging interfaces.

For each installed extension you’ll see:

  • Extension ID
  • “Inspect views” — links to open DevTools for the background service worker, popup page, and options page if they exist.

Click “service worker” under your extension to open a DevTools window connected to the background service worker. This is the DevTools window for background logic — console.logs from the service worker appear here, not in any page’s DevTools.

The service worker DevTools behaves like a normal DevTools console and Sources panel but in the service worker context. You can set breakpoints, step through code, and inspect variables in the service worker just as you would for a web page.

Content Script Debugging

Content scripts run in the context of the page they’re injected into, but in a separate JavaScript sandbox. They share the DOM with the page’s JavaScript, but they don’t share the JavaScript scope. The window object in a content script is not the same as the window object in the page’s JavaScript.

To debug content scripts:

  1. Open DevTools for the page the content script is injected into (F12 from the page).
  2. Go to Sources → Content scripts (a section in the left sidebar, separate from the page’s own sources).
  3. Find your extension’s content scripts under the extension ID.
  4. Set breakpoints and inspect normally.

Console output from content scripts appears in the page’s DevTools console with a label showing the extension source. If your content script calls console.log("hello"), it appears in the page’s console, not in the service worker console.

The popup page is a normal HTML page with extension API access. To debug it:

  • Click the extension icon to open the popup.
  • Right-click anywhere in the popup → Inspect.
  • This opens a DevTools window attached to the popup’s document.

The popup DevTools shows the popup’s DOM, JavaScript context, network requests made by the popup, and console output. This is where you debug popup UI logic.

Important: the popup closes when it loses focus. Right-clicking to Inspect keeps it open long enough to connect DevTools, but navigating away will still close it. For debugging popup initialization code that runs on open, add a debugger; statement or a brief setTimeout to pause execution until you’ve attached DevTools.

The Console Routing Summary

ContextWhere to see console output
Background service workerDevTools for the service worker (from chrome://extensions)
Content scriptPage’s DevTools console (labeled with extension source)
PopupPopup’s own DevTools (right-click → Inspect in popup)
Options pageOptions page’s own DevTools

Misrouting console output is the most common source of “I can’t see my logs” confusion.

Debugging Message Passing

Message passing between contexts is where integration bugs concentrate. A content script sends a message; the background worker receives it and sends a response; the content script processes the response. Any step can fail silently.

Add logging at every message passing boundary:

// Content script
chrome.runtime.sendMessage({type: 'getData'}, response => {
  if (chrome.runtime.lastError) {
    console.error('Message failed:', chrome.runtime.lastError.message);
    return;
  }
  console.log('Response received:', response);
});

// Background service worker
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  console.log('Message received:', message, 'from tab:', sender.tab?.id);
  // Must return true if sendResponse is called asynchronously
  sendResponse({data: 'result'});
  return false; // or true for async response
});

A common bug: forgetting to return true from the onMessage listener when the response is sent asynchronously. Without this, the message channel closes before the async operation completes and the response is never received.

The chrome.runtime.lastError check is essential in the sending context. Unchecked, a runtime error from an unresponded message throws an uncaught error. With the check, you get a descriptive error message.

Breakpoints in Service Workers

Service worker code can be paused with breakpoints in the Sources panel of the service worker DevTools window. One complication: service workers can be stopped by the browser to save resources when idle. If a service worker is stopped, breakpoints in it won’t trigger.

Force the service worker to stay alive during debugging:

  1. In chrome://extensions, your extension should show an “inspect” link for the service worker.
  2. Once you’ve opened the service worker DevTools, the service worker stays running as long as that DevTools window is open.

Alternatively, add chrome.runtime.onInstalled.addListener(() => {}) or another persistent listener — having an active listener keeps the service worker alive for a period after events.

Debugging the Storage API

When debugging storage bugs, read the current storage state directly from the console:

In the service worker DevTools console:

// Read all storage
chrome.storage.local.get(null, data => console.log(data));

// Clear storage for a fresh state
chrome.storage.local.clear(() => console.log('Cleared'));

In the page DevTools, storage is also visible under Application → Storage → Extension Storage. This gives a tree view of the stored data that’s useful for spot-checking without console commands.

Firefox Extension Debugging

Firefox’s extension debugging is at about:debugging#/runtime/this-firefox. Click “Inspect” on your extension to open a DevTools window showing the extension’s background page and registered content scripts.

Firefox’s extension DevTools differ from Chrome’s in that Firefox still supports background pages (persistent, not service workers) for MV2 extensions, which stay alive continuously and are easier to debug — no service worker lifecycle management. For MV3 extensions in Firefox, the debugging workflow is similar to Chrome.

Handling Extension Reloads During Development

When you change extension code, the extension needs to reload to pick up changes. In chrome://extensions, click the reload circular arrow on the extension card. Note:

  • Reloading the extension terminates and restarts the background service worker. Any in-memory state is lost.
  • Content scripts already injected into pages are not re-injected by default. Close and reopen the tab to get the new content script version.
  • The popup closes and reopens normally after reload.

For faster iteration, the web-ext tool from Mozilla supports automatic reload on file changes and works for Chrome extensions too.

FAQ

Why don’t my console.log statements from the background worker appear in the page console? Background service worker logs appear in the service worker’s own DevTools window (accessible from chrome://extensions), not in any page’s DevTools. Open the service worker DevTools from the extensions page.

How do I debug an extension on a specific site without affecting other sites? Use Chrome profiles. Open Chrome with a new profile, install the extension only in that profile, navigate to the test site. This isolates the debugging environment from your regular browser state.

Can I use source maps with extension DevTools? Yes. If you build your extension with a bundler that generates source maps, add "//# sourceMappingURL=..." comments to the bundled output. Chrome DevTools in the extension context respects source maps the same way as for web pages.

The extension’s service worker keeps stopping during debugging — why? Service workers stop after a period of inactivity (typically 5 minutes in Chrome). Keep the service worker DevTools window open to prevent this. If the worker stops mid-debug session, reopen it from chrome://extensions and re-set your breakpoints.