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:
Manifest file (
manifest.json
)Background Script β Handles events, persistent logic
Content Script β Injects code/UI into the active webpage
Popup / Options Page β React-based UI
React App β Built with Vite or CRA and bundled for use in the extension
π Flow Diagram
Hereβs a simplified data and control flow:
π§± 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
Open Chrome and go to
chrome://extensions/
Turn on Developer Mode (top-right)
Click "Load unpacked"
Select the
dist/
folder generated by Vite
Your extension should now appear in the Chrome extension toolbar!
π§ Pros & Cons
Pros | Cons |
β 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
Feature | Chrome | Firefox | Edge | Safari |
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
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
