JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 32
  • Score
    100M100P100Q72138F
  • License MIT

Ship browser extensions to Chrome Web Store, Firefox Add-ons, and Edge Add-ons from CI or your terminal. One config, three stores, dry-run first.

Package Exports

  • @extension.dev/deploy

Readme

Version workflow

@extension.dev/deploy

Deploy browser extensions to Chrome Web Store, Firefox AMO, and Edge Partner Center from a single command.

  • Upload and publish to one, two, or all three stores in a single run
  • Works as a CLI or as a Node.js library
  • Reads credentials from CLI flags, environment variables, or a .env.submit file
  • Dry-run mode to verify auth without uploading anything
  • Chrome staged rollout and expedited review support
  • Zero runtime dependencies beyond zod

Install

npm install @extension.dev/deploy

Quick start

If you're setting up a new project, run the interactive wizard. It walks you through each store's credential acquisition (including auto-generating a Chrome refresh token from your OAuth client), runs a verification check against each store's API, and writes everything to a .env.submit file:

npx extension-deploy init

Non-interactive use (e.g. CI setup scripts):

npx extension-deploy init \
  --init-stores chrome,firefox \
  --init-output config/.env.submit \
  --init-skip-verify \
  --init-force

After init has written .env.submit, a full submission is a one-liner:

npx extension-deploy --dry-run   # validate creds and ZIPs
npx extension-deploy             # actually submit

CLI usage

If you'd rather skip the wizard and pass credentials directly:

npx extension-deploy \
  --chrome-zip dist/chrome.zip \
  --chrome-extension-id abcdefghijklmnopabcdefghijklmnop \
  --chrome-client-id 123.apps.googleusercontent.com \
  --chrome-client-secret GOCSPX-xxx \
  --chrome-refresh-token 1//0xxx

Run extension-deploy --help for the full list of flags.

Global flags

Flag Description
--dry-run Verify auth and validate ZIPs without uploading or publishing
--help Show the help message

Chrome Web Store

Flag Env var Description
--chrome-zip <path> CHROME_ZIP Path to the extension ZIP file
--chrome-extension-id <id> CHROME_EXTENSION_ID Chrome Web Store extension ID
--chrome-publisher-id <uuid> CHROME_PUBLISHER_ID Publisher UUID from the dev console (required)
--chrome-client-id <id> CHROME_CLIENT_ID OAuth2 client ID
--chrome-client-secret <secret> CHROME_CLIENT_SECRET OAuth2 client secret
--chrome-refresh-token <token> CHROME_REFRESH_TOKEN OAuth2 refresh token
--chrome-deploy-percentage <n> CHROME_DEPLOY_PERCENTAGE Staged rollout percentage (1-100)
--chrome-review-exemption CHROME_REVIEW_EXEMPTION Request expedited review
--chrome-skip-submit-review CHROME_SKIP_SUBMIT_REVIEW Upload only, skip publish step

Firefox AMO

Flag Env var Description
--firefox-zip <path> FIREFOX_ZIP Path to the extension ZIP file
--firefox-sources-zip <path> FIREFOX_SOURCES_ZIP Path to sources ZIP (optional, for review)
--firefox-extension-id <id> FIREFOX_EXTENSION_ID Addon GUID ({uuid}) or email-style ID
--firefox-jwt-issuer <issuer> FIREFOX_JWT_ISSUER AMO JWT issuer (API key)
--firefox-jwt-secret <secret> FIREFOX_JWT_SECRET AMO JWT secret
--firefox-channel <channel> FIREFOX_CHANNEL "listed" or "unlisted"

--firefox-extension-id is optional for an unlisted first submission: with no GUID and --firefox-channel unlisted, a new add-on is created and AMO assigns the GUID. Provide the GUID to push a new version to an existing add-on. Listed submissions always require an existing GUID.

Edge Partner Center

Flag Env var Description
--edge-zip <path> EDGE_ZIP Path to the extension ZIP file
--edge-product-id <id> EDGE_PRODUCT_ID Partner Center product ID
--edge-client-id <id> EDGE_CLIENT_ID Partner Center client ID
--edge-api-key <key> EDGE_API_KEY Partner Center API key (v1.1)
--edge-skip-submit-review EDGE_SKIP_SUBMIT_REVIEW Upload only, skip publish step

Environment variables

All flags can be set via SCREAMING_SNAKE_CASE environment variables (e.g. CHROME_ZIP, FIREFOX_JWT_SECRET). CLI flags take precedence over environment variables.

A .env.submit file in the current working directory is loaded automatically. Existing environment variables are never overwritten.

