Setting up CI/CD for Web-browser extension with GitHub Actions
Hi there!
Last time I've told you how to debug web extensions with VS Code. This time
I gonna show you some tools which help you to automate your extension development workflow. I'm talking about
contiuous integration and delivery
So, what CI/CD exactly is?
Contiuous integration and delivery allows you to automate some parts of a development workflow like building, testing, packaging and distributing. So everything you need here is to push your code into your repository and the rest will be done by computer.
Cool, how can I opt-in?
The answer is easy: all you need is to set up your CI/CD pipeline. But actually, it't not that simple if you try to do it yourslef since there're a lot of underwater rocks. So that is why I'm doing this article
Today I'll cover set-ups of CI/CD pipelines for Chrome and Firefox extension webstores
Prerequisites
So, all you need right now is your WebExtension source code saved in GitHub repo. I will use for reference my latest Password generator extension. You can also check it out later to see how it is done there
Note that your project has to have at least one submission to selected stores to be automated
Workflow file setup
So, first thing we need to do is create our workflow file. These files contain instructions for computer on what and how it should integrate and deliver your extension. Usually WFs have a .yaml
file extension. So GitHub Actions WF is not an exclusion.
-
Navigate to you repository root folder and create a
.yaml
file on/.github/workflows/your_workflow_file_name.yaml
Name of the file can be anything you want. Path to the file cannot though
So, here I've created a file on the exact folder path (you shouldn't push your file for now though)
-
Next thing, we need to fill our file with base instructions which we will use no matter where we will publish our project
name: CI # Name of your workflow on: workflow_dispatch: # Allows you to manually trigger workflow push: # Triggers workflow only if code is pushed to master/main branch branches: [ master, main ] paths: # Triggers workflow only if manifest content is changed - 'manifest.json' jobs:
Here we've set some triggers: conditions in which pipeline will be started
Here our triggers are set to fire only if we changed extension manifest on master/main branch. We did it because of several reasons:
- We don't want to update our extension package if we just edited some typos in repo documentation
- We don't want our changes from development brach be published as a major update
- Everytime we submit new extension version we have to change its version in manifest, so every push to master/main we want to publish has to contain this change
Note that YAML file syntax is indetation-sensitive. Make sure you didn't mess with tabs and spaces. More information about workflow file syntax you can find here
-
Now we need to make sure we have GitHub Actions turned on for our repository
To do that we need to navigate to
Settings -> Actions
of the repo (https://github.com/%username%/%repository%/settings/actions
) and make sure we have checked anything butDisable Actions
Here ends general preparation of the workflow and now we will move to store-spesific actions
Setting up Firefox webstore submission
Obtain API credential
Since we deal with some sensitive data here, we need to make sure everything is secured and nobody can sabotage our release process. So we need to obtain some access keys which will authorize our pipelines.
-
First thing, we need to go to Firefox Add-in API management page, generate and copy JWT issuer value (that will be our client ID value) and JWT secret (client secret)
Note: do not share these values with anyone else! Especially, DO NOT put these values into your workflow file as a plain text!
-
Navigate to the repository's secrets page (
Settings -> Secrets
orhttps://github.com/%username%/%repository%/settings/secrets
) and create two new secrets:FIREFOX_API_KEY
- Paste here yourJWT issuer
tokenFIREFOX_CLIENT_SECRET
- Put yourJWT secret
token here
You can use any other names for your secrets variables
This is a safe storage for sensitive data. No one will be able to values stored here. Even you won't be able to see them
Update the workflow file
Now we need to add a new job to our workflow which will push our extension to the webstore
Open your workflow YAML file and add a new job:
jobs:
Firefox: # Job name
runs-on: ubuntu-latest # specify required OS for deployment machine
steps:
- uses: actions/checkout@v2 # Download your source code to the DM
- name: Build Extension for Firefox
id: web-ext-build
uses: kewisch/action-web-ext@v1
with:
cmd: build
- name: 'Sign & publish'
id: web-ext-sign
uses: kewisch/action-web-ext@v1
with:
cmd: sign
channel: listed
source: ${{ steps.web-ext-build.outputs.target }}
apiKey: ${{ secrets.FIREFOX_API_KEY }}
apiSecret: ${{ secrets.FIREFOX_CLIENT_SECRET }}
- name: Drop artifacts
uses: actions/upload-artifact@v2
with:
name: 'Firefox Artefacts'
path: ${{ steps.web-ext-sign.outputs.target }}
Here we have 4 tasks in our job:
- actions/checkout@v2 clones our source code to a deployment machine
- kewisch/action-web-ext@v1 with
cmd: build
packs our extension source code into XPI file which will be published to the webstore -
kewisch/action-web-ext@v1 with
cmd: sign
signs the package (confirms that this package is legit and derrived from official source) and publishes it to the webstoreHere we have to provide some parameters to publish our extension correctly:
cmd: sign
: The command to run. This command signs and submits our extension to Firefox servers for verificationsource
: path to our xpi package file. Here we use${{ steps.web-ext-build.outputs.target }}
since it is an environmental variable-
channel
: Once verification is complete, our extension will become available for everybody in the webstore (listed
) or it will become available for self-distribution and won't become visible thorugh the webstore (unlisted
)Leave it
channel: listed
apiKey
: our JWT issuer token required for legit publishing. We can use here our secret variables -${{ secrets.VARIABLE_NAME }}
. In our case it's${{ secrets.FIREFOX_API_KEY }}
apiSecret
: JWT secret token
- actions/upload-artifact@v2 drops our XPI file to workflow artifacts and makes it available for us to download for sideloading and testing purposes (optional task)
More info on workflow variables can be found on official GitHub documentation. If you click on links from actions list above, you can find out more about specific action and its documentation
Setting up Google Chrome webstore submission
Obtain API credential
For Chrome webstore a process of obtaining credential is a bit tricky and requires a lot of explanations. So, instead of making another article I just leave a link to a guideline which covers the case: https://xfox111.net/vtnlk8
All we need here is to obtain client ID
, client secret
and refresh token
. For more information what is it and how to live with it you cand find by googling a 'OAuth2' topic

