Signing and Publishing to AMO from CI

Release a Firefox extension from CI with web-ext sign — AMO API keys, listed versus unlisted channels, submitting source code for minified builds, and handling the asynchronous review.

Published September 18, 2026 Updated September 18, 2026 8 min read
Table of Contents

Every Firefox extension must be signed by Mozilla before release Firefox will install it, whether it is listed on addons.mozilla.org or distributed yourself. web-ext sign does the upload, validation and signing in one command, which makes it natural to run in CI — as long as the pipeline handles the two things that surprise people: listed submissions go into a human review queue and do not return a signed file immediately, and a minified or bundled build must be accompanied by its source code. This guide is part of CI and release automation.

Listed and unlisted channels

AMO distribution channelsThe listed and unlisted AMO channels compared on visibility, review, whether web-ext sign returns a signed file immediately, and update delivery.PropertyListedUnlistedVisible on addons.mozilla.orgYesNoReviewAutomated + humanAutomatedSigned file returned by web-extAfter review, laterWithin minutesUpdates delivered byAMOYour update_urlTypical usePublic releasesBetas, internal, self-hosted
Unlisted returns a signed XPI in minutes; listed returns a submission that is reviewed on Mozilla's schedule.

Step-by-step

1. Get AMO API credentials

On addons.mozilla.org, open Tools → Manage API Keys and generate a JWT issuer and secret. Store them as CI secrets, never in the repository — the rules for publishing credentials are in managing store credentials in CI secrets.

2. Pin the add-on id in the Firefox manifest

1{
2  "browser_specific_settings": {
3    "gecko": {
4      "id": "reader@example.com",
5      "strict_min_version": "121.0"
6    }
7  }
8}

Execution context: parsed by Firefox and by AMO at upload. Without a pinned id, AMO assigns one on first upload and every later upload must match it — pin it before the first submission. The per-target manifest is generated as in generating a manifest per browser target.

3. Lint before you sign

1npx web-ext lint --source-dir dist/firefox --warnings-as-errors

Execution context: the CI job, as a gate before signing. The same validator runs on AMO; catching its complaints locally saves a round trip and a rejected version number. --warnings-as-errors is strict but pays off — warnings about unsafe innerHTML or unknown manifest keys are exactly what a reviewer will ask about.

4. Sign an unlisted build for testing channels

