Fixing "Service worker registration failed"

Diagnose 'Service worker registration failed. Status code: 3/15' in an MV3 extension: wrong paths, top-level exceptions, module type mismatches, unsupported syntax, importScripts errors and missing files in the build.

Published October 2, 2026 Updated October 2, 2026 7 min read
Table of Contents

You load the extension and chrome://extensions shows an error: “Service worker registration failed. Status code: 15” — or status 3 — and the “Inspect views: service worker” link is missing or labelled “(Inactive)”. Nothing in the extension works, because the background never started. The message is terse, but the status code narrows the cause considerably, and almost every case comes down to one of a handful of problems: the file is not where the manifest says, the script throws during its first run, or the manifest and the code disagree about whether the worker is an ES module. This guide walks through the diagnosis. It belongs to service worker fundamentals.

What the status codes mean

Chrome registers the extension’s service worker at install and on every reload, then starts it. Registration fails if Chrome cannot fetch or evaluate the script. The numeric status comes from Chromium’s service worker status codes. 15 means script evaluation failed: the file was found and loaded, but running its top-level code threw — a syntax error, a ReferenceError such as using window or document, an exception in code that runs at import time, or an import statement in a non-module worker. 3 means start worker failed: commonly the file path is wrong, the file is missing from the build, or a module import could not be resolved. Other codes are rarer. In all cases, the cause is usually visible in the “Errors” view on chrome://extensions, which shows the underlying exception and line.

Diagnosing a failed registrationDecision tree: status 3 points to a missing or unresolvable file, status 15 to an exception during top-level evaluation; each branch lists the usual causes.Which status code?3Couldn't startfile or import not foundCheck path + build outputand module imports15Evaluation threwtop-level exceptionRead Errors viewwindow, import, syntaxotherRaredisk, policy, timeoutReload, check policyclean profile
The status code tells you whether the file was found; the Errors view tells you why it failed.

Step-by-step: find and fix the cause

1. Read the real error

1chrome://extensions → Developer mode on → your extension → "Errors"
2  Uncaught ReferenceError: window is not defined
3  Context: Service worker   Stack: sw.js:3 (anonymous function)

Execution context: the extensions page. The Errors view records exceptions thrown during registration with file and line — far more useful than the status code. If it is empty, click “Service worker” under “Inspect views” if present, or use chrome://serviceworker-internals to find the worker and its last error. Clear old errors before reloading so you read only the current failure.

2. Check the path matches the build output

1// manifest.json in the BUILT extension folder
2{ "background": { "service_worker": "background/sw.js", "type": "module" } }
1ls dist/chrome/background/sw.js       # must exist, relative to the folder you loaded

Execution context: the built extension. The path is relative to the extension root — the folder containing manifest.json — not to your source tree. A bundler that outputs sw.js at the root while the manifest says background/sw.js produces status 3. So does loading the source folder instead of the dist folder by mistake. The path must not start with / or ./ in some versions; plain relative paths are safest.

Common causes by status codeTypical mistakes that make service worker registration fail, the status code each usually produces, and the fix.CauseUsual statusFixWrong service_worker path3Match build outputimport without type: module15Add "type": "module"Unresolvable module import3Fix path or bundlewindow / document at top level15Remove or guardSyntax unsupported by Chrome version15Lower build targetimportScripts in module worker15Use import
Most failures are path, module-type or top-level-code problems.

3. Align module type with the code

1// If sw.js contains `import` statements:
2"background": { "service_worker": "sw.js", "type": "module" }
3
4// If sw.js uses importScripts():
5"background": { "service_worker": "sw.js" }            // classic worker, no "type"

Execution context: the manifest. An import statement in a classic worker is a syntax error (status 15); importScripts in a module worker throws (status 15). Module workers also require every imported path to be resolvable relative to the importing file and to include the .js extension — bare specifiers like import x from "lodash" fail unless bundled. See using ES modules in an MV3 service worker.

4. Remove DOM and window references from top-level code

1// Fails at evaluation: libraries or code touching window/document on import
2// import someLib from "./lib-that-reads-window.js";
3
4// Guard shared code
5const g = globalThis;
6const isWorker = typeof g.document === "undefined";

Execution context: the service worker and every module it imports. A dependency that reads window.navigator or document.createElement when imported crashes the worker before any of your code runs — the most common cause of status 15 after a migration. Replace window with globalThis, move DOM-dependent code to an offscreen document, or swap the dependency. Bundle the worker separately and run it in Node (node --check for syntax, a smoke import for top-level errors) in CI to catch this early.

From reload to running workerChrome reads the manifest, fetches the service worker file at the declared path, resolves module imports, evaluates the top-level code, and only then starts and dispatches events; failure at fetch or resolution gives status 3, at evaluation status 15.Read manifestbackground.service_workerFetch filepath in packageResolve importsmodule workersfailure here → status 3Evaluate top levellisteners, imports runFailure → status 15exception thrownRunningevents dispatched
Each stage has its own failure — the status code tells you which stage.

5. Match syntax to your minimum Chrome version

1// esbuild / Vite: target the oldest Chrome you support
2// esbuild --target=chrome116

Execution context: the build. A worker built for the latest syntax fails on older Chrome versions with a syntax error at evaluation. Set the bundler target to your minimum_chrome_version. Users on older browsers see the registration failure as “the extension does nothing”.

6. Catch top-level errors without hiding them

1// sw.js
2try {
3  registerListeners();                     // must stay synchronous
4} catch (err) {
5  console.error("[sw] top-level failure", err);
6  throw err;                               // keep it visible in the Errors view
7}

Execution context: the service worker’s top level. Wrapping top-level setup lets you log context — or report the error to your telemetry endpoint with a fetch — while still rethrowing so Chrome records the failure. Swallowing the exception would register a worker with no listeners, which is worse: it looks healthy and does nothing.

7. Reproduce in a clean profile

If the extension works for you but users report the failure, load the exact store build in a clean Chrome profile on the minimum supported version. Differences in Chrome version, policies that block extension features, or a corrupted profile cache (rare — fixed by reinstalling the extension) account for the remaining cases.

Common mistakes

  • Loading the source folder instead of the build output. Paths and files differ.
  • import in a classic worker. Add "type": "module" or bundle.
  • DOM access in imported libraries. Crashes evaluation silently from your perspective.
  • Build target newer than users’ Chrome. Syntax errors on older versions.
  • Swallowing top-level errors. The worker registers but never works.

Cross-browser variation

  • Chrome / Edge: status codes and the Errors view as described.
  • Firefox: background scripts load in an event page; failures appear in about:debugging → Inspect → Console, with clear exception messages and no numeric status.
  • Safari: errors appear in the Develop menu’s Web Extension Background Content inspector; module workers require recent Safari versions.

Verification

  1. After the fix, chrome://extensions shows no errors and “Inspect views: service worker” is present.
  2. Click it and confirm the console shows your startup log without exceptions.
  3. Stop the worker in chrome://serviceworker-internals and trigger an event; it restarts cleanly.
  4. Add a CI step that evaluates the worker bundle in a worker-like environment and fails on top-level errors.

FAQ

Why does the error only appear after reload, not on install?

Both register the worker; you may simply not have looked at the Errors view after install. Errors persist until cleared.

Can a failed registration break existing users on update?

Yes — the new version’s worker fails to start and the extension stops working. Test the packaged build before publishing.

Does a runtime error in a listener cause this?

No. Listener errors happen after registration and appear as ordinary errors. Registration failures come only from top-level evaluation.

Other MV3 Architecture & Extension Lifecycle Resources