JSPM

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

Deploy browser extensions to Chrome Web Store, Firefox AMO, and Edge Partner Center.

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

CLI usage

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-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-publish-target <target> CHROME_PUBLISH_TARGET "default" or "trustedTesters"
--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"

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

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

License

MIT