Setting Minimum Browser Versions

Choose minimum_chrome_version, gecko strict_min_version and Safari minimums for an MV3 extension: find the oldest version your APIs need, what the key does to installs and updates, and feature detection.

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

An error report arrives: “TypeError: Cannot read properties of undefined (reading ‘open’)” from chrome.sidePanel.open, on a Chrome version eighteen months old. Your manifest says nothing about minimum versions, so the store happily delivered your latest release to a browser that lacks half the APIs it calls. Or the opposite: you set minimum_chrome_version to the version on your laptop, and a slice of users on managed machines silently stopped receiving updates. Minimum versions are a contract with the store about who gets each release; this guide shows how to set them deliberately. It belongs to the manifest keys reference.

What the minimum version actually does

minimum_chrome_version is read by the Chrome Web Store and by Chrome. A browser older than the minimum cannot install the extension, and — more importantly for existing users — is not offered updates whose manifest names a newer minimum. A user already on an older version of your extension keeps it; they are frozen at the last release whose minimum they satisfied. Firefox reads browser_specific_settings.gecko.strict_min_version (and strict_max_version) with the same effect on AMO. Safari reads browser_specific_settings.safari.strict_min_version, though most Safari version control happens through the containing app’s deployment target in Xcode. None of these keys makes an API available; they only stop the extension reaching browsers that lack it.

