DEV Community

Cover image for Simplify Browser Extension Deployment with GitHub Actions
jellyfith
jellyfith

Posted on

Simplify Browser Extension Deployment with GitHub Actions

Are you ready to streamline your browser extension deployment process? Manually deploying extensions across multiple stores and managing complex manifest v3 requirements can be a daunting task. With GitHub Actions, you can automate the entire deployment process, ensuring consistency, reducing errors, and saving valuable time.

In this guide, we’ll walk you through how to automate deployments for Chrome Web Store, Microsoft Edge Add-ons, and Firefox Add-ons using GitHub Actions. Whether you’re deploying individual versions or compiling a release tag, our workflow will handle everything from checking out the latest code to packaging and publishing your extensions.

By the end of this article, you’ll be able to:

  • Automate deployments across multiple stores
  • Manage complex manifest requirements efficiently
  • Set up version control integration for smooth deployment
  • Take advantage of GitHub’s built-in features like release tracking

This guide is perfect for developers who want to ensure their extension deploys seamlessly and reliably. So, are you ready to take your extension deployment process to the next level? Let’s get started!

The Struggles of Multi-Browser Extension Deployment

Deploying a web extension to various browser web stores can be a laborious and error-prone task, particularly when working with manifest v3. Each platform imposes unique requirements that complicate the deployment process. Here are some common pain points:

  • Firefox: Extensions utilizing bundlers like webpack or Extension.js require zipped source code. Additionally, Firefox mandates submissions in Cross-Platform Install (XPI) format.
  • Chrome and Edge expect submissions in .zip format without the need for source code.

In addition to these specific requirements, you must manually visit each web store and upload extension artifacts. To ease this burden, we can create a streamlined solution using GitHub Actions. This method will offer us numerous advantages:

  • Automation: The entire deployment pipeline is automated, checking out the project, building it, and saving build artifacts in a GitHub release.
  • Direct publishing: Artifacts are published directly to the Chrome Web Store, Mozilla Add-on Store, and Edge Partner Center.
  • One-time setup: Once set up, you only need to cut a new release on GitHub to initiate the entire process.
  • Signed artifacts: The action generates signed artifacts, attaches them to the release, and makes them available for users to download, inspect, or run locally without using a web store.

This guide will demonstrate gathering the necessary API keys and metadata, setting up each component of the pipeline, and automating the entire deployment process for your web extension.

Prerequisites

Before automating your web extension deployment, please ensure you have completed the following prerequisites:

  • Have a Web Extension: You may choose to create a simple extension using various tools such as Extension.js or adapt an existing project that adheres to manifest v3 requirements. While this guide demonstrates using Extension.js, it is not strictly required for the deployment process.
  • Manually Publish the First Version: The initial publication process is essential for setting up your extension on each platform. Depending on the store, this process may take varying amounts of time:
    • Chrome Web Store: Typically 1-3 days for review and approval.
    • Mozilla Add-on Store: Usually 1-2 days.
    • Microsoft Edge Add-ons: Initial reviews may take 4-7 days.

Once the initial build has been successfully published for review, proceed with gathering the required credentials to set up the automations.

Accumulating Credentials

To automate deploying your web extension to multiple browser web stores, we need API keys and some store-specific metadata for each store's APIs. Collect these credentials as follows:

  • Add them to your Repository Secrets as you go: You can find the Actions Secrets section in your GitHub repository settings under github.com/<user>/<repo>/settings/secrets/actions. Add all the collected keys and metadata to the Repository secrets under the Secrets tab.

Chrome Web Store Credentials

To publish on the Chrome Web Store, you’ll need:

  • Extension ID: Available on your extension's product page. Open the Chrome Web Store, find your extension, and view its URL. The ID is the long string of characters at the end of the URL.
  • API Credentials: Set up through the Chrome Web Store API in Google Cloud. Follow these instructions to obtain:
    • CHROME_CLIENT_ID
    • CHROME_CLIENT_SECRET
    • CHROME_REFRESH_TOKEN If you have trouble getting the Refresh Token, give it some time and try again. It can take an hour or so after setting up access to the Chrome Web Store API before it works in some cases.

Microsoft Edge Web Store Credentials

To automate publishing to the Microsoft Edge Add-ons store, use the Edge Add-ons REST API:

  1. Sign in to the Partner Center at partner.microsoft.com.
  2. Visit the Publish API page.
  3. Click Create API credentials to generate:
    • EDGE_CLIENT_ID
    • EDGE_CLIENT_SECRET
    • ACCESS_TOKEN_URL Additionally, you’ll need the Product ID:
  4. Navigate to the Edge Overview page.
  5. Click on your extension to find the Product ID.

Firefox Add-on Store Credentials

For Firefox, use webext, the preferred tool for automated deployments:

  1. Log in to addons.mozilla.org.
  2. Go to Tools > Manage API Keys.
  3. Agree to the terms and retrieve your Issuer ID and Secret:
    • FIREFOX_ISSUER
    • FIREFOX_SECRET Once you are done collecting all of the necessary keys and metadata, your Repository Secrets should look similar to this:

