How to Upload Browser Extensions Through an API
Automate Chrome, Edge, and Firefox extension releases with store APIs, credentials, polling, validation, publishing, and CI/CD guidance.
Direct answer: treat Chrome Web Store, Microsoft Edge Add-ons, and Firefox Add-ons as separate release adapters. Build and validate the extension package, authenticate with that store’s credential model, upload it to the existing item or product identifier, poll asynchronous validation, and publish only after the store reports a releasable state. Keep first-time listing creation, privacy declarations, descriptions, screenshots, and other dashboard-only work in a controlled dashboard workflow.
Release architecture
A reliable release job has the same stages for every store:
- Build a reproducible artifact and record its hash.
- Validate the manifest, permissions, version, and package contents.
- Load the store identifier and credentials from a secret manager.
- Upload the package and capture the returned operation or upload UUID.
- Poll with bounded retries until validation succeeds or fails.
- Publish only from a successful validation state.
- Log the package hash, manifest version, request identifiers, response, and final review state.
An HTTP 200 from an upload endpoint means that the store accepted the upload request. It does not mean that review or publication is complete.
Prepare a reproducible extension package
Chrome and Edge ZIP
#!/usr/bin/env bash
set -euo pipefail
rm -rf dist
mkdir -p dist
cp -R src/. dist/
# Exclude development files from the archive.
(cd dist && zip -qr ../extension.zip . \
-x '*.map' 'node_modules/*' '.DS_Store' '*.log')
sha256sum extension.zip
unzip -l extension.zip
Use the same manifest version for the package and release metadata. Do not include source-control directories, local environment files, test fixtures, or build caches.
Firefox XPI
npx web-ext build --source-dir src --artifacts-dir artifacts
Mozilla documents web-ext sign version 8+ for listed and self-distributed extensions. A first listed Manifest V3 submission needs a stable browser_specific_settings.gecko.id in manifest.json; updates must keep that extension ID.
Chrome Web Store API
Google’s Chrome Web Store API supports creation, updates, and publishing of store items. Before a first publication, complete the Store listing and Privacy tabs in the Developer Dashboard, enable the API in a Google Cloud project, configure OAuth, and use a Google account with two-step verification. The required OAuth scope is https://www.googleapis.com/auth/chromewebstore. See the Chrome Web Store API documentation.
Upload an update
#!/usr/bin/env bash
set -euo pipefail
: "${PUBLISHER_ID:?Set PUBLISHER_ID}"
: "${EXTENSION_ID:?Set EXTENSION_ID}"
: "${OAUTH_ACCESS_TOKEN:?Set OAUTH_ACCESS_TOKEN}"
curl -sS -X POST \
"https://chromewebstore.googleapis.com/upload/v2/publishers/${PUBLISHER_ID}/items/${EXTENSION_ID}:upload" \
-H "Authorization: Bearer ${OAUTH_ACCESS_TOKEN}" \
-H "Content-Type: application/zip" \
--data-binary @extension.zip
The response includes uploadState and crxVersion. If uploadState is UPLOAD_IN_PROGRESS, poll the item with the API’s fetchStatus operation before publishing.
Publish after validation
curl -sS -X POST \
"https://chromewebstore.googleapis.com/upload/v2/publishers/${PUBLISHER_ID}/items/${EXTENSION_ID}:publish" \
-H "Authorization: Bearer ${OAUTH_ACCESS_TOKEN}" \
-H "Content-Type: application/json"
Chrome also exposes cancelSubmission and setPublishedDeployPercentage. Percentage rollout is documented for items with more than 10,000 seven-day active users, so treat it as a conditional control rather than a standard release step.
Microsoft Edge Add-ons API
Microsoft’s Update REST API updates an existing Edge Add-ons product and supports package upload, upload-operation status, publishing, and publishing-status checks. Microsoft says these endpoints can be integrated directly into a CI/CD pipeline. The public API is update-only: create the product and change metadata such as the description in Partner Center. Use v1.1; Microsoft notes that v1 support ended on 2024-12-31. See Microsoft’s Add-ons API documentation.
Upload a ZIP
#!/usr/bin/env bash
set -euo pipefail
: "${PRODUCT_ID:?Set PRODUCT_ID}"
: "${EDGE_API_KEY:?Set EDGE_API_KEY}"
: "${EDGE_CLIENT_ID:?Set EDGE_CLIENT_ID}"
curl -i -sS -X POST \
"https://api.addons.microsoftedge.microsoft.com/v1.1/products/${PRODUCT_ID}/submissions/draft/package" \
-H "Authorization: ApiKey ${EDGE_API_KEY}" \
-H "X-ClientID: ${EDGE_CLIENT_ID}" \
-H "Content-Type: application/zip" \
--data-binary @extension.zip
The upload is asynchronous. Capture the operation location returned by the response headers and poll that location until the package operation succeeds or fails.
Publish the draft
curl -sS -X POST \
"https://api.addons.microsoftedge.microsoft.com/v1.1/products/${PRODUCT_ID}/submissions" \
-H "Authorization: ApiKey ${EDGE_API_KEY}" \
-H "X-ClientID: ${EDGE_CLIENT_ID}" \
-H "Content-Type: application/json" \
--data '{"notes":"Automated release from CI"}'
After publishing, poll the publishing-status operation. Keep certification notes with the release in version control.
Firefox Add-ons (AMO) API
Firefox’s v5 submission flow validates an XPI first, then attaches the validated upload to a new add-on or a new version. Use AMO JWT credentials and send channel=listed for a public listing or channel=unlisted for self-distribution. Mozilla recommends polling every 5–10 seconds and timing out after 10 minutes. The Extension Workshop documentation covers web-ext signing and submission concepts.
Upload for validation
#!/usr/bin/env bash
set -euo pipefail
: "${AMO_JWT:?Set AMO_JWT}"
curl -sS -X POST \
"https://addons.mozilla.org/api/v5/addons/upload/" \
-H "Authorization: JWT ${AMO_JWT}" \
-F "upload=@artifacts/extension.xpi" \
-F "channel=listed"
Save the returned upload UUID. Poll that UUID until validation succeeds. Do not attach it to a listing while validation is pending or failed.
Polling pattern
#!/usr/bin/env bash
set -euo pipefail
: "${UPLOAD_UUID:?Set UPLOAD_UUID}"
: "${AMO_JWT:?Set AMO_JWT}"
for attempt in $(seq 1 120); do
response=$(curl -fsS \
-H "Authorization: JWT ${AMO_JWT}" \
"https://addons.mozilla.org/api/v5/addons/upload/${UPLOAD_UUID}/")
echo "$response"
if echo "$response" | grep -q '"valid":true'; then
exit 0
fi
if echo "$response" | grep -q '"valid":false'; then
exit 1
fi
sleep 5
done
echo "Validation timed out after 10 minutes" >&2
exit 1
Once the upload is valid, use the UUID in the AMO request that creates the add-on or adds a new version. The exact metadata payload depends on whether this is a first submission or an update.
Credential and capability comparison
| Store | Credential | Artifact | First product creation | Async status | Metadata coverage |
|---|---|---|---|---|---|
| Chrome Web Store | OAuth bearer token with Chrome Web Store scope | ZIP | Supported by API, with required dashboard setup | Upload state and fetch-status polling | Listing and Privacy tabs require dashboard completion |
| Edge Add-ons | API key plus client ID | ZIP | No; create in Partner Center | Operation location and publishing-status polling | Product metadata remains in Partner Center |
| Firefox AMO | AMO JWT | XPI | Upload then create or attach | Upload UUID polling | Metadata is supplied when creating or updating the add-on |
CI/CD implementation checklist
- Pin build dependencies and generate the package in a clean workspace.
- Check that the manifest version is greater than the currently published version.
- Persist Chrome publisher and item IDs, the Edge product ID, and the Firefox add-on ID.
- Store OAuth refresh/access tokens, Edge API key and client ID, and AMO JWT issuer/secret in a secret manager.
- Upload once per release and persist the operation or upload UUID.
- Poll with bounded retries and exponential backoff where the store permits it; use Mozilla’s 5–10 second interval and 10-minute limit for AMO validation.
- Stop immediately on validation failure. Publish only from a successful state.
- Keep certification notes, privacy declarations, screenshots, descriptions, and review metadata under controlled change.
- Log request IDs, package hashes, manifest version, store response, and final review state for rollback and auditability.
Or skip the browser setup
If your workflow needs screenshots of release notes, listing pages, or QA environments, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. The API accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses identify the result with X-Page-Verdict and X-Billed headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
See the ScreenshotNeo API documentation for all options.
# cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
# Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
# Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
There are 1,000 free screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
Troubleshooting
Upload succeeds but publication does not start
Cause: validation is asynchronous or the item is still processing. Fix: persist the operation identifier, poll the documented status endpoint, and call publish only after a successful state.
Chrome returns an OAuth error
Cause: the token lacks the https://www.googleapis.com/auth/chromewebstore scope, is expired, or belongs to the wrong Google account. Fix: refresh the token with the required scope and verify the publisher and item IDs.
Edge rejects authentication
Cause: missing or malformed Authorization: ApiKey ... or X-ClientID headers. Fix: send both headers exactly and target the v1.1 endpoints.
Edge cannot create a new listing
Cause: the Update REST API is for existing products. Fix: create the product and complete metadata in Partner Center, then use the API for package updates.
Firefox validation fails
Cause: invalid manifest data, missing stable Gecko ID for a Manifest V3 listed submission, disallowed permissions, or an invalid package. Fix: inspect the validation response, correct the package, and upload a new XPI. Keep the same extension ID for updates.
Polling runs forever
Cause: no deadline or no terminal-state handling. Fix: use a maximum attempt count, record the last response, and fail the job after the store-specific timeout.
A release cannot be reproduced
Cause: mutable dependencies or development files included in the archive. Fix: build in a clean environment, pin dependencies, print the archive listing, and store the SHA-256 hash with the release.
Performance, reliability, and cost
- Performance: package once and reuse the exact artifact for retries. Polling frequency affects API load and release latency; follow each store’s guidance.
- Reliability: make uploads idempotent at the pipeline level by recording the package hash and operation ID. Never publish a different artifact under the same release record.
- Retries: retry transient network failures, but do not retry validation failures without changing the package. Preserve the response body for diagnosis.
- Review time: the official material does not publish comparable cross-store success, review-time, or failure-rate figures. A successful API operation still leaves store review gates.
- Cost: store API pricing is not specified in the supplied documentation. Budget for CI runners, artifact storage, and any store-specific account requirements separately.
FAQ
Can one API publish to all three stores?
No. Build one release coordinator with separate Chrome, Edge, and Firefox adapters because their credentials, artifact formats, identifiers, and status models differ.
Can I publish a brand-new Edge extension entirely by API?
No. The public Edge Update REST API updates existing products. Product creation and metadata changes remain in Partner Center.
Do I need an extension ID for Firefox?
Yes. A first listed Manifest V3 submission needs a stable Gecko add-on ID, and updates must retain that ID.
Is an upload response proof that users can install the release?
No. Upload validation, certification, and review can continue after the upload request succeeds.
What should a rollback contain?
Keep the previous package, manifest version, hash, store identifier, request logs, and final review state so the prior release can be rebuilt or resubmitted.


