Chrome Extension Service Worker Explained

Finding What an MV3 Service Worker Does

Find the background entry file, follow its event handlers, and track where a message ends up.

Locate background.service_worker in manifest.json

Download a public package with CRXFile and extract the ZIP to inspect it without installation. Start with our manifest reading guide if the entry fields are unfamiliar. This excerpt comes from the original Source Review Demo ZIP:

{
  "manifest_version": 3,
  "permissions": [
    "storage"
  ],
  "background": {
    "service_worker": "worker.js"
  }
}

Open worker.js relative to the package root. Follow its dependencies. Chrome accepts one worker entry file, but that file can load other packaged code with importScripts() or static imports when the background declaration includes type: "module". The JSON above is only a manifest excerpt. Chrome's service worker basics documents both loading methods and the lack of dynamic import support.

Read the event before reading the helper

Here's the entire worker. There's one listener and no imported helper:

chrome.runtime.onMessage.addListener((message, sender) => {
  if (
    sender.url?.startsWith("https://example.com/") &&
    message?.type === "PAGE_TITLE" &&
    typeof message.title === "string"
  ) {
    chrome.storage.local.set({ lastTitle: message.title });
  }
});

runtime.onMessage is the trigger. The handler accepts a string title only when the sender URL starts with the exact HTTPS prefix and the message type is PAGE_TITLE. The slash after the host matters: https://example.com.evil/ doesn't pass this check. An absent sender URL fails too. This small guard isn't a general message security policy; larger extensions may need tab or frame checks and limits on payload size.

In a larger worker, search for addListener and record each trigger before chasing helper functions. An action click and an alarm can reach different branches of the same function. Chrome requires event listeners to register synchronously at the top level, as its worker events guide explains. A listener added only after an asynchronous storage read can miss the event that woke the worker.

Trace the message through storage to the popup

Our content script inspection guide covers the page side. In this package, content.js sends the title:

chrome.runtime.sendMessage({
  type: "PAGE_TITLE",
  title: document.title
});
StepEvidenceEffect
Inputcontent.js reads document.titleSends PAGE_TITLE to the extension.
Guardworker.js checks sender, type, and titleRejects messages outside that condition.
Writestorage.local.setReplaces lastTitle with the accepted string.
Readpopup.js gets lastTitleDisplays the saved value as text.
NetworkNo fetch call in this workerNo outbound request along this handler.

The popup reads lastTitle from chrome.storage.local and assigns it to textContent. That closes this particular trail. The worker overwrites one key, so the example keeps the latest accepted title rather than a browsing history. Chrome's storage API reference explains the permission and storage areas.

We checked this flow with mocked Chrome APIs in Node.js on October 25, 2026. A valid message reached storage and the popup reader. Five rejected cases produced no writes: another origin, a lookalike host, the wrong message type, a numeric title, and a missing sender URL. Those checks exercise the JavaScript branches. They don't test Chrome installation, message delivery, or worker lifetime.

There's a timing limit here. The sender doesn't wait for an acknowledgement, and the worker doesn't reply after saving. Opening the popup before the write completes could show the previous value. For a feature that needs confirmed delivery, inspect how it reports completion and handles storage errors.

How MV3 differs from an MV2 background page

An MV2 persistent background page had a document and could remain loaded. MV2 also had nonpersistent event pages, so “old background script” doesn't always mean “always running.” An MV3 service worker has no window or page DOM. It can't read document.title itself, which explains why this example gets the value from a content script.

Chrome can stop an idle worker and restart it for an event. Globals reset. Keep needed state in storage, as this example does, rather than trusting an in-memory variable. The lifecycle documentation describes termination conditions and version-specific exceptions. Don't treat a worker as a permanent loop or confuse onInstalled with an event that fires on every wake-up.

Check what starts each job. runtime.onStartup relates to browser profile startup, rather than every worker restart. An alarms.onAlarm listener waits for a scheduled alarm. If you find a timer, ask whether the task survives shutdown. Chrome's migration guide explains why ordinary timers can disappear when a worker stops.

Follow network requests beyond the first handler

Search for fetch and follow URL builders into imported modules. For each request, write down the destination and the payload. Does it contain the title just received? Does a stored setting change the host? A literal URL search can miss a destination assembled at runtime. Our network request review guide includes a worked example.

Worker cross-origin requests need the appropriate host permissions. Those grants don't remove CORS restrictions from content scripts. Chrome's cross-origin request guide covers that boundary. Our example worker has no network call, but that observation doesn't establish what every other file in an unfamiliar package does.

Check a running worker in Chrome DevTools

For a trusted extension you already run, open chrome://extensions, turn on Developer mode, and select its service worker link under Inspect views. Set a breakpoint inside the handler, trigger the matching action, and inspect the message and call stack. Use Network to compare actual request payloads with the source trace.

Close DevTools before checking idle behavior. Inspecting keeps the worker active, as Chrome's extension debugging tutorial warns. Record the triggering event, source file, accepted input, and final destination. If a bundled helper remains unread, name that file and call in your review note so someone can continue the investigation there.