Publishing to Edge Add-ons from CI
Automate Microsoft Edge Add-ons releases with the Partner Center publish API: API credentials, uploading a package, polling operation status, submitting with release notes, handling errors, and adding Edge to a multi-store pipeline.
Table of Contents
The release pipeline uploads to the Chrome Web Store and AMO automatically, and then someone has to log in to Microsoft Partner Center, upload the same ZIP, paste the release notes and click Publish — every release, usually a day late. Edge Add-ons has a REST API for exactly this: upload a package to an existing product, poll until it is processed, then submit it for certification. The API is asynchronous and status-driven in a way the Chrome Web Store API is not, so a script that fires and forgets reports success for releases that actually failed. This guide builds a robust Edge publishing step. It belongs to CI and release automation.
How the Edge Add-ons API works
The API updates an existing product — the first submission must be made manually in Partner Center, which assigns the product ID. Authentication uses API credentials generated in Partner Center (the current v1.1 API uses a Client ID and an API key sent as headers). Publishing is two asynchronous operations: upload a package to the product’s draft (POST …/products/{productId}/submissions/draft/package), which returns an operation ID in the Location header that you poll until it reports Succeeded or Failed; then publish the draft (POST …/products/{productId}/submissions) with optional notes for certification, which also returns an operation to poll. Certification then happens on Microsoft’s side, typically within days.
Step-by-step: an Edge publish script
1. Create API credentials and store them as secrets
In Partner Center, open the Edge program’s Publish API page and create API credentials. Store the Client ID, API key and the product ID as CI secrets (for example EDGE_CLIENT_ID, EDGE_API_KEY, EDGE_PRODUCT_ID). Keys expire; record the expiry date and rotate before it. See managing store credentials in CI secrets.
2. Upload the package
1// scripts/publish-edge.mjs
2import { readFile } from "node:fs/promises";
3
4const BASE = "https://api.addons.microsoftedge.microsoft.com/v1/products";
5const { EDGE_CLIENT_ID, EDGE_API_KEY, EDGE_PRODUCT_ID } = process.env;
6const headers = { "Authorization": `ApiKey ${EDGE_API_KEY}`, "X-ClientID": EDGE_CLIENT_ID };
7
8async function upload(zipPath) {
9 const res = await fetch(`${BASE}/${EDGE_PRODUCT_ID}/submissions/draft/package`, {
10 method: "POST",
11 headers: { ...headers, "Content-Type": "application/zip" },
12 body: await readFile(zipPath),
13 });
14 if (res.status !== 202) throw new Error(`Upload failed: ${res.status} ${await res.text()}`);
15 return res.headers.get("Location"); // operation id
16}
Execution context: Node in CI. The request body is the raw ZIP built for Edge — usually the same package as Chrome, since Edge is Chromium-based. A 202 Accepted means processing has started, not finished. Check the current API documentation for the exact header names and base URL for your API version; Microsoft has revised authentication between versions.
3. Poll the operation with backoff
1async function waitFor(url, label, { timeoutMs = 10 * 60_000 } = {}) {
2 const start = Date.now();
3 let delay = 5000;
4 while (Date.now() - start < timeoutMs) {
5 const res = await fetch(url, { headers });
6 if (res.status === 429 || res.status >= 500) { await sleep(delay *= 2); continue; }
7 const body = await res.json();
8 if (body.status === "Succeeded") return body;
9 if (body.status === "Failed") {
10 throw new Error(`${label} failed: ${body.message ?? ""}\n${JSON.stringify(body.errors ?? [], null, 2)}`);
11 }
12 await sleep(delay = Math.min(delay * 1.5, 30_000));
13 }
14 throw new Error(`${label} timed out after ${timeoutMs / 1000}s`);
15}
16const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
Execution context: Node in CI. Package validation can take from seconds to minutes. Exponential backoff with a cap keeps the polling polite, and a timeout prevents a stuck job from running for hours. Printing the errors array makes failures actionable — common ones are a version not higher than the published one, or manifest validation errors.
4. Submit for certification
1async function submit(notes) {
2 const res = await fetch(`${BASE}/${EDGE_PRODUCT_ID}/submissions`, {
3 method: "POST",
4 headers: { ...headers, "Content-Type": "application/json" },
5 body: JSON.stringify({ notes }),
6 });
7 if (res.status !== 202) throw new Error(`Submit failed: ${res.status} ${await res.text()}`);
8 return res.headers.get("Location");
9}
10
11const uploadOp = await upload(process.argv[2]);
12await waitFor(`${BASE}/${EDGE_PRODUCT_ID}/submissions/draft/package/operations/${uploadOp}`, "Upload");
13const submitOp = await submit(`Release ${process.env.VERSION}. See changelog: ${process.env.CHANGELOG_URL}`);
14await waitFor(`${BASE}/${EDGE_PRODUCT_ID}/submissions/operations/${submitOp}`, "Submit");
15console.log("Edge submission accepted for certification");
Execution context: Node in CI. The notes field is for certification testers — explain anything they need to test (test accounts, how to trigger a feature) and link the changelog. A submission fails if another submission is already in certification; the error says so, and the job should fail clearly rather than retrying.
5. Add it to the release workflow
1# .github/workflows/release.yml (excerpt)
2 publish-edge:
3 needs: build
4 runs-on: ubuntu-latest
5 environment: edge-store
6 steps:
7 - uses: actions/checkout@v4
8 - uses: actions/setup-node@v4
9 with: { node-version-file: .nvmrc }
10 - uses: actions/download-artifact@v4
11 with: { name: packages, path: release }
12 - run: node scripts/publish-edge.mjs release/extension-chrome-${{ github.ref_name }}.zip
13 env:
14 EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }}
15 EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }}
16 EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }}
17 VERSION: ${{ github.ref_name }}
18 CHANGELOG_URL: https://github.com/${{ github.repository }}/releases/tag/${{ github.ref_name }}
Execution context: GitHub Actions. A separate job per store means an Edge failure doesn’t block or mask the Chrome publish, and can be re-run alone. A protected environment can require manual approval and restricts who can use the secrets. See building a GitHub Actions pipeline for extensions.
6. Keep Edge’s listing in sync
The API updates the package and submission, not the listing text or images. When the store description or screenshots change, update them in Partner Center manually (or note it in the release checklist). See publishing to Microsoft Edge Add-ons.
7. Handle the version rule
Edge requires each upload’s version to be higher than the last published one. If an upload fails for a version you already published elsewhere, you need a new version — keep versions in lockstep across stores to avoid confusion. See versioning and changelogs for extension releases.
Common mistakes
- Treating 202 as success. Poll the operation.
- No timeout on polling. Jobs hang for hours.
- Retrying a submission already in certification. Fails repeatedly; report and stop.
- Expired API keys. Releases fail on the day you need them.
- One job for all stores. One failure hides the others.
Cross-browser variation
- Chrome / Edge: Edge uses its own Partner Center API; the package is usually identical to Chrome’s.
- Firefox: AMO uses
web-ext signwith JWT credentials — see signing and publishing to AMO from CI. - Safari: App Store Connect via
xcrun altool/notarytooland Transporter — see automating Safari builds with xcodebuild.
Verification
- Run the script against a test product (or with a deliberately old version) and confirm a
Failedstatus fails the job with readable errors. - Run a real release and confirm the submission appears as “In certification” in Partner Center.
- Remove a secret and confirm the job fails before uploading.
- Confirm the Edge job can be re-run alone.
FAQ
Can the API create a new product?
No. Create the product and first submission in Partner Center; the API handles updates.
How long does certification take?
Typically up to several business days; the API only confirms submission.
Can I publish to a subset of markets or as a draft?
Market and visibility settings are managed in Partner Center; the API submits the draft with the existing settings.
Can I check certification status from CI later?
The publish operation reports acceptance, not certification results. Check Partner Center or its email notifications for the outcome, and keep the operation IDs in the job log so support requests can reference them.
Related
- Automating Chrome Web Store uploads with the API — the Chrome step.
- Managing store credentials in CI secrets — keys and rotation.
- Building reproducible release ZIPs — the package.
- CI and release automation — the parent topic.