A GitHub Action to upload and publish browser extensions to the Chrome Web Store and/or Microsoft Edge Add-ons store.
Both stores deploy in parallel when multiple targets are specified. The first submission to each store must be done manually — this action only updates existing extensions.
src/
├── index.ts — entrypoint (calls run); ncc bundles this into dist/index.js
├── main.ts — run(): parse inputs → deploy each target → write summary
├── inputs.ts — input parsing/validation (getInputs, parseTargets)
├── env.ts — requireEnv for credentials
├── google-auth.ts — Chrome OAuth token (RS256 JWT) + base64url
├── summary.ts — GitHub job-summary table
└── targets/
├── base.ts — abstract DeployTarget base class
├── chrome.ts — ChromeWebStoreTarget
└── edge.ts — EdgeAddonsTarget
tests/ — vitest unit tests
└── fixtures/ — sample extension package (dist.zip) for the manual test workflow
- uses: GreedyLabs/action-deploy-browser-extension@v1
with:
zip-path: dist/extension.zip
targets: chrome
chrome-extension-id: ${{ vars.CHROME_EXTENSION_ID }}
env:
CHROME_SERVICE_ACCOUNT_KEY: ${{ secrets.CHROME_SERVICE_ACCOUNT_KEY }}- uses: GreedyLabs/action-deploy-browser-extension@v1
with:
zip-path: dist/extension.zip
targets: edge
edge-product-id: ${{ vars.EDGE_PRODUCT_ID }}
env:
EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }}
EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }}- uses: GreedyLabs/action-deploy-browser-extension@v1
with:
zip-path: dist/extension.zip
targets: chrome, edge
chrome-extension-id: ${{ vars.CHROME_EXTENSION_ID }}
edge-product-id: ${{ vars.EDGE_PRODUCT_ID }}
publish: true
env:
CHROME_SERVICE_ACCOUNT_KEY: ${{ secrets.CHROME_SERVICE_ACCOUNT_KEY }}
EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }}
EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }}name: Release Extension
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: npm ci && npm run build
- name: Zip extension
run: zip -r dist/extension.zip dist/ --exclude "*.map"
- name: Deploy
uses: GreedyLabs/action-deploy-browser-extension@v1
with:
zip-path: dist/extension.zip
targets: chrome, edge
chrome-extension-id: ${{ vars.CHROME_EXTENSION_ID }}
edge-product-id: ${{ vars.EDGE_PRODUCT_ID }}
publish: true
env:
CHROME_SERVICE_ACCOUNT_KEY: ${{ secrets.CHROME_SERVICE_ACCOUNT_KEY }}
EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }}
EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }}| Input | Required | Default | Description |
|---|---|---|---|
zip-path |
✅ | — | Path to the .zip file to upload |
targets |
✅ | chrome |
Comma-separated list of targets: chrome, edge |
publish |
❌ | false |
Set to true to publish after upload |
chrome-extension-id |
When targeting chrome |
— | Chrome Web Store extension ID (use vars) |
edge-product-id |
When targeting edge |
— | Edge Add-ons product ID (use vars) |
| Output | Description |
|---|---|
chrome-upload-status |
Chrome Web Store upload state (SUCCESS or error) |
chrome-publish-status |
Chrome Web Store publish status |
edge-operation-id |
Edge Add-ons upload operation ID |
- Google Cloud Console → IAM → Service Accounts → Create service account
- Keys tab → Add Key → JSON → download
- Enable the Chrome Web Store API in APIs & Services → Library
- Chrome Web Store Developer Dashboard → Account → API Access → register the service account email
- Store the JSON file contents (minified with
jq -c . key.json) asCHROME_SERVICE_ACCOUNT_KEY
- Partner Center → Extensions → Publish API
- Click Create API credentials
- Copy the Client ID and API Key