Run extension logic with MV2 background scripts or MV3 service workers. Extension.js compiles entries from manifest.json and applies reload behavior.
Run extension-wide logic in background contexts with clear support for both Manifest V2 (MV2) background scripts and Manifest V3 (MV3) service workers.Extension.js reads background entries from manifest.json and compiles them into dedicated background outputs. It applies browser-specific reload behavior during development.
Firefox: service worker translated to an event page
Firefox does not run MV3 background.service_worker (loading such an add-on fails with “background.service_worker is currently disabled. Add background.scripts.”). You don’t need to special-case it: for the Firefox build, Extension.js automatically translates a background.service_worker entry into a background.scripts event page pointing at the same compiled bundle. Author one background.service_worker and it loads on both Chromium and Firefox.
The translation runs in the other direction too. In a Chromium MV3 build, a background.scripts array becomes background.service_worker pointing at the compiled bundle.Chromium MV3 rejects the whole extension whenever background.scripts is present, even next to a valid service_worker. Extension.js therefore always removes the scripts key from the emitted Chromium MV3 manifest. When the source declares both fields, the existing service_worker entry wins.
From 4.1.20, Safari builds emit a non-persistent background page. Extension.js translates a background.service_worker entry into background: {scripts: [...]}, the same translation it runs for Firefox.The reason was measured. A Manifest V3 service worker never started on Safari 26.5.2, including for extensions built by Apple’s own converter. A background page ran immediately. WebKit deliberately prefers the page form (WebKit bug 270750).
One unguarded Chromium-only call at the top level of a background script
throws on Safari, and Safari then discards the background context. You get no
logs, no reload, and no error, so the extension looks dead. Guard the
call, or branch at build time.
// Throws on Safari and takes the whole background context with it.chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });// Safe on every target.chrome.sidePanel?.setPanelBehavior({ openPanelOnActionClick: true });
Safari builds from 4.1.20 also warn about the extension APIs that Safari lacks, which is the cheapest way to find this before you start debugging. See Building Safari extensions.
background.service_worker runs under the browser’s service-worker lifecycle, not as a long-running process.That means:
In-memory state can disappear between events.
Design long-running work around events, not process permanence.
Startup should stay small and predictable.
Your code should restore important state from storage or recompute it safely.
Use background.scripts only for MV2-style compatibility cases. For MV3-first extensions, treat the service worker as the central coordinator for browser API calls and cross-context communication.
Some files never appear as import statements but still must ship with the extension. Extension.js traces these into the output so the built extension behaves like the source loaded unpacked in the browser:
importScripts(...) dependencies of classic service workers. String-literal arguments are resolved recursively (a dependency can call importScripts itself) against the emitted worker location, so worker-relative URLs keep working even though the worker moves to background/service_worker.js. WebAssembly sibling files loaded by wasm-bindgen glue (*_bg.wasm) ship alongside their loader.
chrome.scripting.executeScript({files: [...]}) and insertCSS({files: [...]}) payloads. Files referenced only from files: arrays (not declared in manifest content_scripts) are copied verbatim at their extension-root paths.
A referenced file that does not exist in your project produces a build warning naming the missing path, instead of a silent runtime failure.