Introduction
Creating web extensions that work seamlessly across multiple browsers can be challenging, especially when you want to use popular front-end frameworks like React, Astro, Svelte, and Angular. In this guide, you’ll learn how to create a simple web extension using each framework. The extension will display a popup with a customizable message.
PLEASE NOTE: I do not consider myself an expert in all of these frameworks! I have shipped Firefox and Chrome extensions using both Svelte and React, so I’m not a total newb, but just so you know. If I’m doing anything silly, please do contact me and let me know. My contact info is on the front page of my site.
I’ve created a GitHub Repository to test these examples, both this blog post and the repository are a work-in-progress but I’ve manually tested each. I will continue to flesh out the examples and add automated tests over time:
https://github.com/rhelmer/webext-framework-examples
Table of contents:
Shared Setup
Manifest File (manifest.json
)
{
"manifest_version": 3,
"name": "Framework Demo Extension",
"version": "1.0",
"description": "A simple web extension built using popular frameworks.",
"action": {
"default_popup": "index.html",
"default_icon": {
"16": "icon.png",
"48": "icon.png",
"128": "icon.png"
}
},
"host_permissions": ["<all_urls>"]
}
Folder Structure
project-root/
|-- react-extension/
|-- astro-extension/
|-- svelte-extension/
|-- angular-extension/
|-- shared/
|-- manifest.json
|-- icon.png
1. React
Commands
# NOTE: this installs React 19. If you want 18, try: `npx create-react-app react-extension`
npm create vite@latest react-extension --template react
cd react-extension
npm install --save-dev webextension-polyfill @types/webextension-polyfill
Key File Adjustments
- Add
manifest.json
topublic/
.
cp ../shared/manifest.json public
- Create a
src/Popup.tsx
for the popup component:
import browser from "webextension-polyfill";
class PopUp extends React.Component {
handleClick() {
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/create
browser.tabs.create("about:blank");
}
render() {
return (
<>
<p>Hello world!</p>
<button type="button" onClick={handleClick}>Create Tab</button>
</>
);
}
}
export default PopUp;
- Modify
src/main.tsx
:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import PopUp from './PopUp'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<PopUp />
</StrictMode>,
)
Build
npm run build
cp ../shared/{manifest.json,icon.png} ./dist
Test
cd dist
npx web-ext run
# Or for Chrome: `npx web-ext run -t chromium`
2. Astro
Commands
npm create astro@latest astro-extension
cd astro-extension
npm install --save-dev webextension-polyfill @types/webextension-polyfill
Key File Adjustments
- Create a
./src/components/Popup.astro
component:
<script>
import browser from "webextension-polyfill";
const button = document.getElementById("newtab");
button?.addEventListener("click", () => {
browser.tabs.create({ url: "about:blank" });
});
</script>
<p>Hello world!</p>
<button id="newtab">Open New Tab</button>
- Modify
./src/pages/index.astro
:
---
import PopUp from "../components/Popup.astro";
---
<html>
<head>
<title>Popup</title>
</head>
<body>
<PopUp />
</body>
</html>
Build
npm run build
cp ./shared/{manifest.json,icon.png} ./dist
Test
cd dist
npx web-ext run
# Or for Chrome: `npx web-ext run -t chromium`
3. Svelte
Commands
npx degit sveltejs/template svelte-extension
cd svelte-extension
npm install --save-dev webextension-polyfill
Key File Adjustments
- Create a
./src/Popup.svelte
:
<script>
import browser from "webextension-polyfill";
let message = "Hello from Svelte!";
</script>
<h1>{message}</h1>
- Modify
./src/main.js
:
import App from './Popup.svelte';
// TODO: call the `browser.*` API
const app = new App({
target: document.body,
});
export default app;
Build
npm run build
cp ../shared/{manifest.json,icon.png} ./dist
Test
cd dist
npx web-ext run
# Or for Chrome: `npx web-ext run -t chromium`
4. Angular
Commands
npm install -g @angular/cli
ng new angular-extension --defaults --style=scss
cd angular-extension
npm i --save-dev @types/webextension-polyfill webextension-polyfill
Key File Adjustments
- Create a
./src/app/popup.component.ts
with:
import { Component } from '@angular/core';
import browser from 'webextension-polyfill';
@Component({
selector: 'app-popup',
template: `<h1>{{message}}</h1>`
})
export class PopupComponent {
message = 'Hello from Angular!';
// TODO: call the `browser.*` API
}
Build
npm run build
cp ../shared/{manifest.json,icon.png} dist/angular-extension
Test
cd dist/angular-extension
npx web-ext run
# Or for Chrome: `npx web-ext run -t chromium`
Testing
- Load Extensions in Browsers
- Chrome:
chrome://extensions
- Firefox:
about:debugging#/runtime/this-firefox
- Chrome:
- Test Popup Functionality
Click on the puzzle-piece icon in the toolbar and click the “Framework Demo Extension”
Packaging and Publishing
Produce unsigned .zip file for upload to extension stores
npx web-ext build
Chrome Web Store
Chrome extensions are installed from the Chrome Web Store, and must be hosted there as well.
Firefox Add-ons
Firefox extensions are installed from Addons.Mozilla.Org aka AMO. They may be self-hosted, but must be uploaded to and signed by AMO first. Hosting on AMO is easiest (and the default).
Safari Extensions
Safari extensions use the normal Apple App Store, and are packaged as macOS/iOS/iPadOS/etc. apps.
Distributing your Safari web extension
Edge
Extensions must use Edge Add-ons.
Vivaldi Extensions
Vivaldi supports extensions from the Chrome Web Store (source).
Brave
Brave supports extensions from the Chrome Web Store (source).
Opera
Extensions must use the Opera addons store.
Conclusion
This guide demonstrates how to build a basic web extension using popular frameworks, ensuring compatibility across major browsers. With these setups, you can extend your favorite framework into the realm of web extensions.