Chrome vs Firefox vs Safari API Gap Reference

A reference table of MV3 extension API gaps between Chrome, Firefox and Safari: background model, storage, networking, UI surfaces, identity, scripting and platform features, with the workaround for each gap.

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

Before writing a cross-browser feature, you need to know whether the API it depends on exists in each engine, behaves the same, or needs a workaround. Documentation is spread across three vendors, each describing its own engine in its own terms, and compatibility tables list method names without saying what to do about the gaps. This page collects the differences that matter in practice for Manifest V3 extensions, grouped by area, with the workaround for each. Use it at design time to decide what a feature can promise on each browser, and alongside building a capability matrix for your extension to track your own feature set. It belongs to cross-browser API compatibility.

How to read the gaps

Three kinds of difference appear below. Missing APIs do not exist in an engine at all — chrome.offscreen in Firefox, chrome.sidePanel in Safari — and need either a different design or a feature that is simply unavailable there. Divergent APIs exist with different behaviour — blocking webRequest survives in Firefox but not Chrome; alarms fire late in Safari — and need code that tolerates both. Shape differences are the same capability with different names or signatures — sidebar_action versus side_panel, browser.* promises versus chrome.* — and are handled by thin adapters. Versions change: these tables describe the stable channels as of this page’s last update, and the guidance throughout is to feature-detect at runtime rather than trust any table, including this one.

Three kinds of cross-browser differenceMissing APIs that need a different design, divergent APIs that need tolerant code, and shape differences that need a thin adapter.Missingoffscreen, sidePanel, declarativeContentredesign or degradeDivergentwebRequest blocking, alarm timingtolerant codeShapesidebar_action vs side_panelthin adapterSamestorage, runtime, tabs basicsshared code
Classify each gap first; the kind of gap decides the kind of fix.

Background and lifecycle

1// Chrome / Safari
2"background": { "service_worker": "sw.js", "type": "module" }
3// Firefox (MV3)
4"background": { "scripts": ["sw.js"], "type": "module" }

Execution context: the manifest per target. Chrome requires a service worker; Firefox’s MV3 background is an event page with a DOM (service worker support is newer and optional); Safari accepts either a service worker or a non-persistent page. Code written for the service worker model runs everywhere.

Background and lifecycleBackground model, DOM availability, idle termination, offscreen documents, alarms timing and runtime.onSuspend across Chrome, Firefox and Safari.CapabilityChrome / EdgeFirefoxSafariBackground modelService workerEvent page (scripts)Worker or pageDOM in backgroundNoYesOnly with pagechrome.offscreenYes (109+)NoNoAlarm timingReliable, 1 min floorReliableDelayed when idleruntime.onSuspendNoYesLimited
Write for Chrome's service worker and it runs in all three; use Firefox's extras only behind detection.

Workarounds. For DOM work, use an offscreen document in Chrome and the event page’s own DOM in Firefox; in Safari with a service worker, use worker-native APIs or do the work in an extension page. For timing, reconcile scheduled work on startup and when UI opens, as in alarms in Firefox and Safari.

Networking and content blocking

1// Detect blocking webRequest support at runtime (Firefox MV3 keeps it)
2const canBlock = (() => {
3  try {
4    chrome.webRequest.onBeforeRequest.addListener(() => {}, { urls: ["https://example.invalid/*"] }, ["blocking"]);
5    return true;
6  } catch { return false; }
7})();

Execution context: the background, during startup. Registering a throwaway blocking listener on an unreachable URL pattern is a reliable probe: Chrome throws for store-installed extensions, Firefox accepts. Remove the probe listener afterwards in real code.

Networking and content blockingdeclarativeNetRequest, blocking webRequest, response body filtering, cookies partitions and webRequestAuthProvider across Chrome, Firefox and Safari.CapabilityChrome / EdgeFirefoxSafarideclarativeNetRequestFullMost featuresMost featuresBlocking webRequestPolicy installs onlyYesNoResponse body accessNofilterResponseDataNoonAuthRequired asyncwebRequestAuthProviderblockingNoCookie partition keysYesYesNo
DNR is the common denominator; Firefox adds blocking webRequest on top.

Workarounds. Express blocking as DNR rules everywhere; add Firefox-only refinements with blocking webRequest behind detection. Check DNR feature support per engine — regex filters, modifyHeaders coverage and rule limits vary — and test static rulesets in each browser before shipping. See declarativeNetRequest rules.

UI surfaces

1// One "open the panel" helper for three engines
2export async function openPanel(windowId) {
3  if (chrome.sidePanel?.open) return chrome.sidePanel.open({ windowId });           // Chrome
4  if (globalThis.browser?.sidebarAction?.open) return browser.sidebarAction.open(); // Firefox
5  return chrome.tabs.create({ url: chrome.runtime.getURL("panel.html") });          // Safari
6}