correctly configured api keys

Building the Github Actions Workflow

Now that we have accumulated all of the information we need to use our publishing APIs, it is time to build the workflow. First, create a new file in your repository's .github/workflows directory with a name such as release.yml.
Let's review the workflow plan before we get started.

### Workflow Summary

The workflow will be triggered upon Release of the project. This action implies that a tag and release have been created for a specific commit, followed by publishing the release.

Note: You may choose alternative methods to release your extension. If you do, ensure to skip the section regarding the automatic upload of build artifacts to the release after publication.

Advantages of this approach include:

  • Automatically generated change logs for each release, based on GitHub's git history.
  • Ability to keep certain versions of the extension available on the release page for future use, such as debugging older versions or providing transparency to users.

In detail, the workflow will perform the following tasks:

  • Run upon a published GitHub release
  • Set up a build environment
  • Checkout code and install dependencies
  • Create builds for each target browser
  • Publish builds to their respective webstores
  • Upload build artifacts to the latest GitHub release (if applicable)

Configure the Workflow to Run on a Published Release

Let's get started by giving our workflow a name and a hook.

# Used by the Github Actions runner to describe the workflow
name: Upload Release Build
# Configure the workflow to run when a new release has been published
on:
release:
    types: [published]
Enter fullscreen mode Exit fullscreen mode

Setup Build Environment

To ensure a smooth build process, let's set up our environment and run the builds for each browser:

# Define a job named "release" that runs on an Ubuntu image
jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      # Checkout the latest code from the repository
      - name: Checkout code
        uses: actions/checkout@v4

      # Install Node.js
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      # Create a directory for the builds
      - name: Create directory
        run: cd ..; mkdir ./builds

      # Zip the source code (only needed if publishing to Firefox)
      - name: Zip Source Code
        run: zip -r ../builds/Source.zip .

      # Install project dependencies
      - name: Install Dependencies
        run: npm ci
Enter fullscreen mode Exit fullscreen mode

Run Targeted Builds

Depending on the bundler or framework being used, production builds will be triggered via your package file scripts. Since this example utilizes Extension.js, I've included the following build scripts:

{
  "scripts": {
    "build:ci:chrome": "extension build --browser chrome --polyfill",
    "build:ci:firefox": "extension build --browser firefox --polyfill",
    "build:ci:edge": "extension build --browser edge --polyfill"
  }
}
Enter fullscreen mode Exit fullscreen mode

Upload Bundles and Publish

Here's where the magic happens! We'll run through each target browser, automatically deploying to their respective webstores that have been configured.

Automate Release to the Firefox Add-on Store

This section will sign, upload, and publish our extension artifacts to the Firefox Add-on Store. You'll need to be sure to have web-ext as a devDependency in your package file. You can install it with npm install -D web-ext.

# Sign & Upload Firefox artifacts
- name: Sign and Publish artifact
continue-on-error: true
env:
  FIREFOX_ISSUER: ${{ secrets.FIREFOX_ISSUER }}
  FIREFOX_SECRET: ${{ secrets.FIREFOX_SECRET }}
