Building Cross Browser Web Extensions in 2023 - The Ultimate Guide
At Stateful I recently worked and launched a web extension that will help the company to bring Runme Notebook features into the browser. In this blog post I would like to share my learnings building a cross browser web extension.
The original version of this blog post was posted on the Stateful blog.
The initial extension release currently adds a “Run with Runme” button to every repository and will help you to check out these projects and get up and running quickly. You can find it in the Chrome Web Store and for Firefox.
I started based on a template from Akos Kemives and updated it based on current developments and new APIs that landed in the browser since the creation of the original template. You can find our current maintained Web Extension Starter Kit on GitHub which can be your starting point for your extension.
Let’s have a look at how web extensions are set-up.
Web Extension 1 on 1
- The manifest: Located in the extensions root directory as manifest.json, this file defines important metadata information and resources, declares permissions, and identifies which files to run in the background and on the page.
- The service worker: The extension service worker handles and listens for browser events. There are many types of events, such as navigating to a new page, removing a bookmark, or closing a tab. It can use all the browser APIs, but it cannot interact directly with the content of web pages; that’s the job of content scripts.
- The popup and other pages: An extension can include various HTML files, such as a popup, an options page, and other HTML pages. All these pages have access to Chrome APIs.
We will find all these components in our starter kit. You can remove them as you wish based on what your web extension is supposed to do.
After you checked out the starter-kit repository using Runme, go ahead and install the dependencies through the Runme notebook. Next you can start the Chrome or Firefox browser with the extension loaded. You can make changes to the background, content or popup scripts and have them apply to the browser by reloading the extension, e.g. in Chrome on the
The demo extension showcases how the popup modal or a content script can make a request to an API through the same interface. It uses a polyfill package to ensure that the behavior is equal between Chrome and Firefox. Unfortunately this is currently still required as the web standard for web extension shapes out.
The extension UI used in the injected content script as well as in the popup modal uses React with TailwindCSS. If your extension heavily relies on injecting UI elements into a page, using a web-component would be the ideal choice as styling such an element in isolation assures that page styles don’t apply to the component. However browsers currently don’t have great support for web-components in content scripts which makes React with scoped TailwindCSS classes the best alternative solution.
You will notice that the project defines two separate
manifest.json files. One that specifies a web extension according to the manifest version v3 and one with v2. This is required because Firefox currently does not support
web_worker as background script. It overall seems that Firefox’s implementation of the manifest v3 API is way behind Chrome’s implementation which makes such setups necessary.
The project has two different types of tests: UI component tests for our pop-up and content-script elements that get injected into the website as well as an end to end test that verifies whether the context script was actually injected properly into the website.
You can run the tests by calling
npm test in your terminal or just pressing the Play Button in the Runme notebook view.
While the component test mock the interaction between UI component and backend script, the e2e tests actually validate the whole communication process between these interfaces. The
wdio.e2e.conf.ts file that defines the e2e test run has a custom command defined called
openExtensionPopup which finds your extension id and opens the popup window within a page frame to validate that the component behaves in the popup modal as expected.
In order to release our extension to Google’s Web Store and Mozilla’s Addon platform (AMO) you can set-up a release action that automatically bundles and publishes it to these platforms. Make sure you create a developer account for both of them which in case of Google’s Web Store will cost you $5. They say the fee is to create better safeguards against fraudulent extensions in the gallery and limit the activity of malicious developer accounts.
For both platforms there are useful utility packages to manage extensions and allow publishing them from the command line or CI/CD:
- Chrome Web Store: use the fregante/chrome-webstore-upload package that can be used from the command line as well as within e.g. a GitHub Action. It comes with well written docs that also explain how to obtain all necessary credentials for publishing
- Mozilla Addon: the Mozilla team maintains a handy command line tool for building, testing and publishing extensions called web-ext
You can find a ready to use Github workflow in the starter kit template that helps you to create new releases and have them automatically published to these platforms.
When I began working on this project, I was filled with anticipation to witness the extent to which the standardization of building cross-platform browser extensions has progressed. Unfortunately it turned out that the development of the proposed APIs is far behind from what I would expect to be cross platform compatible. It seems that Chrome is ahead in the development of mentioned APIs compared to other browsers.
We intentionally left out Apple’s Safari completely because it seems to be most behind of all APIs and given its market share in the desktop space, which we believe our target audience for Runme to be in, it would not justify the effort.
After-all the biggest learning is that everyone attempting to build cross platform web extensions for browsers should be aware that there will be compatibility issues and workarounds needed to get to a satisfactory end result. While browser vendors are in process to standardize developing such extensions, more years will have to pass by until this experience becomes truly enjoyable.
We hope our starter kit will provide folks a good foundation to start building their extension with some useful scripts and commands that improve the developing experience.
Thanks for reading!