Execution context: the background or an extension page, called from a user gesture (both sidePanel.open and sidebarAction.open require one). The same HTML page serves all three surfaces.

UI surfacesAction popup, side panel, context menus, omnibox, DevTools panels and declarativeContent across Chrome, Firefox and Safari.SurfaceChrome / EdgeFirefoxSafariAction popupYesYesYesSide panelsidePanelsidebar_actionNoContext menuscontextMenusmenus (+ onShown)contextMenus (subset)OmniboxYesYes (plain text)NoDevTools panelsYesYesLimiteddeclarativeContentYesNoNo
Popup and context menus are universal; side panels and the omnibox need fallbacks.

Workarounds. Build every panel as an ordinary extension page so it can be shown as a side panel, a sidebar, or a tab. Offer popup search where the omnibox is missing. Replace declarativeContent with tabs.onUpdated and action.enable/disable where it is absent. Details in side panel support across browsers and omnibox support and alternatives across browsers.

Data, identity and platform APIs

Data, identity and platform APIsstorage.session, bookmarks, history, downloads, identity.getAuthToken, launchWebAuthFlow, native messaging, userScripts and readingList across Chrome, Firefox and Safari.APIChrome / EdgeFirefoxSafaristorage.sessionYes115+16.4+bookmarks / historyYesYesNodownloadsYesYesNoidentity.getAuthTokenGoogle accountsNoNolaunchWebAuthFlowYesYesYes (recent)Native messagingHost executableHost executableContaining appuserScriptsYes (dev mode toggle)Yes (MV3)NoreadingList120+NoNo
Safari lacks most browser-data APIs; identity works everywhere through launchWebAuthFlow.

Workarounds. Use launchWebAuthFlow for sign-in everywhere rather than getAuthToken. Treat bookmarks, history and downloads features as unavailable on Safari and hide them. Native messaging on Safari goes to the containing app, as described in native messaging in Firefox and Safari.

Step-by-step: use the reference in a project

1. List the APIs each feature needs

1export const FEATURE_APIS = {
2  readerMode:   ["scripting", "storage"],
3  sidePanel:    ["sidePanel"],
4  blocker:      ["declarativeNetRequest"],
5  backupExport: ["bookmarks", "downloads"],
6};

Execution context: a shared module. Mapping features to APIs turns this reference into a per-feature verdict.

2. Detect at runtime and record the result

1export function detect() {
2  return {
3    sidePanel: typeof chrome.sidePanel?.open === "function" || typeof globalThis.browser?.sidebarAction?.open === "function",
4    bookmarks: typeof chrome.bookmarks?.getTree === "function",
5    downloads: typeof chrome.downloads?.download === "function",
6    offscreen: typeof chrome.offscreen?.createDocument === "function",
7  };
8}

Execution context: any extension context. Store the result in chrome.storage.session at startup so UI pages can hide unavailable features without probing again.

3. Hide, degrade or replace

For each missing capability, decide: hide the feature (backup export on Safari), degrade it (side panel opens as a tab), or replace it (omnibox becomes popup search). Write the decision into the capability matrix so support staff know what each browser offers.

Common mistakes

  • Branching on the user agent. Detect capabilities instead; Edge, Brave and Opera share Chrome’s UA tokens but not always its APIs.
  • Assuming Firefox MV3 equals Chrome MV3. The background model and blocking webRequest differ.
  • Treating Safari as Chrome with a different store. Many browser-data APIs are absent.
  • Trusting old tables. APIs land every few months; re-check at each major release.
  • Shipping features that silently do nothing. Hide or explain unavailable features.

Cross-browser variation

This page is the cross-browser variation; for each area, the matrices above list the behaviour per engine. For the mechanics of shipping one package to all three, see shipping one manifest for Chrome and Firefox and porting a Chrome extension to Safari with Xcode.

Verification

  1. Run detect() in each browser’s background console and compare with the matrices.
  2. For each feature marked missing on an engine, confirm the UI hides or replaces it there.
  3. Re-run detection after each major browser release in CI and diff the results.

FAQ

Why not use the WebExtension polyfill to close gaps?

The polyfill converts callbacks to promises and aliases chrome to browser; it cannot add APIs an engine lacks. See using the WebExtension polyfill in MV3.

Does Edge have every Chrome API?

Almost all, but not all — some Google-service APIs, such as identity.getAuthToken, behave differently. Detect rather than assume.

How often do these gaps change?

Every few months. Firefox and Safari have been closing MV3 gaps steadily; re-check before designing around a gap.

Where should I record our own decisions about each gap?

In your capability matrix, next to the feature it affects: the gap, the chosen response (hide, degrade, replace) and the version where you last checked it. That record is what support and product staff need, and it tells you which decisions to revisit when a browser closes a gap.

Other Core APIs & Cross-Browser Data Management Resources