The old Chrome Web Store API (v1.1) stops working on 15 October 2026. Scripts calling www.googleapis.com/chromewebstore/v1.1 need the v2 endpoints below.What you can automate
| Task | How |
|---|---|
| Upload a new version of an existing item | API: upload |
| Submit it for review, publish on approval | API: publish |
| Publish later, or to a percentage of users | API: publish, setPublishedDeployPercentage |
| Check where a submission is | API: fetchStatus |
| Withdraw a submission | API: cancelSubmission |
| Create a new item | Dashboard, by hand, once |
| Description, category, icon, screenshots, promo tiles | Not in the API: Listing Filler |
| Single purpose, permission justifications, data use | Not in the API: Listing Filler |
| Visibility, regions, pricing | Dashboard, by hand |
The API only updates items that already exist, and only once their Store listing and Privacy pages have been completed. So the first release is always by hand; every update after it can be a script.
One-time setup
- In Google Cloud, create a project and enable the Chrome Web Store API.
- Set up the OAuth consent screen as External, then publish it. While it stays in Testing, refresh tokens expire after 7 days and the script stops working a week later.
- Create an OAuth client of type Web application with the redirect URI
https://developers.google.com/oauthplayground. - In the OAuth Playground, use your own client ID and secret, authorize the scope
https://www.googleapis.com/auth/chromewebstoreand exchange the code for a refresh token. - Copy your publisher ID from the developer dashboard's account settings, and the 32-letter item ID of the extension.
The publishing account needs 2-step verification. v2 also accepts a service account instead of a refresh token, which suits company CI; see Google's guide.
The script
#!/bin/bash
# publish.sh: upload extension.zip and submit it for review.
set -euo pipefail
API=https://chromewebstore.googleapis.com
ITEM="publishers/$PUBLISHER_ID/items/$ITEM_ID"
TOKEN=$(curl -s https://oauth2.googleapis.com/token \
-d client_id="$CLIENT_ID" -d client_secret="$CLIENT_SECRET" \
-d refresh_token="$REFRESH_TOKEN" -d grant_type=refresh_token \
| node -pe 'JSON.parse(require("fs").readFileSync(0)).access_token')
# 1. Upload the package. The version in manifest.json must be higher than the live one.
curl -sf -H "Authorization: Bearer $TOKEN" -X POST -T extension.zip \
"$API/upload/v2/$ITEM:upload"
# 2. Wait until the upload is processed.
curl -sf -H "Authorization: Bearer $TOKEN" "$API/v2/$ITEM:fetchStatus"
# 3. Submit for review; it goes live when approved.
curl -sf -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-X POST -d '{"publishType":"DEFAULT_PUBLISH"}' "$API/v2/$ITEM:publish"To release to some users first, or to publish at a moment you choose after approval, send this body to :publish instead, then raise the share with :setPublishedDeployPercentage:
{
"publishType": "STAGED_PUBLISH",
"deployInfos": [{ "deployPercentage": 10 }]
}skipReview: true asks Google to skip review; Google decides whether it can. blockOnWarnings: true makes the request fail on validation warnings instead of passing them by.
Publish on every tag
# .github/workflows/publish.yml
name: Publish to Chrome Web Store
on:
push:
tags: ["v*"]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd extension && zip -r ../extension.zip . -x '*.map'
- run: ./publish.sh
env:
CLIENT_ID: ${{ secrets.CWS_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.CWS_CLIENT_SECRET }}
REFRESH_TOKEN: ${{ secrets.CWS_REFRESH_TOKEN }}
PUBLISHER_ID: ${{ secrets.CWS_PUBLISHER_ID }}
ITEM_ID: ${{ secrets.CWS_ITEM_ID }}Bump version in manifest.json, tag the commit, push. The store rejects an upload whose version isn't higher than the live one.
The listing: what the API can't do
Description, category, icon, screenshots, promo tiles and every privacy answer live only in the developer dashboard, typed in by hand, once per language. For an extension in 55 languages that is an afternoon of pasting per release.
The free Chrome Web Store Listing Filler fills those pages from one ZIP and stops before Submit. Chrome blocks extensions on the dashboard by default, so it runs in a separate Chrome profile started with one setting; the page has the commands.
The ZIP's format is documented, so any script can write it. Or let the Extension Marketing skill ($79) research your competitors and keywords, let you pick and preview the listing, and write the ZIP for you.
The whole release, in order
- First release: create the item and upload the package in the dashboard.
- Fill Store listing and Privacy with the Listing Filler; submit by hand.
- Every update after that: tag, and the workflow uploads and submits.
- Listing changes: new ZIP, Fill this page, save.