Auto-Publishing to Chrome & Firefox Stores
joe-links will automatically publish new extension versions to the Chrome Web Store and Firefox Add-ons (AMO) when you push a version tag, once the publish jobs land in CI. This guide walks you through setting up the required API credentials for each store ahead of time.
The CI publish jobs described here are planned, not implemented — there are no publish-chrome / publish-firefox jobs in .github/workflows/ci.yml today. Store publishing is tracked in #219 (Chrome Web Store) and #220 (Firefox Add-ons). The credential setup below is real and can be done now; the workflow YAML further down is a draft for those issues.
Both stores require the initial extension submission to be uploaded manually through their web dashboards. The automated workflow will only handle updates to an already-listed extension.
Chrome Web Store
1. Enable the Chrome Web Store API
- Go to the Google Cloud Console
- Create a new project (or select an existing one)
- Navigate to APIs & Services → Library
- Search for Chrome Web Store API and click Enable
2. Create OAuth2 credentials
- Go to APIs & Services → Credentials
- Click Create Credentials → OAuth client ID
- Select application type: Desktop app
- Name it something like
joe-links-extension-publish - Click Create — note the Client ID and Client Secret
3. Set the OAuth consent screen to production
If the OAuth consent screen is left in "Testing" mode, your refresh token will expire after 7 days. You must set it to "In production" for long-lived tokens.
- Go to APIs & Services → OAuth consent screen
- Click Publish App to move from Testing to Production
- This does not require Google verification for the Chrome Web Store API scope
4. Obtain a refresh token
Use the chrome-webstore-upload-cli helper to complete the one-time OAuth2 consent flow:
npx chrome-webstore-upload-cli init
This opens a browser window. Sign in with the Google account that owns the Chrome Web Store listing, grant access, and the CLI prints a Refresh Token.
Alternatively, you can use the OAuth 2.0 Playground with the https://www.googleapis.com/auth/chromewebstore scope.
5. Find your Extension ID
After your extension is approved on the Chrome Web Store:
- Go to the Chrome Web Store Developer Dashboard
- Click on your extension
- The URL contains the Extension ID — a 32-character alphanumeric string
6. Add GitHub secrets
Add these as Repository secrets in your GitHub repo (Settings → Secrets and variables → Actions):
| Secret | Value |
|---|---|
CHROME_EXTENSION_ID | 32-character ID from the CWS dashboard |
CHROME_CLIENT_ID | OAuth2 Client ID from Google Cloud Console |
CHROME_CLIENT_SECRET | OAuth2 Client Secret |
CHROME_REFRESH_TOKEN | Refresh token from the consent flow |
Firefox Add-ons (AMO)
1. Generate API credentials
- Sign in at addons.mozilla.org
- Go to your API Keys page
- Click Generate new credentials
- Note the JWT issuer (API Key) and JWT secret
AMO JWT secrets do not expire automatically, but Mozilla recommends rotating them periodically. If you regenerate them, the old credentials are immediately invalidated — update your GitHub secrets right away.
2. Confirm your addon GUID
Your extension's GUID is defined in manifest.json under browser_specific_settings.gecko.id. For joe-links this is:
joe-links@joestump.net
You do not need to store this as a secret — it's in the manifest. But it must match the listing on AMO.
3. Add GitHub secrets
| Secret | Value |
|---|---|
AMO_JWT_ISSUER | JWT issuer from the AMO API keys page |
AMO_JWT_SECRET | JWT secret from the same page |
GitHub Actions workflow (draft)
The jobs below are a draft/reference for #219 and #220 — they are not yet part of .github/workflows/ci.yml. Once the secrets are configured and the jobs land, they will run on version tag pushes alongside the existing release job:
publish-chrome:
name: Publish to Chrome Web Store
needs: build
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Package extension
run: |
cd integrations/extension
zip -r ../../joe-links-extension.zip \
manifest.json background.js options.html options.js \
popup.html popup.js icons/
- name: Upload and publish
run: |
npx chrome-webstore-upload-cli@3 upload \
--source joe-links-extension.zip \
--auto-publish
env:
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
publish-firefox:
name: Publish to Firefox Add-ons
needs: build
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Submit to AMO
run: |
npx web-ext@8 sign \
--source-dir integrations/extension \
--channel listed
env:
WEB_EXT_API_KEY: ${{ secrets.AMO_JWT_ISSUER }}
WEB_EXT_API_SECRET: ${{ secrets.AMO_JWT_SECRET }}
Publishing flow (once #219/#220 land)
- Update the extension version in
integrations/extension/manifest.json - Commit and tag a release:
git tag vX.Y.Z && git push origin vX.Y.Z - The CI workflow will:
- Run lint and tests
- Package the extension zip
- Upload to Chrome Web Store and submit for review
- Upload to Firefox Add-ons and submit for review
- Both stores review the update (minutes to a few days)
You cannot re-upload the same version number to either store. Always bump the version in manifest.json before tagging.
Gotchas
| Issue | Details |
|---|---|
| Chrome review times | Unpredictable — minutes to several days. Extensions with broad permissions (<all_urls>) may trigger manual review. |
| Chrome refresh token expiry | Set OAuth consent screen to "In production" or the token expires in 7 days. |
| Chrome rate limits | Max ~20 uploads per day per extension. |
| Firefox source code | Not required since joe-links ships raw (unbundled) JS — no build step to verify. |
| Firefox signing | Listed extensions are signed automatically after review approval. |
| AMO key rotation | Regenerating AMO credentials immediately invalidates old ones. |