Logo

Cross-Browser Extensions Using React, Astro, Svelte, +more

Step-by-step guide to creating a web extension in React, Astro, Svelte, and Angular for Firefox, Chrome, Edge, Brave, Vivaldi, and Safari.

Rob Helmer

Rob Helmer

1/10/2025 · 4 min read

Tags: webextensions react astro svelte angular webdev


Web browser logos

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:

  1. React
  2. Astro
  3. Svelte
  4. Angular
  5. Testing
  6. Packaging and Publishing

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 to public/.
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

  1. Load Extensions in Browsers
    • Chrome: chrome://extensions
    • Firefox: about:debugging#/runtime/this-firefox
  2. 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.