run: npx web-ext sign --channel listed -s ./dist/firefox/ --upload-source-code ../builds/Source.zip --artifacts-dir ./dist/firefox --api-key $FIREFOX_ISSUER --api-secret $FIREFOX_SECRET
# Move the artifact to a more appropriate name
- name: Move artifact
run: mv ./dist/firefox/<your-app>-*.xpi ./builds/FirefoxExtension.xpi
# Upload the Firefox extension to the release
- name: Upload FirefoxExtension to release
uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
with:
  name: FirefoxExtension.xpi
  path: ./builds/FirefoxExtension.xpi
  repo-token: ${{ secrets.GITHUB_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

Automate Release to the Chrome WebStore

This section will package, upload, and publish our extension artifacts to the Google Chrome Web Store

# Zip the Chrome artifacts
- name: Zip Chrome Artifacts
run: cd ./dist/chrome ; zip -r ../../builds/ChromeExtension.zip *
# Upload the Chrome extension to the release
- name: Upload ChromeExtension to release
uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
with:
  name: ChromeExtension.zip
  path: ./builds/ChromeExtension.zip
  repo-token: ${{ secrets.GITHUB_TOKEN }}
# Publish the extension to the Chrome Web Store
- name: Publish to Chrome Web Store
continue-on-error: true
uses: wdzeng/chrome-extension@v1
with:
  client-id: ${{ secrets.CHROME_CLIENT_ID }}
  client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
  refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
  zip-path: './builds/ChromeExtension.zip'
  extension-id: <your-chrome-extension-id>
Enter fullscreen mode Exit fullscreen mode

Automate Release to the Edge Add-on Store

This section will package, upload, and publish our extension artifacts to the Microsoft Edge Add-on Store

# Upload Edge artifacts
- name: Zip Edge Artifacts
run: cd ./dist/edge ; zip -r ../../builds/EdgeExtension.zip *
# Upload the Edge extension to the release
- name: Upload EdgeExtension to release
uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
with:
  name: EdgeExtension.zip
  path: ./builds/EdgeExtension.zip
  repo-token: ${{ secrets.GITHUB_TOKEN }}
# Publish the extension to the Edge Addon Store
- name: Publish to Edge Addon Store
continue-on-error: true
uses: wdzeng/edge-addon@v2
with:
  product-id: <your-edge-addon-id>
  zip-path: ./builds/EdgeExtension.zip
  api-key: ${{ secrets.EDGE_CLIENT_SECRET }}
  client-id: ${{ secrets.EDGE_CLIENT_ID }}
Enter fullscreen mode Exit fullscreen mode

Test the Workflow

Testing your new GitHub Actions workflow is essential to ensure it functions as intended and avoids any issues upon deployment. Here's an easy-to-follow guide on testing your workflows locally before pushing them to the main repository:

  1. Create a Test Branch: Start by creating a separate branch, ideally named workflow-test, to isolate any changes related to the workflow from your production code. This ensures that any potential issues will not impact your live application.

  2. Add your Modifications: Commit the new release.yml file to your test branch and push it to the GitHub remote branch.

  3. Enable Local Runs: Navigate to your repository's settings page, select Code in the left-hand menu, click on Actions, then click on General. Scroll down to the "Local environment" section, and enable "Allow local runs". This enables you to run your workflow locally.

  4. Run Workflow Locally: You can now execute the workflow locally by using GitHub CLI or running the workflow file in your local GitHub Actions runner.

  5. Validate Test Results: Review the output from the locally executed workflow to ensure it behaves as intended. Address any issues or errors that may have arisen during testing before merging your changes into the main branch.

Remember to be patient! I almost always run into small configuration issues the first few times I write a new pipeline. If you run into any issues, feel free to mention me on GitHub and I'll try to help in any way I can.
If you still need some more help, you can checkout a simple working example from the LinkedZen extension on my GitHub.

Wrapping Up

Once everything is configured, your pipeline should look something like this:

name: Upload Release Build

on:
  release:
    types: [published]

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      # Checkout the code
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '18'
      - name: Create directory
        run: cd ..; mkdir ./builds

      # Zip the source code
      - name: Zip Source
        run: zip -r ../builds/Source.zip *

      # Install dependencies
      - name: Install dependencies
        run: npm ci

      # Create build directory
      - name: Create build directory
        run: mkdir ./builds

      # Build for Firefox, Chrome and Edge
      - name: Build for Firefox
        run: npm run build:ci:firefox
      - name: Build for Chrome
        run: npm run build:ci:chrome
      - name: Build for Edge
        run: npm run build:ci:edge

      # Sign & Upload Firefox artifacts
      - name: Sign and Publish artifact
        continue-on-error: true
        env:
          FIREFOX_ISSUER: ${{ secrets.FIREFOX_ISSUER }}
          FIREFOX_SECRET: ${{ secrets.FIREFOX_SECRET }}
        run: npx web-ext sign --channel listed -s ./dist/firefox/ --upload-source-code  ../builds/Source.zip --artifacts-dir ./dist/firefox --api-key $FIREFOX_ISSUER --api-secret $FIREFOX_SECRET
      - name: Move artifact
        run: mv ./dist/firefox/<your-extension>-*.xpi ./builds/FirefoxExtension.xpi
      - name: Upload FirefoxExtension to release
        uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
        with:
          name: FirefoxExtension.xpi
          path: ./builds/FirefoxExtension.xpi
          repo-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Zip Chrome Artifacts
        run: cd ./dist/chrome ; zip -r ../../builds/ChromeExtension.zip *
      - name: Upload ChromeExtension to release
        uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
        with:
          name: ChromeExtension.zip
          path: ./builds/ChromeExtension.zip
          repo-token: ${{ secrets.GITHUB_TOKEN }}
      - name: Publish to Chrome Web Store
        continue-on-error: true
        uses: wdzeng/chrome-extension@v1
        with:
          client-id: ${{ secrets.CHROME_CLIENT_ID }}
          client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
          refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
          zip-path: ./builds/ChromeExtension.zip
          extension-id: <your chrome extensions id>

      # Upload Edge artifacts
      - name: Zip Edge Artifacts
        run: cd ./dist/edge ; zip -r ../../builds/EdgeExtension.zip *
      - name: Upload EdgeExtension to release
        uses: Shopify/upload-to-release@07611424e04f1475ddf550e1c0dd650b867d5467
        with:
          name: EdgeExtension.zip
          path: ./builds/EdgeExtension.zip
          repo-token: ${{ secrets.GITHUB_TOKEN }}
      - name: Publish to Edge Addon Store
        continue-on-error: true
        uses: wdzeng/edge-addon@v2
        with:
          product-id: <your edge product id>
          zip-path: ./builds/EdgeExtension.zip
          api-key: ${{ secrets.EDGE_CLIENT_SECRET }}
          client-id: ${{ secrets.EDGE_CLIENT_ID }}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)