Andy YeungI stopped uploading my Chrome extension by hand: every merge now builds, versions and submits it to the Chrome Web Store, with no Google secret stored anywhere.
I maintain a small Chrome extension that translates text with Chrome's built-in AI. It doesn't have many users, but I update it now and then, and every update meant the same routine: bump the version, write the changelog, build, zip, sign in to the store dashboard, upload and submit. None of it was hard. It was just easy to get wrong, like the time I forgot the version number and the store refused the upload.
Most guides for automating this put a long-lived credential in your CI secrets, either a service account key or an OAuth refresh token. That works, but it means a key that never expires sits next to your code. If it leaks, anyone can publish as you.
I wanted the automation without a key to lose.
1. Let the commits decide the version. I write commit messages in the Conventional Commits style. A new feature bumps the minor version, a fix bumps the patch and docs or chores don't release at all. A release tool reads them, updates the manifest and changelog, and tags the release.
2. Always keep a zip. Every release builds the extension and attaches the zip to a GitHub release, so there's always something to upload by hand if the store step ever fails.
3. Create an identity that can't do much. In Google Cloud I created a service account with no roles at all. Its only power comes from being added as a member of my publisher account in the Chrome Web Store dashboard.
4. Trust GitHub instead of a key. Workload Identity Federation lets a GitHub Actions run show Google a short-lived proof of which repository it comes from. Google checks that it's my repository and, only then, gives it a token for the service account that lasts 15 minutes. I restricted it to one repository, so forks and other projects can't use it.
5. Connect the store. In the dashboard's Settings, I added the service account and copied my publisher ID. (Older guides send you to an Account page that no longer exists.)
6. Keep a human in the loop. The upload runs in a protected GitHub environment with me as the required reviewer. Every store submission stops and waits until I press Approve. The store job starts like this:
permissions:
contents: read
id-token: write # lets the run ask GitHub who it is
environment: chrome-web-store # waits for my approval
steps:
- uses: google-github-actions/auth@v3
with:
workload_identity_provider: ${{ vars.GCP_WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ vars.GCP_SERVICE_ACCOUNT }}
token_format: access_token
access_token_scopes: https://www.googleapis.com/auth/chromewebstore
7. Test without changing anything. The workflow has a read-only status mode that just asks the store what's published. When it answered with my current version, I knew the whole chain worked.
Google's review still takes its time, and the listing text and screenshots still live in the dashboard. But shipping a fix no longer needs a free evening.
If you'd like the same setup, paste this into an AI coding assistant inside your extension's repository:
Set up automatic publishing for my Chrome extension in this repository, using this design:
1. Versioning: use Conventional Commits with release-it (or the closest tool for this stack).
feat bumps minor, fix bumps patch, docs/chore don't release. On every push to main it
bumps manifest.json, updates CHANGELOG.md, commits, tags and creates a GitHub Release
with the zipped build attached.
2. Store upload: a reusable GitHub Actions workflow that calls the Chrome Web Store API v2
(upload, wait for processing, then publish) with a small script. Support three modes:
status (read-only), upload (draft only) and publish. Refuse to upload a version that
isn't higher than the one in the store.
3. Credentials: no long-lived secrets. Use Google Workload Identity Federation with
google-github-actions/auth, permissions id-token: write, token_format access_token and
the chromewebstore scope. The service account gets no IAM roles; restrict the WIF
provider with an attribute condition to this repository only.
4. Safety: run the store job in a GitHub Environment named chrome-web-store with me as a
required reviewer. Read all IDs from repository variables (not secrets):
GCP_WORKLOAD_IDENTITY_PROVIDER, GCP_SERVICE_ACCOUNT, CWS_PUBLISHER_ID, CWS_EXTENSION_ID.
Skip the store job (don't fail) until all four are set.
5. Docs: write docs/PUBLISHING.md with the one-time setup: the gcloud commands for Cloud
Shell (enable iamcredentials, sts and chromewebstore APIs, create the pool and provider,
bind roles/iam.workloadIdentityUser), adding the service account under Settings in the
Chrome Web Store Developer Dashboard, where to find the Publisher ID, and how to test
with the status mode.
Don't ask me for any secret. Tell me which steps need me in a browser, and stop after
writing the code and docs so I can review them.
Cover: Free carton boxes factory photo by Unknown, CC0 1.0.