<!-- LLM_VERSION_INFO
FORMAT: text/markdown
CONTENT_TYPE: article
ORIGINAL_URL: https://jam.dev/blog/automating-chrome-extension-publishing
ALTERNATE_VERSION: blog/automating-chrome-extension-publishing/index.html (text/html)
EXTRACTION_DATE: 2026-04-17T03:50:36.889Z

This is the markdown version with text-only content (images converted to alt-text).
For rich formatting with images, request the HTML version at: blog/automating-chrome-extension-publishing/index.html
-->

In the earlier days of Jam engineering, we did a few things that don’t scale. As one of those many things, we _manually_ submitted this [developer Chrome extension](/content/blog/best-chrome-extensions-for-developers/?ref=strawberryjam.ghost.io) to the Chrome Web Store. This meant whenever we wanted to release newer versions of our extension, one of our engineers had to run a script to build the extension bundle and literally click and drop the archive file into the Web Store dashboard.

Needless to say this became a pain point for us! This post describes our approach to automating this, but before that here’s a quick overview of how publishing an extension works:

1. Build your Chrome extension
2. Archive the output folder
3. Go to [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/devconsole?ref=strawberryjam.ghost.io)
4. Potentially login, and switch to your organization’s publisher
5. Upload new package
6. Submit for review
7. Wait 1-2 business days for extension review
8. Extension is published

To save us time, whenever we push a tag to our `master` branch, our continuous integration system automatically uploads the extension archive to the Web Store!

## Automating extension upload

We use GitHub Workflows as our CI system and follow the [GitHub branching model](https://docs.github.com/en/get-started/quickstart/github-flow?ref=strawberryjam.ghost.io) so our shared branches are `develop`, `staging`, and `master`.

When we push a semver git tag to master, CI runs a specific workflow for publishing the extension:

```yaml
name: Update Chrome Extension

on:
  push:
    tags:
      - v*.*.*
```

Inside of this workflow we have two main jobs, building and publishing the artifact.

```yaml
jobs:
  build-chrome-extension:
    name: Build Chrome extension artifact
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

- name: Build
        run: |-
          # fill in your build steps here...
          # we archive the dist folder and include SHA commit as the last step
          zip -r chrome-extension-${{ github.event.pull_request.head.sha }}.zip dist

- name: Archive chrome-extension artifact
        uses: actions/upload-artifact@v2
        with:
          name: chrome-extension-${{ github.sha }}
          path: chrome-extension-${{ github.event.pull_request.head.sha }}.zip
```

Then the next job is for uploading the extension. We use a CLI utility called [chrome-webstore-upload-cli](https://github.com/fregante/chrome-webstore-upload-cli?ref=strawberryjam.ghost.io)

```yaml
  upload-extension:
    name: Upload extension
    runs-on: ubuntu-latest
    needs: build-chrome-extension
    env:
      EXTENSION_ID: <extension id here>

steps:
      - uses: actions/setup-node@v2-beta
        with:
          node-version: "16.10"

- name: Download bundle artifact
        uses: actions/download-artifact@v3
        with:
          name: chrome-extension-${{ github.sha }}

- name: Install webstore cli
        run: |-
          npm install -g chrome-webstore-upload-cli

- name: Upload step
        run: |-
          chrome-webstore-upload upload \
            --source chrome-extension-${{ github.event.pull_request.head.sha }}.zip \
            --extension-id ${{ env.EXTENSION_ID }} \
            --client-id ${{ secrets.CI_GOOGLE_CLIENT_ID }} \
            --client-secret ${{ secrets.CI_GOOGLE_CLIENT_SECRET }} \
            --refresh-token ${{ secrets.CI_GOOGLE_REFRESH_TOKEN }}
```

You’ll need to generate Google API keys which is outlined [here](https://github.com/fregante/chrome-webstore-upload/blob/main/How%20to%20generate%20Google%20API%20keys.md?ref=strawberryjam.ghost.io).

Then in your GitHub repo go to **Settings** > **Security** > **Actions** and click **New repository secret** for each of the above secrets.

### Accessing the bundle

As a bonus, in the GitHub Workflow summary page, you can access the produced artifact at the bottom of the page.

In our other builds like `staging` branch or feature branches, we use the build artifact to leave a GitHub comment with a link to the bundle. You could also send a notification to your company chat like Slack, Hipchat, etc.

## Chrome Web Store Submission

Once the above workflow gets triggered it only uploads the package. Remember that this is only _staging_ the extension submission, we still manually visit the developer dashboard and submit for review but we’ve automated the most tedious parts of it.

Typically, you’ll update your manifest version for each release. We verify that the newest draft in the [Developer Dashboard](https://chrome.google.com/webstore/devconsole?ref=strawberryjam.ghost.io) matches our upcoming version.

When we are ready to submit, we click **Submit for Review**.

Then we wait! The approval process is async and takes 1-2 days but now that we’ve automated some of the toil, we can spend more time on building more features and making Jam better!

## Dealing with bugs is 💩, but not with Jam.

Capture bugs fast, in a format that thousands of developers love.

[Get Jam for free](/content/?utm_source=jam-blog&utm_medium=blog&utm_campaign=jam_blog_footer_2023_01/index.html)
