---
title: "Continuous release of a chrome extension using CircleCI"
description: "Continuous release of a chrome extension using CircleCI"
canonical_url: "https://www.bigbinary.com/blog/continuously-upload-chrome-extension-with-circleci"
markdown_url: "https://www.bigbinary.com/blog/continuously-upload-chrome-extension-with-circleci.md"
---

# Continuous release of a chrome extension using CircleCI

Continuous release of a chrome extension using CircleCI

- Author: Amit Choudhary
- Published: June 27, 2018
- Categories: Misc

We have recently worked on many chrome extensions. Releasing new chrome
extensions manually gets tiring after a while.

So, we thought about automating it with CircleCI, similar to continuous
deployment.

We are using the following configuration in `circle.yml` to continuously release
chrome extensions from the master branch.

```yaml
workflows:
  version: 2
  main:
    jobs:
      - test:
          filters:
            branches:
              ignore: []
      - build:
          requires:
            - test
          filters:
            branches:
              only: master
      - publish:
          requires:
            - build
          filters:
            branches:
              only: master

version: 2
jobs:
  test:
    docker:
      - image: cibuilds/base:latest
    steps:
      - checkout
      - run:
          name: "Install Dependencies"
          command: |
            apk add --no-cache yarn
            yarn
      - run:
          name: "Run Tests"
          command: |
            yarn run test
  build:
    docker:
      - image: cibuilds/chrome-extension:latest
    steps:
      - checkout
      - run:
          name: "Install Dependencies"
          command: |
            apk add --no-cache yarn
            apk add --no-cache zip
            yarn
      - run:
          name: "Package Extension"
          command: |
            yarn run build
            zip -r build.zip build
      - persist_to_workspace:
          root: /root/project
          paths:
            - build.zip

  publish:
    docker:
      - image: cibuilds/chrome-extension:latest
    environment:
      - APP_ID: <APP_ID>
    steps:
      - attach_workspace:
          at: /root/workspace
      - run:
          name: "Publish to the Google Chrome Store"
          command: |
            ACCESS_TOKEN=$(curl "https://accounts.google.com/o/oauth2/token" -d "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token&redirect_uri=urn:ietf:wg:oauth:2.0:oob" | jq -r .access_token)
            curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -X PUT -T /root/workspace/build.zip -v "https://www.googleapis.com/upload/chromewebstore/v1.1/items/${APP_ID}"
            curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -H "Content-Length: 0" -X POST -v "https://www.googleapis.com/chromewebstore/v1.1/items/${APP_ID}/publish"
```

We have created three jobs named as `test`, `build` and `publish` and used these
jobs in our workflow to run tests, build the extension, and publish them to the
chrome store, respectively. Every step requires the previous step to run
successfully.

Let's check each job one by one.

```yaml
test:
  docker:
    - image: cibuilds/base:latest
  steps:
    - checkout
    - run:
        name: "Install Dependencies"
        command: |
          apk add --no-cache yarn
          yarn
    - run:
        name: "Run Tests"
        command: |
          yarn run test
```

We use [cibuilds](https://github.com/cibuilds/base) docker image for this job.
First, we do a checkout to the branch and then use `yarn` to install
dependencies. Alternatively, we can use `npm` to install dependencies as well.
Then, as the last step, we are use `yarn run test` to run tests. We can skip
this step if running tests is not needed.

```yaml
build:
  docker:
    - image: cibuilds/chrome-extension:latest
  steps:
    - checkout
    - run:
        name: "Install Dependencies"
        command: |
          apk add --no-cache yarn
          apk add --no-cache zip
          yarn
    - run:
        name: "Package Extension"
        command: |
          yarn run build
          zip -r build.zip build
    - persist_to_workspace:
        root: /root/project
        paths:
          - build.zip
```

For building chrome extensions, we use the
[chrome-extension](https://github.com/cibuilds/chrome-extension) image. Here, we
also first do a checkout and then, install dependencies using yarn. Note, we are
install zip utility along with yarn because we need to zip our chrome extension
before publishing it in next step. Also, we are not generating version numbers
on our own. The version number will be picked from the manifest file. This step
assumes that we have a task named `build` in `package.json` to build our app.

The Chrome store rejects multiple uploads with the same version number. So, we
have to make sure to update the version number, which should be unique in the
manifest file before this step.

In the last step, we use `persist_to_workspace` to make `build.zip` available
for the next step, publishing.

```yaml
publish:
  docker:
    - image: cibuilds/chrome-extension:latest
  environment:
    - APP_ID: <APP_ID>
  steps:
    - attach_workspace:
        at: /root/workspace
    - run:
        name: "Publish to the Google Chrome Store"
        command: |
          ACCESS_TOKEN=$(curl "https://accounts.google.com/o/oauth2/token" -d "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&refresh_token=${REFRESH_TOKEN}&grant_type=refresh_token&redirect_uri=urn:ietf:wg:oauth:2.0:oob" | jq -r .access_token)
          curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -X PUT -T /root/workspace/build.zip -v "https://www.googleapis.com/upload/chromewebstore/v1.1/items/${APP_ID}"
          curl -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "x-goog-api-version: 2" -H "Content-Length: 0" -X POST -v "https://www.googleapis.com/chromewebstore/v1.1/items/${APP_ID}/publish"
```

For publishing of the chrome extension, we use the
[chrome-extension](https://github.com/cibuilds/chrome-extension) image.

We need `APP_ID`, `CLIENT_ID`, `CLIENT_SECRET` and
`REFRESH_TOKEN`/`ACCESS_TOKEN` to publish our app to the chrome store.

`APP_ID` needs to be fetched from
[Google Webstore Developer Dashboard](https://chrome.google.com/webstore/developer/dashboard).
`APP_ID` is unique for each app whereas `CLIENT_ID`, `CLIENT_SECRET` and
`REFRESH_TOKEN`/`ACCESS_TOKEN` can be used for multiple apps. Since `APP_ID` is
generally public, we specify that in the yml file. `CLIENT_ID`, `CLIENT_SECRET`
and `REFRESH_TOKEN`/`ACCESS_TOKEN` are stored as private environment variables
using CircleCI UI. For cases when our app is unlisted in the chrome store, we
need to store `APP_ID` as a private environment variable.

`CLIENT_ID` and `CLIENT_SECRET` need to be fetched from
[Google API console](https://console.developers.google.com/). There, we need to
select a project and then click on the credentials tab. If there is no project,
we need to create one and then access the credentials tab.

`REFRESH_TOKEN` needs to be fetched from Google API. It also defines the scope
of access for Google APIs. We need to refer to
[Google OAuth2](https://developers.google.com/identity/protocols/OAuth2WebServer)
for obtaining the refresh token. We can use any language library.

In the first step of the `publish` job, we are attaching a workspace to access
`build.zip`, which was created previously. Now, by using all the required tokens
obtained previously, we need to obtain an access token from Google OAuth API
(Link is not available), which must be used to push the app to the chrome store.
Then, we make a `PUT` request to the Chrome store API to push the app, and then
use the same API again, to publish the app.

Uploading via API has one more advantage over manual upload. Manual upload
generally takes up to 1 hour to show the app in the chrome store. Whereas
uploading using Google API generally reflects the app within 5-10 minutes,
considering app does not go for a review by Google.

## Links

- [Human page](https://www.bigbinary.com/blog/continuously-upload-chrome-extension-with-circleci)