1npx web-ext sign \
2  --source-dir dist/firefox \
3  --artifacts-dir artifacts \
4  --channel unlisted \
5  --api-key "$AMO_JWT_ISSUER" \
6  --api-secret "$AMO_JWT_SECRET"
7ls artifacts/*.xpi

Execution context: the CI job. The unlisted channel returns a signed .xpi within minutes, which makes it suitable for beta testers or internal distribution. The version number is consumed permanently even for unlisted uploads, so bump it for every signing run.

5. Submit a listed version with its source

For a public release, submit to the listed channel. If the build is bundled or minified, AMO requires the original source and exact build instructions.

 1git archive --format=zip -o artifacts/source.zip HEAD
 2
 3npx web-ext sign \
 4  --source-dir dist/firefox \
 5  --artifacts-dir artifacts \
 6  --channel listed \
 7  --upload-source-code artifacts/source.zip \
 8  --approval-timeout 0 \
 9  --api-key "$AMO_JWT_ISSUER" \
10  --api-secret "$AMO_JWT_SECRET"

Execution context: the CI job. --approval-timeout 0 tells web-ext to submit and return rather than wait — listed review can take hours or days, and a CI job that waits that long will time out. git archive produces the source exactly as committed, without node_modules or build output, which is what reviewers expect.

6. Include build instructions a reviewer can follow

1<!-- BUILD.md, included in source.zip -->
2Requirements: Node 20.x, npm 10.x, Linux or macOS.
3
4    npm ci
5    npm run build:firefox
6
7The output in dist/firefox/ is byte-identical to the submitted package.

Execution context: a file at the repository root. Reviewers rebuild from these instructions and diff the result against your upload. A build that is not reproducible — timestamps in the bundle, unpinned dependencies — produces a diff and a delayed review; the reproducibility habits are in webpack configuration for MV3 extensions.

A Firefox release from CIThe Firefox build is linted, the source archive is created, web-ext sign submits to the listed channel with the source and returns immediately, and review happens asynchronously on AMO.Build dist/firefoxpinned gecko idweb-ext lintwarnings as errorsgit archivesource.zip + BUILD.mdthen submit and returnweb-ext sign --channel listed--approval-timeout 0AMO reviewhours to daysUsers updated by AMOno action needed
The pipeline ends at submission — approval arrives later, and AMO delivers the update to users itself.

Keeping the version number honest

AMO consumes a version number the moment it is uploaded, on either channel, and never lets it be reused. That interacts badly with a CI pipeline that retries on failure: a job that uploads 2.4.1, fails in a later step, and is re-run will be rejected because 2.4.1 already exists.

The robust arrangement is to separate building a release candidate from uploading it, and to make the upload step idempotent by checking first:

1VERSION=$(jq -r .version dist/firefox/manifest.json)
2if curl -sf "https://addons.mozilla.org/api/v5/addons/addon/reader@example.com/versions/$VERSION/" \
3     -H "Authorization: JWT $(node scripts/amo-jwt.mjs)" >/dev/null; then
4  echo "version $VERSION already on AMO — skipping upload"
5  exit 0
6fi

Execution context: the CI job, before web-ext sign. The JWT is generated from the same issuer and secret; a 200 response means the version exists and the upload would fail anyway. Skipping cleanly lets a re-run complete the remaining steps instead of failing at the start.

For unlisted beta builds, a common pattern is to append a build number as a fourth version component — 2.4.1.37 — so every CI run has a unique version without touching the public version sequence.

A listed Firefox release after the pipeline finishesThe CI job submits within minutes; automated validation completes shortly after; human review follows on Mozilla's schedule; users receive the update over the following day.tag pushed+3 daysBuild…~6 minAutom…minutesHuman review queuehours to daysRollout to usersauto-updateCI job endsapproved
The pipeline's part is the first few minutes — plan communications around the review, not the build.

What AMO reviewers look for in an automated submission

Automation changes nothing about what review checks, but it does change what a reviewer sees: a submission with no human-written notes, often at an odd hour, from a pipeline. A few habits make those submissions easier to approve.

Fill the reviewer notes. web-ext sign accepts release notes and reviewer notes through AMO’s API metadata; a one-paragraph note saying what changed and pointing at BUILD.md saves the reviewer from reconstructing it from a diff. Keep permissions stable between versions where you can — a version that adds a permission gets closer scrutiny, and bundling a permission change into an otherwise routine release delays the whole thing. And keep third-party code identifiable: vendored libraries in their own directory with their original filenames and licences are faster to review than the same code folded into a single bundle, because reviewers can match them against known releases.

None of this is specific to CI, but a pipeline is the natural place to enforce it — a step that fails when the manifest’s permission list differs from the previous release without an explicit override turns “we accidentally added a permission” from a review delay into a build failure.

Cross-browser variation

  • Firefox (AMO): web-ext sign with JWT credentials; listed and unlisted channels; source upload required for minified builds; version numbers are single-use.
  • Chrome Web Store: OAuth-based API with upload and publish endpoints and a staged rollout percentage, described in automating Chrome Web Store uploads with the API.
  • Safari / App Store: App Store Connect submission of the containing app, typically through Fastlane with an App Store Connect API key.
  • All three: submission is automatable; approval is not. Design the pipeline to end at “submitted” and treat approval as an external event.

Verification

  1. Run the unlisted signing path against a test add-on and confirm a signed .xpi appears in artifacts/.
  2. Install the signed file in a release Firefox build via about:addons → Install Add-on From File and confirm it loads without a signature warning.
  3. For a listed submission, confirm the version appears in the AMO developer hub with the source attached.
  4. Re-run the job for an already-uploaded version and confirm it skips rather than fails:
1./scripts/amo-publish.sh
2# version 2.4.1 already on AMO — skipping upload

Execution context: the CI job or your shell. A failure here means the idempotence check is missing and a retry will always break.

FAQ

Do self-distributed Firefox extensions need signing?

Yes. Release and Beta Firefox refuse unsigned extensions. Use the unlisted channel to get a signature without an AMO listing.

Why was my listed submission rejected for source code?

Either the source was missing, or the reviewer could not reproduce the build. Include BUILD.md, pin Node and dependency versions, and remove anything environment-dependent from the output.

Can CI wait for approval and then do something?

Not within a single job — listed review is too slow. Poll the AMO API from a scheduled job instead, and act when the version’s status changes to public.

Should the Chrome and Firefox releases share a version number?

Yes. One version across targets makes error reports, support conversations and changelogs line up. If Firefox needs a re-upload after a rejection, bump both — a Chrome release that skips a number costs nothing.

Other Testing, Debugging & Performance Optimization Resources