browser_specific_settings for Firefox and Safari
Configure browser_specific_settings in an MV3 manifest: Firefox gecko id and version bounds, data_collection_permissions, gecko_android, Safari minimums, and keeping one manifest valid for every store.
Table of Contents
The Chrome build uploads cleanly. The same package uploaded to addons.mozilla.org fails validation with “The extension ID is required in Manifest Version 3 and above”, and once that is fixed, a later upload is rejected because the id changed. Firefox and Safari read a manifest key Chrome ignores — browser_specific_settings — and Firefox in particular relies on it for the extension’s identity, its supported version range, and newer data-collection declarations. This guide covers what belongs there and how to keep one manifest source valid everywhere. It belongs to the manifest keys reference.
What the key carries and who reads it
browser_specific_settings is an object with one sub-object per browser family. gecko is read by Firefox on desktop and holds the add-on id, minimum and maximum supported versions, an optional self-hosting update_url, and — in recent Firefox — data_collection_permissions. gecko_android overrides version bounds for Firefox for Android. safari holds Safari’s minimum and maximum versions. Chrome and Edge ignore the whole key with at most a warning on chrome://extensions, so a single manifest can carry all of it. The id is the field that matters most: Firefox uses it to sign the add-on, to match updates to the existing listing, to route native messaging, and as the stable identifier that browser.runtime.id returns.
Step-by-step: a complete, portable block
1. Choose a permanent Firefox id
1"browser_specific_settings": {
2 "gecko": { "id": "readable@acme.example" }
3}
Execution context: the manifest, read by Firefox at install and by AMO at upload. The id can be an email-like string (name@domain) or a GUID in braces ({9c1f…}); the email form is easier to read in logs and native host manifests. Choose it before the first upload and never change it — a new id is a new add-on, and existing users will not receive updates. The email form does not need to be a real address, but using a domain you own avoids collisions.
2. Set version bounds
1"gecko": {
2 "id": "readable@acme.example",
3 "strict_min_version": "121.0"
4},
5"gecko_android": {
6 "strict_min_version": "121.0"
7}
Execution context: the manifest. strict_min_version must use dotted form. Omit strict_max_version unless you know a future release breaks you; with it set, AMO marks the add-on incompatible with newer Firefox until you upload again. gecko_android overrides the bounds for Firefox for Android — include it only if you support Android, where several APIs (native messaging, some windows features) are unavailable. Picking the minimum is covered in setting minimum browser versions.
3. Declare data collection for Firefox
Recent Firefox versions show what data an add-on collects in the install prompt, driven by a manifest declaration rather than only by the listing.
1"gecko": {
2 "id": "readable@acme.example",
3 "strict_min_version": "121.0",
4 "data_collection_permissions": {
5 "required": ["none"],
6 "optional": ["technicalAndInteraction"]
7 }
8}
Execution context: the manifest. "none" states that the add-on collects nothing by default; optional categories can be requested at runtime the same way optional permissions are. Check Mozilla’s current documentation for the category names and the Firefox versions that enforce the declaration — AMO began requiring it for new submissions and the list of categories has evolved. Whatever you declare must match what the code does and what the privacy policy says.
4. Add Safari bounds if you distribute there
1"safari": { "strict_min_version": "16.4" }
Execution context: the manifest packaged inside the Safari app. In practice the containing app’s deployment target in Xcode is the stronger constraint — an app built for macOS 13 cannot run on 12 regardless of the manifest — but declaring the Safari version documents which Safari features (for example storage.session, available from 16.4) the extension relies on.
5. Validate before uploading
1npx web-ext lint --source-dir dist/firefox
Execution context: a terminal or CI step on the Firefox build. web-ext lint runs the same validator AMO uses and catches a missing id, a malformed version, unknown permissions and unsupported keys before an upload round-trip. It also flags Chrome-only keys as warnings, which is your cue to strip them from the Firefox build or accept them as harmless.
6. Keep the legacy name out of new manifests
Older Firefox manifests used "applications" instead of "browser_specific_settings". MV3 accepts only the new name.
1- "applications": { "gecko": { "id": "readable@acme.example" } }
2+ "browser_specific_settings": { "gecko": { "id": "readable@acme.example" } }
Execution context: the manifest. AMO rejects applications in MV3 packages. If you are migrating from MV2, this rename belongs in the same change as manifest_version: 3.
7. Self-hosted Firefox builds
Add-ons distributed outside AMO — enterprise builds, beta channels — can point Firefox at your own update manifest with gecko.update_url. The package must still be signed by AMO through the unlisted channel; the update URL only tells Firefox where to look for new versions.
1"gecko": {
2 "id": "readable-beta@acme.example",
3 "update_url": "https://updates.acme.example/firefox/updates.json"
4}
Execution context: the manifest of a self-distributed Firefox build. Use a different gecko.id for the beta channel so it can be installed alongside the listed release. The updates.json file lists versions and signed .xpi URLs per add-on id; Firefox polls it roughly daily. AMO-listed builds must not include update_url — AMO handles updates for them and rejects packages that try to override it.
Common mistakes
- Changing the gecko id. The id is the add-on’s identity on AMO; changing it creates a new add-on and strands existing users on the old one.
- Using a short version string.
"121"fails AMO validation; use"121.0". - Setting
strict_max_version“to be safe”. It blocks your add-on on every future Firefox until you re-upload. - Assuming Chrome’s
keygives Firefox a stable id. It does not; onlygecko.iddoes. - Declaring data collection that the code contradicts. Reviewers compare the declaration, the privacy policy and the code. A mismatch is grounds for rejection.
Cross-browser variation
- Chrome / Edge: ignore
browser_specific_settings. Edge Add-ons does not use it either. - Firefox: requires
gecko.idfor MV3; reads version bounds and, in recent versions, data collection declarations. Firefox for Android readsgecko_androidoverrides. - Safari: reads the
safaribounds; identity comes from the containing app’s bundle identifier.
Verification
- Run
npx web-ext linton the Firefox build: no errors. - Load the build in Firefox via
about:debugging→ “This Firefox” → “Load Temporary Add-on” and confirm the displayed id matchesgecko.id. - Load the same
manifest.jsonin Chrome and confirm only a warning (or nothing) is shown for the Firefox block. - Run
browser.runtime.idin the Firefox background console and confirm it returns the gecko id.
FAQ
Do I need gecko.id for a temporary add-on during development?
Not strictly — Firefox assigns a temporary id — but without a fixed id, storage and native messaging change every load. Set it from the start.
Can I use the same id format for Chrome?
No. Chrome ids are derived from a public key and are always 32 letters from a to p. The two identities are independent.
Where does Safari get its extension identifier?
From the bundle identifier of the extension target in the Xcode project, typically com.acme.Readable.Extension.
Does Firefox need anything else Chrome does not?
For MV3, the main differences live outside this key: Firefox prefers background.scripts over background.service_worker, uses sidebar_action instead of side_panel, and supports theme_icons for the action. Generate those per target rather than carrying them all in one manifest, so each store sees a clean file without warnings it has to wave through.
Should the Firefox id match anything in Chrome?
No. Keep the identities independent and record both in one place — a small ids.json in the repository works — so native host manifests, server allow-lists and documentation can be generated from a single source.
Related
- Pinning a stable extension id with the key field — Chrome’s equivalent of a fixed id.
- Publishing to Firefox Add-ons and Safari — the upload process these settings feed.
- Shipping one manifest for Chrome and Firefox — portability beyond this key.
- Manifest keys reference — the parent topic.