Package Exports
- @extension.dev/deploy
Readme
@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.submitfile - 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/deployQuick 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 initNon-interactive use (e.g. CI setup scripts):
npx extension-deploy init \
--init-stores chrome,firefox \
--init-output config/.env.submit \
--init-skip-verify \
--init-forceAfter 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 submitCLI 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//0xxxRun 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//0xxxDrafts 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 testRelease
This repository publishes through the Release GitHub Actions workflow.
Prerequisites:
- Add an
NPM_TOKENrepository secret with publish access for@extension.dev/deploy - Trigger the
Releaseworkflow from GitHub Actions with a plain semver like0.1.0
What the workflow does:
- Validates that the requested version and git tag do not already exist
- Installs dependencies, then runs lint, build, and test
- Updates
package.json - Creates commit
release: v<version>and tagv<version> - Pushes the commit and tag
- 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