Update the workflow file
Same as setting up Firefox workflow, we now need to add a new job to our pipeline:
jobs:
Chrome:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Pack extension
uses: TheDoctor0/zip-release@0.4.1
with:
filename: ./Package.zip
exclusions: '.git/* .vscode/* .github/* *.md'
- name: Publish to Chrome Webstore
uses: SebastienGllmt/chrome-addon@v3
with:
extension: yourextensionidhere
zip: ./Package.zip
client-id: ${{ secrets.CHROME_CLIENT_ID }}
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
- name: Drop artifacts
uses: actions/upload-artifact@v2
with:
name: 'Chrome Artifacts'
path: ./Package.zip
As you can see, there're some major differences between Firefox and Chrome jobs. Here's some key points:
- We don't have to pack our extension with special format. Everything we need is to create a zip archive of our file with mainfest in the root
- We don't have to sign our package. Everything will be done after submission
- We need to specify extension ID and provide a refresh token since we will publish our extension using "3d party" (technically, our) application
Everything else is pretty much the same as it is on the Firefox job, so I see no reason to stay on it anymore
More information about used actions you can find their marketplace pages:
- actions/checkout@v2
- TheDoctor0/zip-release@0.4.1
- SebastienGllmt/chrome-addon@v3
- actions/upload-artifact@v2
So, here's our complete workflow file:
name: CI # Name of your workflow | |
on: | |
workflow_dispatch: # Allows you to manually trigger workflow | |
push: | |
# Triggers workflow only if code is pushed to master/main branch | |
branches: [ master, main ] | |
paths: | |
# Triggers workflow only if manifest content is changed | |
- 'manifest.json' | |
jobs: | |
Firefox: # Job name | |
runs-on: ubuntu-latest # specify required OS for deployment machine | |
steps: | |
- uses: actions/checkout@v2 # Download your source code to the DM | |
- name: Build Extension for Firefox | |
id: web-ext-build | |
uses: kewisch/action-web-ext@v1 | |
with: | |
cmd: build | |
- name: 'Sign & publish' | |
id: web-ext-sign | |
uses: kewisch/action-web-ext@v1 | |
with: | |
cmd: sign | |
channel: listed | |
source: ${{ steps.web-ext-build.outputs.target }} | |
apiKey: ${{ secrets.FIREFOX_API_KEY }} | |
apiSecret: ${{ secrets.FIREFOX_CLIENT_SECRET }} | |
- name: Drop artifacts | |
uses: actions/upload-artifact@v2 | |
with: | |
name: 'Firefox Artefacts' | |
path: ${{ steps.web-ext-sign.outputs.target }} | |
Chrome: | |
runs-on: ubuntu-latest | |
steps: | |
- uses: actions/checkout@v2 | |
- name: Pack extension | |
uses: TheDoctor0/zip-release@0.4.1 | |
with: | |
filename: ./Package.zip | |
exclusions: '.git/* .vscode/* .github/* *.md' | |
- name: Publish to Chrome Webstore | |
uses: SebastienGllmt/chrome-addon@v3 | |
with: | |
extension: yourextensionidhere | |
zip: ./Package.zip | |
client-id: ${{ secrets.CHROME_CLIENT_ID }} | |
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }} | |
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }} | |
- name: Drop artifacts | |
uses: actions/upload-artifact@v2 | |
with: | |
name: 'Chrome Artifacts' | |
path: ./Package.zip |
So, that's it. All we have to do now is to push our updated workflow file to the repo and trigger our workflow and make sure everything is up and running
Wait, and how do I set up CI/CD for Safari/Microsoft Edge/etc. extensions?
You can't. Unfortunately, neither Apple nor Microsoft didn't make action tasks for automatic deployment of their browsers' extensions. And while it's understandable for the former one, since Safari has a different architecture of extensions and different submission processes, it's unclear for Microsoft not doing this, since new MS Edge is basically modified Google Chrome and it has same architecture and same submission processes as Chrome
Other browsers either aren't just popular enough or are based on different architecture
Conclusion
Hope this article will help you with your own project. If you still have any questions left, feel free to ask them in the comment section below. You can also leave a topic suggestion for my next article in the comment section as well
If you ❤ this, you can Buy Me a Coffee ☕ or follow me on Twitter 🗨. Thanks for your time ;)
- Cheers,
- XFox 🦊
Comments
Post a Comment