Skip to content
Latchkey

CI/CD for a Chrome Extension with GitHub Actions

Build, zip, and publish your MV3 extension to the Chrome Web Store automatically.

A Manifest V3 extension is a web bundle with a manifest.json. The build produces a zip, and the Chrome Web Store API uploads and publishes it. This recipe validates on PRs and publishes on a version tag.

What the pipeline does

  • install deps with npm ci
  • lint and test
  • build the extension bundle
  • zip the dist directory
  • upload and publish to the Chrome Web Store on a tag

The workflow

The build emits dist/. On a version tag, the chrome-webstore-upload-cli uploads the zip and publishes it using OAuth client credentials and a refresh token stored as secrets.

.github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]
    tags: ['v*']
  pull_request:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run lint --if-present
      - run: npm test --if-present
      - run: npm run build
      - run: cd dist && zip -r ../extension.zip .
      - if: startsWith(github.ref, 'refs/tags/v')
        run: |
          npx chrome-webstore-upload-cli@3 upload --source extension.zip --auto-publish
        env:
          EXTENSION_ID: ${{ secrets.CWS_EXTENSION_ID }}
          CLIENT_ID: ${{ secrets.CWS_CLIENT_ID }}
          CLIENT_SECRET: ${{ secrets.CWS_CLIENT_SECRET }}
          REFRESH_TOKEN: ${{ secrets.CWS_REFRESH_TOKEN }}

Caching and speed

cache: npm handles installs. Extension bundles are small, so builds are fast and CI is dominated by install; cheaper managed runners such as Latchkey keep frequent PR runs inexpensive and auto-retry transient failures.

Deploying

The Web Store review process runs after the upload, so a publish is a submission, not an instant rollout. Bump the version in manifest.json on every release since the store rejects duplicate versions. Store the OAuth credentials as repository secrets, never in the repo.

Key takeaways

  • Build, zip dist/, then upload to the Chrome Web Store.
  • Bump manifest.json version on every release.
  • Store Web Store OAuth credentials as repo secrets.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →