crx-upload v0.12.18
WebExt Deploy
The ultimate automation tool for deploying to multiple extension stores simultaneously!
Made by leizhenpeng
Supported stores:
Core packages/APIs used
- Playwright - for updating extensions on Opera Add-ons store.
- Chrome Web Store Publish API
- Microsoft Edge Publish API
- Firefox Add-ons Store Submission API
Installing
npm i -D crx-upload
# or
pnpm i -D crx-upload
# or
yarn add -D crx-uploador install globally
npm i -g crx-upload
# or
pnpm i -g crx-upload
# or
yarn global add crx-uploadDeployment to Chrome Web Store: follow this guide. Deployment to Edge Add-ons Store: follow this guide.
Usage
1. Obtain the relevant cookie(s) of the publisher's account:
Disclaimer: I do NOT take any responsibility for leaked cookies or credentials.
- Opera:
sessionid,csrftoken
If you have a hard time obtaining the cookie(s), you can run:
crx-upload --get-cookies=operaNote that for the Chrome Web Store, you'll use the Chrome Web Store Publish API. As for the Edge Add-ons Store, you'll use the Microsoft Edge Publish API.
2. Decide how to access the data & credentials
.env files method
Use the .env snippet(s) relevant to your extension.
Include each one in your root directory.
Make sure to have *.env in your .gitignore
Note that if you used the aforementioned --get-cookies, it automatically added the .env listing(s) to it.
To use the .env files, in the CLI:
crx-upload --envAdditional arguments for the .env mode:
--verboseboolean? If specified, the steps of every store will be logged to the console.--publish-only("chrome" | "firefox" | "edge" | "opera")[]? If specified, for each specified store that has an.envfile, it will be deployed. E.g. if you havechrome.env,firefox.env,opera.env, and you run:crx-upload --env --publish-only=chrome firefoxIt will only deploy to Chrome Web Store and Firefox Add-ons Store.
--zipstring? If specified, it will be used for every.envthat theZIPis not specified.--firefox-changelogstring? If specified andfirefox.envexists, it will be used to provide changelog for the Firefox users. New lines (\n) are supported.--firefox-dev-changelogstring? If specified andfirefox.envexists, it will be used to provide changelog for the Firefox Add-ons reviewers. New lines (\n) are supported.--edge-dev-changelogstring? If specified andedge.envexists, it will be used to provide changelog for the Edge Add-ons reviewers. New lines (\n) are supported.--opera-changelogstring? If specified andopera.envexists, it will be used to provide changelog for the Opera users. New lines (\n) are supported.
Notes:
Chrome Web Store:
REFRESH_TOKEN,CLIENT_ID,CLIENT_SECRET- follow this guide.EXT_ID- Get it fromhttps://chrome.google.com/webstore/detail/EXT_ID, e.g.https://chrome.google.com/webstore/detail/fcphghnknhkimeagdglkljinmpbagone
Firefox Add-ons store:
EXT_ID- Get it fromhttps://addons.mozilla.org/addon/EXT_IDZIP- The relative path to the ZIP. You can use{version}, which will be replaced by theversionentry from yourpackage.jsonZIP_SOURCE- Optional. The relative path to the ZIP that contains the source code of your extension, if applicable.JWT_ISSUER,JWT_SECRET- obtain from the Developer Hub.
Edge Add-ons store:
CLIENT_ID,CLIENT_SECRET,ACCESS_TOKEN_URL,ACCESS_TOKEN- follow this guidePRODUCT_ID- Get it fromhttps://partner.microsoft.com/en-us/dashboard/microsoftedge/PRODUCT_IDZIP- You can use{version}
Opera Add-ons store:
PACKAGE_ID- Get it fromhttps://addons.opera.com/developer/package/PACKAGE_IDZIP- You can use{version}- Source code inspection:
The Opera Add-ons reviewers require inspecting your extension's source code.
This can be done by doing one of the following:
- Uploading the ZIP that contains the source code to a public folder on a storage service (e.g. Google Drive).
- Making the extension's code open source on a platform like GitHub, with clear instructions on the
README.md, and then linking to its repository.
- The keys are case-insensitive, as they will be camel-cased anyway.
Possible .env files
chrome.env
REFRESH_TOKEN="RefreshToken"
CLIENT_ID="ClientID"
CLIENT_SECRET="ClientSecret"
ZIP="dist/some-zip-v{version}.zip"
EXT_ID="ExtensionID"firefox.env
JWT_ISSUER="JwtIssuer"
JWT_SECRET="JwtSecret"
ZIP="dist/some-zip-v{version}.zip"
ZIP_SOURCE="dist/some-zip-source-v{version}.zip"
EXT_ID="ExtensionID"edge.env
CLIENT_ID="ClientID"
CLIENT_SECRET="ClientSecret"
ACCESS_TOKEN_URL="AccessTokenURL"
ACCESS_TOKEN="AccessToken"
ZIP="dist/some-zip-v{version}.zip"
PRODUCT_ID="ProductID"opera.env
SESSIONID="sessionid_value"
CSRFTOKEN="csrftoken_value"
ZIP="dist/some-zip-v{version}.zip"
PACKAGE_ID=123456CLI arguments method
Use it only if your extension's code will not be published.
crx-upload --chrome-zip="some-zip-v{version}.zip" --chrome-ext-id="ExtensionID" --firefox-zip="some-zip-v{version}.zip" --firefox-ext-id="ExtensionID"CLI API
Stores:
Options:
--verboseboolean? If specified, the steps of every store will be logged to the console.--zipstring? If specified, it will be used for every store that thezipis not specified. For example, incrx-upload --zip="zip-v{version}.zip" --chrome-refresh-token="refreshToken" --firefox-sessionid="sessionid_value" --edge-zip="some-zip-v{version}.zip"the
zip-v{version}.zipwill be used for the Chrome Web Store version and the Firefox Add-ons version.
Chrome Web Store CLI
--chrome-ext-idstring Get it fromhttps://chrome.google.com/webstore/detail/EXT_ID, e.g.https://chrome.google.com/webstore/detail/fcphghnknhkimeagdglkljinmpbagone--chrome-refresh-tokenstring The refreshToken you have registered.--chrome-client-idstring The client ID you have registered.--chrome-client-secretstring The client secret you have registered.--chrome-zipstring The relative path to the ZIP from the root. You can use{version}in the ZIP filename, which will be replaced by the version inpackage.json
To get your --chrome-refresh-token, --chrome-client-id and --chrome-client-secret, follow this guide.
Example:
crx-upload --chrome-ext-id="ExtensionID" --chrome-refresh-token="RefreshToken" --chrome-client-id="ClientID" --chrome-client-secret="ClientSecret" --chrome-zip="some-zip-v{version}.zip"Firefox Add-ons CLI
--firefox-ext-idstring The extension ID from the store URL, e.g.https://addons.mozilla.org/addon/EXT_ID--firefox-jwt-issuerstring The JWT issuer.--firefox-jwt-secretstring The JWT secret.--firefox-zipstring The relative path to the ZIP from the root. You can use{version}in the ZIP filename, which will be replaced by theversionentry from yourpackage.json--firefox-zip-sourcestring? The relative path to the ZIP that contains the source code of your extension, if applicable. You can use{version}as well. Note that if your extension's source code is required to be seen by the review team, you do not want to store the command with the package.--firefox-changelogstring? The changes made in this version compared to the previous one. The Firefox users will see this. You can use\nfor new lines.--firefox-dev-changelogstring? The technical changes made in this version, which will be seen by the Firefox Add-ons reviewers. You can use\nfor new lines.
Get your --firefox-jwt-issuer and --firefox-jwt-secret from the Developer Hub.
Example:
crx-upload --firefox-ext-id="ExtensionID" --firefox-jwt-issuer="JwtIssuer" --firefox-jwt-secret="JwtSecret" --firefox-zip="dist/some-zip-v{version}.zip" --firefox-changelog="Changelog\nWith line breaks" --firefox-dev-changelog="Changelog for reviewers\nWith line breaks"Edge Add-ons CLI
--edge-product-idstring The product ID from the Edge Add-ons Dashboard, e.g.https://partner.microsoft.com/en-us/dashboard/microsoftedge/PRODUCT_ID--edge-client-idstring The client ID.--edge-client-secretstring The client secret.--edge-access-token-urlstring The access token URL.--edge-access-tokenstring The access token.--edge-zipstring The path to the ZIP from the root. You can use{version}in the ZIP filename, which will be replaced by theversionentry inpackage.json--edge-dev-changelogstring? The technical changes made in this version, which will be seen by the Edge Add-ons reviewers. You can use\nfor new lines.
To get your --edge-access-token, --edge-client-id, --edge-client-secret, --edge-access-token-url, follow this guide.
Example:
crx-upload --edge-product-id="ProductID" --edge-access-token="accessToken value" --edge-client-id="clientId" --edge-client-secret="clientSecret" --edge-access-token-url="accessTokenUrl" --edge-zip="dist/some-zip-v{version}.zip" --edge-dev-changelog="Changelog for reviewers\nWith line breaks"Note: Due to the way the Edge dashboard works, when an extension is being reviewed or its review has just been canceled, it will take about a minute until a cancellation will cause its state to change from "In review" to "In draft", after which the new version can be submitted. Therefore, expect for longer wait times if you run the tool on an extension you had just published/canceled.
Opera Add-ons CLI
--opera-package-idnumber The extension ID from the Opera Add-ons Dashboard, e.g.https://addons.opera.com/developer/package/PACKAGE_ID--opera-sessionidstring The value of the cookiesessionid, which will be used to log in to the publisher's account.--opera-csrftokenstring The value of the cookiecsrftoken, which will be used to upload the ZIP.--opera-zipstring The relative path to the ZIP from the root. You can use{version}in the ZIP filename, which will be replaced by theversionentry inpackage.json--opera-changelogstring? The changes made in this version, which will be seen by the Opera Add-ons reviewers. You can use\nfor new lines.
Example:
crx-upload --opera-package-id=123456 --opera-sessionid="sessionid_value" --opera-csrftoken="csrftoken_value" --opera-zip="dist/some-zip-v{version}.zip" --opera-changelog="Changelog\nWith line breaks"Notes:
Source code inspection: The Opera Add-ons reviewers require inspecting your extension's source code. This can be done by doing one of the following:
- Uploading the ZIP that contains the source code to a public folder on a storage service (e.g. Google Drive)
- Making the extension's code open source on a platform like GitHub, with clear instructions on the
README.md, and then linking to its repository.
Note that you do not want to store the command with your extension package, as the review team will have access to your precious cookies.
Node.js API method
ESM
import { deployChrome, deployFirefoxSubmissionApi, deployEdgePublishApi, deployOpera } from "crx-upload";Node.js API
Chrome Web Store API
deployChrome object
Options:
extIdstring Get it fromhttps://chrome.google.com/webstore/detail/EXT_ID, e.g.https://chrome.google.com/webstore/detail/fcphghnknhkimeagdglkljinmpbagonerefreshTokenstring The refresh token.clientIdstring The client ID.clientSecretstring The client secret.zipstring The relative path from the root to the ZIP. You can use{version}to use theversionentry from yourpackage.jsonverboseboolean? Iftrue, it will be logged to the console when the uploading has begun.
To get your refreshToken, clientId, and clientSecret, follow this guide.
Returns Promise<true> or throws an exception.
Firefox Publish API
deployFirefoxSubmissionApi object
Options:
extIdstring Get it fromhttps://addons.mozilla.org/addon/EXT_IDjwtIssuerstring The JWT issuer.jwtSecretstring The JWT secret.zipstring The relative path from the root to the ZIP. You can use{version}in the ZIP filename, which will be replaced by theversionentry from yourpackage.jsonzipSourcestring? The relative path from the root to the ZIP that contains the source code of your extension, if applicable. You can use{version}as well. Note that if your extension's source code is required to be seen by the review team, you do not want to store the deployment script with the package.changelogstring? The changes made in this version, compared to the previous one, which will be seen by the Firefox users. I recommend providing the changelog via--firefox-changelog, so it stays dynamic.devChangelogstring? The technical changes made in this version, compared to the previous one, which will be visible only to the Firefox Add-ons reviewers. I recommend providing the changelog via--firefox-dev-changelog, so it stays up to date.verboseboolean? Iftrue, every step of uploading to the Firefox Add-ons will be logged to the console.
Get your jwtIssuer and jwtSecret from the Developer Hub.
Returns Promise<true> or throws an exception.
Edge Publish API
deployEdgePublishApi object
Options:
productIdstring Get it fromhttps://partner.microsoft.com/en-us/dashboard/microsoftedge/PRODUCT_IDaccessTokenstring The access token.clientIdstring The client ID.clientSecretstring The client secret.accessTokenUrlstring The access token URL.zipstring The relative path from the root to the ZIP. You can use{version}in the ZIP filename, which will be replaced by theversionentry from yourpackage.jsondevChangelogstring? The technical changes made in this version, compared to the previous one, which will be visible only to the Edge Add-ons reviewers. I recommend providing the changelog via--edge-dev-changelog, so it stays up to date.verboseboolean? Iftrue, every step of uploading to the Edge Add-ons will be logged to the console.
To get your accessToken, clientId, clientSecret, and accessTokenUrl, follow this guide.
Returns Promise<true> or throws an exception.
Note: Due to the way the Edge dashboard works, when an extension is being reviewed or its review has just been canceled, it will take about a minute until a cancellation will cause its state to change from "In review" to "In draft", after which the new version can be submitted. Therefore, expect for longer wait times if you run the tool on an extension you had just published/canceled.
Opera Publish API
deployOpera object
Options:
packageIdnumber The package ID of the extension from the store dashboard, e.g.https://addons.opera.com/developer/package/PACKAGE_IDsessionidstring The value of the cookiesessionid, which will be used to log in to the publisher's account.csrftokenstring The value of the cookiecsrftoken, which will be used to upload the ZIP.zipstring The relative path from the root to the ZIP. You can use{version}in the ZIP filename, which will be replaced by theversionentry from yourpackage.jsonchangelogstring? The changes made in this version, compared to the previous one, which will be seen by the Opera users. I recommend providing the changelog via--opera-changelog, so it stays up to date.verboseboolean? Iftrue, every step of uploading to the Opera Add-ons will be logged to the console.
If you have a hard time obtaining the values of the cookies sessionid and csrftoken, you can run:
crx-upload --get-cookies=operaReturns Promise<true> or throws an exception.
Notes:
Source code inspection: The Opera Add-ons reviewers require inspecting your extension's source code. This can be done by doing one of the following:
- Uploading the ZIP that contains the source code to a public folder on a storage service (e.g. Google Drive)
- Making the extension's code open source on a platform like GitHub, with clear instructions on the
README.md, and then linking to its repository.
Note that you do not want to store the deployment script with your extension package, as the review team will have access to your precious cookies. If you'll open-source the extension on GitHub, you can exclude the deployment script by listing it in
.gitignore
Examples:
import { deployChrome, deployFirefoxSubmissionApi, deployEdgePublishApi, deployOpera } from "crx-upload";
deployChrome({
extId: "ExtensionID",
refreshToken: "refreshToken",
clientId: "clientId",
clientSecret: "clientSecret",
zip: "dist/some-zip-v{version}.zip",
verbose: false
}).catch(console.error);
deployFirefoxSubmissionApi({
extId: "EXT_ID",
jwtIssuer: "jwtIssuer",
jwtSecret: "jwtSecret",
zip: "dist/some-zip-v{version}.zip",
zipSource: "dist/zip-source-v{version}.zip",
changelog: "Some changes",
devChangelog: "Changes for reviewers",
verbose: false
}).catch(console.error);
deployEdgePublishApi({
productId: "PRODUCT_ID",
clientId: "clientId",
clientSecret: "clientSecret",
accessTokenUrl: "accessTokenUrl",
accessToken: "accessToken",
zip: "dist/some-zip-v{version}.zip",
devChangelog: "Changes for reviewers",
verbose: false
}).catch(console.error);
deployOpera({
packageId: 123456,
sessionid: "sessionid_value",
csrftoken: "csrftoken_value",
zip: "dist/some-zip-v{version}.zip",
changelog: "Some changes",
verbose: false
}).catch(console.error);