Who receives a release with a raised minimumUsers on browsers at or above the new minimum update normally; users below it stay frozen on the last compatible release; new users below it cannot install.Chrome 109currentBelow minimumfrozen on v3.xMinimum (…receives v…Current versionsreceive v4.0minimum_chrome_version: "116"new users above the minimum install v4.0
Raising the minimum never breaks old users — it strands them on an old release.

Step-by-step: pick and enforce the minimum

1. List the APIs and behaviours you depend on

1grep -rhoE "chrome\.[a-zA-Z]+(\.[a-zA-Z]+)?" src | sort -u > apis.txt
2grep -rhoE "\"(side_panel|world|use_dynamic_url|minimum_chrome_version)\"" src/manifest*.json | sort -u

Execution context: a terminal. For each API, look up the version that introduced it in the Chrome extension API reference, and for behaviours rather than APIs — such as WebSocket traffic extending service worker lifetime (Chrome 116) or userVisibleOnly: false push for extensions (Chrome 121) — note the version where the behaviour began. The highest version that a required feature needs is your minimum.

Chrome versions that introduced common MV3 capabilitiesChrome milestone in which selected MV3 APIs and behaviours became available, from chrome.scripting to silent push for extensions.chrome.scripting88 Chrome ver…scripting world: MAIN95 Chrome ver…chrome.offscreen109 Chrome ve…chrome.sidePanel114 Chrome ve…WebSocket keeps worker alive116 Chrome ve…chrome.userScripts120 Chrome ve…Silent push for extensions121 Chrome ve…
Your minimum is the highest bar among the features you cannot live without.

2. Separate required features from optional ones

1// features.js — feature-detect anything newer than the declared minimum
2export const can = {
3  sidePanel: typeof chrome.sidePanel?.open === "function",
4  userScripts: (() => { try { return !!chrome.userScripts; } catch { return false; } })(),
5  dynamicWAR: chrome.runtime.getManifest().web_accessible_resources?.some((w) => w.use_dynamic_url),
6};

Execution context: any extension context. A feature you can degrade gracefully — a side panel that falls back to a tab — should not raise the minimum; feature-detect it instead. Only features without which the extension is useless belong in the minimum. chrome.userScripts throws on access when the user has not enabled it, hence the try.

3. Declare the minimum for each browser

1{
2  "minimum_chrome_version": "116",
3  "browser_specific_settings": {
4    "gecko":  { "id": "readable@acme.example", "strict_min_version": "121.0" },
5    "safari": { "strict_min_version": "16.4" }
6  }
7}

Execution context: the manifest. Chrome’s value is a version string; "116" is fine. Firefox requires the full dotted form ("121.0") and validates it on upload. Edge reads minimum_chrome_version because it shares the Chromium version line. A Firefox strict_max_version is rarely appropriate — it blocks future Firefox releases until you update the manifest.

4. Check your user base before raising it

1// Aggregate, opt-in telemetry of major versions
2const major = Number(/Chrome\/(\d+)/.exec(navigator.userAgent)?.[1] ?? 0);
3reportCount("chrome_major", { bucket: major >= 120 ? "120+" : String(major) });

Execution context: the service worker. Before raising the minimum, find out how many active users sit below the new value. Managed fleets and some regions lag well behind the current release. If the number is meaningful, either keep the old minimum and feature-detect, or make sure the last release they will receive is a good one to be frozen on. Bucket the data coarsely; version numbers are part of a browser fingerprint.

Should this feature raise the minimum?Decision tree: a feature the extension cannot work without raises the minimum; a feature with a reasonable fallback is feature-detected; a cosmetic feature is simply skipped on older browsers.What happens without the feature?extension uselessRaise minimumafter checking usersOld users frozenon last releasefallback existsFeature-detectdegrade gracefullyEveryone updatesno strandingcosmetic onlySkip silentlyno UI changeNothing to explaininvisible to users
Raise the minimum only for features the product cannot do without.

5. Test at the minimum, not just at the latest

1# Playwright can launch a specific Chromium build; pin one at your minimum
2npx @puppeteer/browsers install chrome@116
3CHROME_PATH=$(npx @puppeteer/browsers list | grep "chrome@116" | awk '{print $NF}') npm run test:e2e

Execution context: CI. A test run on the minimum version is the only reliable proof that the minimum is right. Run the full end-to-end suite there periodically — weekly is enough — and on every change that touches feature detection.

6. Communicate the minimum to users

When a user on an old browser cannot install the extension, the store shows a generic incompatibility message. When an existing user is frozen on an old release, they see nothing at all — the extension simply stops gaining features. State the supported browser versions on your website and in the store description, and consider a one-time notice from the last release before a minimum bump.

1// In the release that precedes a minimum bump
2const major = Number(/Chrome\/(\d+)/.exec(navigator.userAgent)?.[1] ?? 999);
3if (major < 116) {
4  chrome.action.setBadgeText({ text: "!" });
5  chrome.action.setTitle({ title: "Update Chrome to keep receiving Readable updates" });
6}

Execution context: the service worker of the last release that still supports older browsers. Users who will be frozen get a visible, actionable message while they can still act on it; everyone else sees nothing. Remove the notice in the following release, which those users will never receive anyway.

Common mistakes

  • Setting the minimum to your own browser’s version. It strands users for no reason. Derive it from the APIs you use.
  • Not setting it at all. The store then delivers releases to browsers that lack required APIs, and you get crash reports instead of a clean “requires Chrome 116”.
  • Raising it in a patch release. Users below the new minimum are frozen on whatever release came before — possibly one with the bug the patch was meant to fix.
  • Forgetting Firefox’s format. "strict_min_version": "121" is rejected by AMO; it must be "121.0".
  • Relying on the minimum instead of feature detection. Chrome can disable features by policy or field trial, and Chromium forks ship different API sets at the same version number.

Cross-browser variation

  • Chrome / Edge: minimum_chrome_version controls install and updates on the Chrome Web Store and Edge Add-ons.
  • Firefox: gecko.strict_min_version controls AMO compatibility; strict_max_version exists but should usually be omitted. Firefox ESR users lag behind release by up to a year.
  • Safari: safari.strict_min_version exists, but the containing app’s macOS and iOS deployment target in Xcode is the effective minimum for App Store distribution.

Verification

  1. Build the manifest and confirm the minimum values per target: jq '.minimum_chrome_version, .browser_specific_settings' dist/*/manifest.json.
  2. Load the extension unpacked in a browser below the minimum: Chrome refuses with “requires Chrome version 116 or greater”.
  3. Run the end-to-end suite on the minimum version and confirm it passes.
  4. Exercise each feature-detected path with the API forcibly absent (stub it to undefined in a test) and confirm the fallback works.

FAQ

Does the minimum apply to unpacked development builds?

Yes, Chrome refuses to load an unpacked extension whose minimum exceeds the running version.

Can I set different minimums for Chrome and Edge?

Not through minimum_chrome_version, which both read. If you need a different minimum for Edge, generate a separate manifest for the Edge build.

What if a user is frozen on an old release with a bug?

They stay on it until they update the browser. That is why the release before a minimum bump should be a stable, well-tested one.

Other MV3 Architecture & Extension Lifecycle Resources