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/deployCLI 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//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-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//0xxxLibrary 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
License
MIT