# .env.submit
CHROME_ZIP=dist/chrome.zip
CHROME_EXTENSION_ID=abcdefghijklmnopabcdefghijklmnop
CHROME_CLIENT_ID=123.apps.googleusercontent.com
CHROME_CLIENT_SECRET=GOCSPX-xxx
CHROME_REFRESH_TOKEN=1//0xxx

Drafts and deletion

This package only uploads and publishes. It intentionally has no delete or discard command: it is meant to run in CI, where the credentials it carries should not be able to destroy a listing.

If you upload a draft (Chrome --chrome-skip-submit-review, Edge --edge-skip-submit-review) you do not need to clean it up:

  • Chrome and Edge drafts are private (never shipped to users) and are overwritten by your next upload. Neither store exposes a draft-discard or item-delete API; deleting an item is a dashboard-only action.
  • Firefox add-ons can be deleted through the AMO Developer Hub. A throwaway unlisted add-on created for testing is best reused (push new versions to the same GUID) rather than recreated each run.

Library usage

import { deploy } from "@extension.dev/deploy";

const result = await deploy({
  dryRun: false,
  chrome: {
    zip: "dist/chrome.zip",
    extensionId: "abcdefghijklmnopabcdefghijklmnop",
    clientId: "123.apps.googleusercontent.com",
    clientSecret: "GOCSPX-xxx",
    refreshToken: "1//0xxx",
    publishTarget: "default",
    deployPercentage: 10, // optional: staged rollout
  },
  firefox: {
    zip: "dist/firefox.zip",
    extensionId: "{12345678-1234-1234-1234-123456789abc}",
    jwtIssuer: "user:12345:678",
    jwtSecret: "your-jwt-secret",
    channel: "listed",
  },
  edge: {
    zip: "dist/edge.zip",
    productId: "your-product-id",
    clientId: "your-client-id",
    apiKey: "your-api-key",
  },
});

if (!result.success) {
  for (const store of result.stores) {
    if (!store.success) {
      console.error(`${store.store} failed: ${store.error}`);
    }
  }
  process.exit(1);
}

deploy(config): Promise<DeployResult>

Deploys to all configured stores in parallel. Only stores with a config object are included; omit a store to skip it.

DeployConfig

Field Type Required Description
dryRun boolean No Verify auth without uploading. Default false
chrome ChromeOptions No Chrome Web Store config
firefox FirefoxOptions No Firefox AMO config
edge EdgeOptions No Edge Partner Center config

DeployResult

Field Type Description
dryRun boolean Whether this was a dry run
success boolean true if all stores succeeded
stores StoreResult[] Per-store results

StoreResult

Field Type Description
store "chrome" | "firefox" | "edge" Store identifier
success boolean Whether submission succeeded
status "dry_run" | "submitted" | "uploaded" | "failed" Result status
error string? Error message on failure
duration number Duration in milliseconds

Validation schemas

Zod schemas are exported for custom validation:

import {
  chromeOptionsSchema,
  firefoxOptionsSchema,
  edgeOptionsSchema,
  deployConfigSchema,
} from "@extension.dev/deploy";

CI example (GitHub Actions)

- name: Deploy extension
  run: npx extension-deploy
  env:
    CHROME_ZIP: dist/chrome.zip
    CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
    CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
    CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
    CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
    FIREFOX_ZIP: dist/firefox.zip
    FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }}
    FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }}
    FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }}
    EDGE_ZIP: dist/edge.zip
    EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }}
    EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }}
    EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }}

Exit codes

Code Meaning
0 All configured stores succeeded
1 One or more stores failed, or a validation/config error occurred

Local development

pnpm install
pnpm lint
pnpm build
pnpm test

Release

This repository publishes through the Release GitHub Actions workflow.

Prerequisites:

  • Add an NPM_TOKEN repository secret with publish access for @extension.dev/deploy
  • Trigger the Release workflow from GitHub Actions with a plain semver like 0.1.0

What the workflow does:

  1. Validates that the requested version and git tag do not already exist
  2. Installs dependencies, then runs lint, build, and test
  3. Updates package.json
  4. Creates commit release: v<version> and tag v<version>
  5. Pushes the commit and tag
  6. Publishes the package to npm

The extension.dev open source stack

Package Use it to
@extension.dev/mcp Give AI agents tools to build, run, and debug extensions
@extension.dev/skill Teach agents the cross-browser rules and silent-failure gotchas
@extension.dev/artifact-integrity Gate releases on artifact verification
@extension.dev/compiler Build extensions in the browser with esbuild-wasm
@extension.dev/core Authenticate and publish to the extension.dev platform

All of it rides on Extension.js, the open-source cross-browser extension framework.

License

MIT