Skip to content
Guide · updated 6 October 2026

How to automate publishing a Chrome extension

A Chrome Web Store release has two halves: the package and the listing. Google's API automates the first. Nothing official automates the second. Here is how to script both.

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

TaskHow
Upload a new version of an existing itemAPI: upload
Submit it for review, publish on approvalAPI: publish
Publish later, or to a percentage of usersAPI: publish, setPublishedDeployPercentage
Check where a submission isAPI: fetchStatus
Withdraw a submissionAPI: cancelSubmission
Create a new itemDashboard, by hand, once
Description, category, icon, screenshots, promo tilesNot in the API: Listing Filler
Single purpose, permission justifications, data useNot in the API: Listing Filler
Visibility, regions, pricingDashboard, 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

  1. In Google Cloud, create a project and enable the Chrome Web Store API.
  2. 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.
  3. Create an OAuth client of type Web application with the redirect URI https://developers.google.com/oauthplayground.
  4. In the OAuth Playground, use your own client ID and secret, authorize the scope https://www.googleapis.com/auth/chromewebstore and exchange the code for a refresh token.
  5. 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

  1. First release: create the item and upload the package in the dashboard.
  2. Fill Store listing and Privacy with the Listing Filler; submit by hand.
  3. Every update after that: tag, and the workflow uploads and submits.
  4. Listing changes: new ZIP, Fill this page, save.
How to Automate Publishing a Chrome Extension (Chrome Web Store API v2) — Verblike