Upload and publish extensions to the Chrome Web Store from Node.js, using the Chrome Web Store API v2.
Looking for a command line tool? Use chrome-webstore-upload-cli.
Need Google API keys? Follow the guide.
npm install --save-dev chrome-webstore-uploadYou will need a Chrome Web Store developer account with an existing extension (the first version must be created manually in the dashboard) and these values:
| Value | Where to get it |
|---|---|
extensionId |
The 32-character ID of your extension (visible in its Chrome Web Store URL and in the Developer Dashboard) |
publisherId |
Your developer account identifier, not the extension ID. Found in the Developer Dashboard URL and on its Settings page |
clientId |
Google OAuth credentials, see the guide |
clientSecret |
Same as above. Optional if the token was created for a "Chrome App" OAuth client |
refreshToken |
Same as above |
Never commit these values. Load them from environment variables or your CI secret store.
import chromeWebstoreUpload from 'chrome-webstore-upload';
const store = chromeWebstoreUpload({
extensionId: process.env.EXTENSION_ID!,
publisherId: process.env.PUBLISHER_ID!,
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.CLIENT_SECRET!,
refreshToken: process.env.REFRESH_TOKEN!,
});
const token = await store.fetchToken();
await store.uploadExisting('./dist', token);
await store.publish('DEFAULT_PUBLISH', token);All methods return a promise.
Creates a client bound to a single extension.
const store = chromeWebstoreUpload({
extensionId: 'ecnglinljpjkbgmdpeiglonddahpbkeb',
publisherId: 'your-publisher-id',
clientId: 'xxxxxxxxxx',
clientSecret: 'xxxxxxxxxx',
refreshToken: 'xxxxxxxxxx',
});| Option | Type | Description |
|---|---|---|
extensionId |
string |
ID of the extension to manage |
publisherId |
string |
Your Chrome Web Store publisher ID |
clientId |
string |
Google OAuth client ID |
clientSecret |
string |
Google OAuth client secret. Not needed for tokens generated for a "Chrome App" OAuth client |
refreshToken |
string |
OAuth refresh token |
Uploads a new version of an existing extension.
| Parameter | Type | Default | Description |
|---|---|---|---|
source |
ReadStream | ReadableStream | string |
— | A zip stream, or a path to a .zip, .crx or directory. Directories are zipped automatically and must contain a manifest.json. .crx is only supported as a path, not as a stream |
token |
string | Promise<string> |
fetched on demand | Access token |
maxAwaitInProgressSeconds |
number |
0 |
If the API responds with IN_PROGRESS, poll every 2 seconds for up to this many seconds. Values below 2 disable polling |
Returns the upload response.
import {createReadStream} from 'node:fs';
// Zip stream
await store.uploadExisting(createReadStream('./mypackage.zip'));
// Zip or crx path
await store.uploadExisting('./path/to/extension.zip');
await store.uploadExisting('./path/to/extension.crx');
// Directory (zipped for you)
await store.uploadExisting('./path/to/extension-directory');
// Wait up to 60 seconds for processing to complete
await store.uploadExisting('./dist', undefined, 60);Submits the uploaded version for review and publishing.
| Parameter | Type | Default | Description |
|---|---|---|---|
publishType |
'DEFAULT_PUBLISH' | 'STAGED_PUBLISH' |
'DEFAULT_PUBLISH' |
When the item is published |
token |
string | Promise<string> |
fetched on demand | Access token |
deployPercentage |
number |
— | Initial rollout percentage |
Returns the publish response.
await store.publish('DEFAULT_PUBLISH');
await store.publish('STAGED_PUBLISH', undefined, 10);Updates the rollout percentage of an already published extension, without triggering a new review. The value must be higher than the current one. Resolves with nothing.
await store.setDeployPercentage(50);See the API reference.
Fetches the current item status.
const status = await store.get();
console.log(status);Exchanges the refresh token for an access token.
const token = await store.fetchToken();Fetch it once and pass it to every other method to avoid redundant token requests.
const token = await store.fetchToken();
const upload = await store.uploadExisting('./dist', token, 120);
console.log(upload);
const publish = await store.publish('DEFAULT_PUBLISH', token);
console.log(publish);const token = await store.fetchToken();
await store.uploadExisting('./dist', token, 120);
await store.publish('STAGED_PUBLISH', token, 5);
// Later, once you're confident in the release
for (const percentage of [25, 50, 100]) {
await store.setDeployPercentage(percentage, token);
}Methods reject when the API returns an error or when the upload fails. API errors are thrown as CWSError.
import chromeWebstoreUpload, {CWSError} from 'chrome-webstore-upload';
try {
await store.uploadExisting('./dist', undefined, 120);
await store.publish();
} catch (error) {
if (error instanceof CWSError) {
console.error('Chrome Web Store API error:', error);
} else {
console.error('Release failed:', error);
}
process.exitCode = 1;
}- chrome-webstore-upload-cli - Command line interface for this module
- chrome-webstore-upload-keys - Generate the Google API keys
- webext-storage-cache - Map-like promised cache storage with expiration
- webext-dynamic-content-scripts - Automatically registers your
content_scriptson domains added viapermission.request - Awesome-WebExtensions - A curated list of awesome resources for WebExtensions development
- More…