Mastering Browser Extensions: A Step-by-Step Guide with React.js

Browser extensions enhance web experiences by adding new functionality, and

with React.js, building modern, dynamic UI inside them is more seamless for a stronger expression.

πŸ—οΈ Architecture Overview

A React-powered browser extension typically has these components:

  1. Manifest file (manifest.json)

  2. Background Script – Handles events, persistent logic

  3. Content Script – Injects code/UI into the active webpage

  4. Popup / Options Page – React-based UI

  5. React App – Built with Vite or CRA and bundled for use in the extension

πŸ” Flow Diagram

Here’s a simplified data and control flow:

Generated image

🧱 Folder Structure

pgsqlCopyEditmy-extension/
β”‚
β”œβ”€β”€ public/
β”‚   └── manifest.json
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ background.ts
β”‚   β”œβ”€β”€ content.ts
β”‚   β”œβ”€β”€ popup/
β”‚   β”‚   └── Popup.tsx
β”‚   └── App.tsx
β”œβ”€β”€ package.json
β”œβ”€β”€ vite.config.js
└── tsconfig.json

πŸ§ͺ Sample Code

manifest.json (v3)

{
  "manifest_version": 3,
  "name": "React Extension Example",
  "version": "1.0",
  "action": {
    "default_popup": "popup.html"
  },
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ],
  "permissions": ["storage", "tabs", "scripting"]
}

React Popup: popup/Popup.tsx

import React from 'react';

export default function Popup() {
  return (
    <div style={{ padding: 10 }}>
      <h2>πŸ”§ Extension Popup</h2>
      <button onClick={() => alert('Clicked!')}>Click Me</button>
    </div>
  );
}

βœ… src/App.tsx

tsxCopyEditimport React from 'react';

function App() {
  const handleClick = () => {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
      const tabId = tabs[0]?.id;
      if (tabId) {
        chrome.scripting.executeScript({
          target: { tabId },
          func: () => alert('Hello from ByteForge UI!'),
        });
      }
    });
  };

  return (
    <div style={styles.container}>
      <h1 style={styles.heading}>πŸ”§ ByteForge UI</h1>
      <p style={styles.description}>Your React-powered browser extension is running!</p>
      <button onClick={handleClick} style={styles.button}>
        Inject Alert
      </button>
    </div>
  );
}

const styles = {
  container: {
    padding: '20px',
    width: '300px',
    fontFamily: 'sans-serif',
    backgroundColor: '#1e1e1e',
    color: '#fff',
    borderRadius: '8px',
  } as React.CSSProperties,
  heading: {
    margin: 0,
    fontSize: '1.5rem',
  },
  description: {
    fontSize: '0.9rem',
    margin: '10px 0',
  },
  button: {
    backgroundColor: '#3B82F6',
    color: '#fff',
    border: 'none',
    borderRadius: '5px',
    padding: '10px 12px',
    cursor: 'pointer',
  },
};

export default App;

Background: background.ts

chrome.runtime.onInstalled.addListener(() => {
  console.log('Extension Installed');
});

πŸ“¦ package.json

{
  "name": "react-browser-extension",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@types/chrome": "^0.0.261",
    "@types/react": "^18.2.14",
    "@types/react-dom": "^18.2.7",
    "typescript": "^5.2.2",
    "vite": "^5.0.0",
    "vite-plugin-crx": "^0.5.0",
    "vite-plugin-react": "^3.1.0"
  }
}

βš™οΈ vite.config.js

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { crx } from 'vite-plugin-crx';
import manifest from './public/manifest.json';

export default defineConfig({
  plugins: [
    react(),
    crx({ manifest })
  ],
  build: {
    rollupOptions: {
      input: {
        popup: 'src/popup/index.html',
        content: 'src/content.ts',
        background: 'src/background.ts',
      },
    }
  }
});

☝️ Replace input paths with your actual file structure.

πŸ”§ tsconfig.json

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "module": "ESNext",
    "moduleResolution": "Node",
    "strict": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": ["src"]
}

πŸ› οΈ Build the Project

In your terminal:

npm install
npm run build

This will generate a dist/ folder with all compiled assets.


🧩 Add Extension to Chrome

  1. Open Chrome and go to chrome://extensions/

  2. Turn on Developer Mode (top-right)

  3. Click "Load unpacked"

  4. Select the dist/ folder generated by Vite

Your extension should now appear in the Chrome extension toolbar!

🧠 Pros & Cons

ProsCons
βœ… React makes UI development fast❌ More build steps than plain JS
βœ… Hot reloading with Vite❌ Needs bundling and config setup
βœ… Component reuse across popup/options❌ Some APIs are not React-friendly
βœ… Can integrate with Redux/Context API❌ Browser API limitations

🌐 Browser Support Table

FeatureChromeFirefoxEdgeSafari
Manifest v3βœ…βœ…*βœ…βŒ
React (via bundler)βœ…βœ…βœ…βœ…
Service Workers in bgβœ…βœ…βœ…βŒ
Content Scriptsβœ…βœ…βœ…βœ…
Declarative Net Requestβœ…πŸ”Άβœ…βŒ

πŸ”Ά = Partial or under development
βœ… = Fully Supported
❌ = Not Supported


Thank You

Please comment and share if there is any issue/improvement and please motivate me by like share this. I will come with more interesting simple ByteForge UI

0
Subscribe to my newsletter

Read articles from Chitta Ranjan Mohapatra directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Chitta Ranjan Mohapatra
Chitta Ranjan Mohapatra