Building a Chrome Extension with React
React can be a great choice for building a Chrome extension, especially when the extension includes a popup, settings page, or other interactive interface. Its reusable components and familiar development tools make it easier to create and maintain a polished user experience.
A Chrome extension is more than a small website, though. It runs inside Chrome’s extension environment, which has its own permissions, security rules, and communication patterns. Understanding those pieces early can make development smoother.
What Makes Up a Chrome Extension?
A typical extension built with React may include several parts:
- Popup: The small interface that opens when someone clicks the extension icon.
- Options page: A page for preferences or settings.
- Content script: A script that runs on web pages and can interact with their content.
- Service worker: A background script that handles events, messages, or other extension tasks.
- Manifest: A configuration file that tells Chrome how the extension works and what permissions it needs.
React is commonly used for the popup and options page. Content scripts and service workers can use JavaScript or TypeScript, and they do not usually need React unless they are responsible for rendering a user interface.
Start with Manifest V3
New Chrome extensions use Manifest V3. The manifest declares the extension’s name, version, entry points, and requested permissions. Here is a minimal example for an extension with a React-powered popup:
{
"manifest_version": 3,
"name": "React Extension",
"version": "1.0.0",
"description": "A simple Chrome extension built with React.",
"action": {
"default_popup": "index.html"
}
}
The popup file and its JavaScript bundle need to be included in the extension’s built output. A tool such as Vite can compile the React application and generate the files Chrome loads.
Creating the React Popup
The popup can be developed much like a small React application. For example, a simple component might look like this:
function App() {
return (
<main>
<h1>Hello from Chrome</h1>
<p>This interface is rendered with React.</p>
</main>
);
}
export default App;
Keep in mind that a popup closes when the user clicks outside it or switches focus. Avoid relying on the popup to perform long-running work. Save important state or delegate longer tasks to the extension’s service worker.
Set Up a Build Process
React source files cannot be loaded directly as an extension. They need to be bundled into browser-ready files. A typical workflow is:
- Create a React project with a bundler such as Vite.
- Configure the build to output the popup HTML and JavaScript into a distribution folder.
- Place the manifest and any required static assets in that folder.
- Build the project and load the resulting folder into Chrome for testing.
In Chrome, open chrome://extensions, turn on Developer mode, and select Load unpacked. Choose the extension’s built output folder. After changes are made, rebuild the project and reload the extension from the extensions page.
Use Permissions Carefully
Extensions must request permissions to access certain Chrome APIs or web pages. Only request what the extension actually needs. For instance, an extension that uses bookmarks may need the bookmarks permission, while an extension that runs on specific websites may need matching host permissions.
Smaller, more focused permission requests are easier for users to understand and can make an extension feel more trustworthy. Permissions should be documented clearly in the extension’s listing and interface.
Communicate Between Extension Parts
The popup, content scripts, and service worker run in separate contexts. When one part needs information from another, Chrome’s extension messaging APIs can pass messages between them. For example, a popup might ask a content script for information about the current page, or a content script might send an event to the service worker.
Keep messages structured and validate incoming data. Each part of the extension should handle the possibility that another part is unavailable or has already closed.
Keep Security in Mind
Chrome extensions have access to sensitive browser capabilities, so security matters. Avoid loading remote code, treat page content as untrusted input, and use safe rendering practices. Do not insert untrusted strings into the page as raw HTML. Store only the data the extension needs, and be transparent about how that data is used.
Test the Extension in Chrome
Testing should cover more than whether the React interface appears. Check how the extension behaves when the popup closes, when permissions are denied, and when a content script cannot run on a page. Also test the built extension, since development and production builds may behave differently.
Chrome’s extension management page provides access to errors and service worker inspection tools. These are useful for diagnosing issues that do not appear in the popup itself.
Conclusion
React provides a practical way to build responsive interfaces for Chrome extensions, while Chrome’s extension APIs handle browser-specific features such as tabs, storage, and messaging. With a clear project structure, a reliable build process, minimal permissions, and careful testing, a React Chrome extension can be both user-friendly and maintainable.
9 Essential Tips for Developing React Chrome Extensions
- Use Vite or another fast bundler for development.
- Set up a clear Manifest V3 configuration.
- Keep popup, options, and content scripts separate.
- Use message passing to connect extension components.
- Request only the permissions you need.
- Avoid direct DOM access from React components.
- Use storage APIs for persistent extension settings.
- Test in Chrome with unpacked extensions.
- Handle service worker suspension gracefully.
Use Vite or another fast bundler for development.
Use Vite or another fast bundler to streamline React Chrome extension development. A fast build and refresh cycle lets you see changes quickly, making it easier to test the popup, options page, and other interfaces as you work. Configure the bundler to output the files Chrome needs, including the extension’s HTML, JavaScript, assets, and manifest, then load the built folder as an unpacked extension in Chrome.
Set up a clear Manifest V3 configuration.
Set up a clear Manifest V3 configuration by defining your extension’s name, version, entry points, and permissions in a well-organized manifest.json file. Include only the permissions and host access your React Chrome extension actually needs, and make sure paths to the popup, service worker, content scripts, and other built files match your project’s output. A precise manifest makes the extension easier to maintain, test, and review—and helps users understand what access it requires.
Keep popup, options, and content scripts separate.
Keep your popup, options page, and content scripts separate so each part of your React Chrome extension has a clear responsibility. The popup should handle quick, short interactions, the options page should manage longer-term settings, and content scripts should work with web pages when needed. Separating these areas makes the code easier to organize, test, and maintain, while reducing the chance that changes to one part will cause problems in another.
Use message passing to connect extension components.
Use message passing to connect the different parts of a React Chrome extension, such as the popup, content scripts, and service worker. Because each runs in its own context, they can’t directly share state; Chrome’s messaging APIs let them send structured requests and responses instead. Keep messages focused, validate incoming data, and handle cases where the receiving component is unavailable so communication stays reliable.
Request only the permissions you need.
When building a React Chrome extension, request only the permissions it genuinely needs. Each permission gives the extension access to browser features or user data, so unnecessary requests can raise privacy concerns and make people less likely to install it. Keep permissions focused, explain their purpose clearly, and add new ones only when a feature requires them. This helps build user trust and keeps the extension’s access easier to review.
Avoid direct DOM access from React components.
When building a React Chrome extension, avoid directly accessing or changing the DOM from React components. Let React manage the interface through state, props, and event handlers so updates stay predictable and consistent with React’s rendering process. If the extension needs to interact with a web page, handle that work in a content script and communicate with the React interface using Chrome’s messaging APIs. This separation helps prevent conflicts, makes the code easier to maintain, and keeps extension behavior more reliable.
Use storage APIs for persistent extension settings.
Use Chrome’s storage APIs to save extension settings that should remain available after the popup closes or the browser restarts. For example, `chrome.storage.local` is useful for preferences stored on the user’s device, while `chrome.storage.sync` can sync supported settings across the user’s signed-in Chrome browsers. Read saved values when the React interface loads, and update storage whenever a setting changes. This is more reliable than keeping preferences only in component state, which disappears when the popup closes.
Test in Chrome with unpacked extensions.
Test your React Chrome extension directly in Chrome before publishing by loading it as an unpacked extension. First, build the project, then open chrome://extensions, turn on Developer mode, and select “Load unpacked” to choose your build folder. This lets you check the extension in its real environment, catch issues with the popup, permissions, or manifest, and quickly reload it after making changes.
Handle service worker suspension gracefully.
In a React Chrome extension, treat the Manifest V3 service worker as temporary: Chrome may suspend it when it is idle and start it again later, so don’t rely on in-memory variables to preserve important state. Save essential data with the `chrome.storage` API, register event listeners at the top level of the service worker, and make each event handler able to resume work independently. When the popup or a content script sends a message, handle cases where the worker has stopped or a response is delayed, and use clear error handling rather than assuming the worker stays active.