How to create a Chrome extension in React JS

Creating a Chrome extension in React is no different than creating one in vanilla JavaScript. After all, it's all JavaScript in the end. But we just need to keep few details in our mind. In this post, let's see how to create a simple Chrome extension with React.

1. Creating and setting up a React application

For this, we are going to use create-react-app tool, as it makes it easy to get started with React. In your command line, go to your workspace directory and run npx create-react-app chrome-react-extension. This will setup a sample React application named "chrome-react-extension", with all the build steps built in.

Once you have the basic react app created, run yarn start to make sure the application is working fine. If all good, you will see a browser page, loaded with the React app.

Now, let's move on to configuring this app to make this as an Chrome extension.

2. Configuring React app to work as a Chrome extension

A Chrome extension requires a manifest.json file to be present in root directory of the extension. Luckily, our create-react-app already has one. We just need to add few details in it to make it compatible with Chrome's manifest.json spec.

Open the file [PROJECT_HOME]\public\manifest.json and add the following code to it. The file should already contain other entries and that's fine, just add the code at the end of it.

{
    /* ... other values */
    "version": "1.0",
    "manifest_version": 2,
    "browser_action": {
        "default_popup": "index.html"
    }
}

Also, the file will have some default entries for icons, but this is not compatible with the Chrome's manifest.json format. You can either update this to comply with "icons" format or just remove the entry as we do not need it for our extension to work.

And, that's it. That's all you need to do to get out React app working as a Chrome extension. Build this app as you normally build a react app with yarn build. This generates the app and place the files inside [PROJECT_HOME]\build.

Now, let's install it in Chrome.

3. Adding React app extension to Chrome

In Chrome, go to chrome://extensions page and switch on developer mode. This enables the ability to locally install a Chrome extension.

Chrome extension developer mode

Now either click on the LOAD UNPACKED and browse to [PROJECT_HOME]\build or just drag and drop the build folder. This will install the React app as a Chrome extension.

React app as Chrome extension

Yay! We just installed our React app as a Chrome extension. When you click the extension icon (the auto generated one), you will see the React app, rendered as a extension popup.

React app opened as Chrome extension

4. Making changes and debugging the extension

You can continue to make changes to your React app and do a build. To refresh the changes in your browser, just click on the Reload button on your extension's tile in chrome://extensions page. This will automatically pull changes from your build folder and re-install the extension.

Reload chrome extension changes

And, to debug the extension in browser, you can either right click the extension icon and select Inspect popup or once the popup is opened, right click in it and select Inspect. This wll open the developer console, where you can inspect console logs, network traffic, local storage, etc.

5. Injecting a React app to page as content script from Chrome extension

So far we used React to render the extension popup. For some extensions, you may also need to render some UI to any underlying page. Chrome extension uses content_scripts for this. Using content_scripts, you can mention a JS and CSS file in manifest.json, that needs to be injected` into the underlying page. Then this script will have access to the page DOM and can do the normal DOM manipulation.

But the problem with our create-react-setup is that the build step will generate the output JS file in different name each time (if the content changed). So we have no way to know the actual file name of the JS file, hence we can't mention in it in our manifest.json file.

To workaround this problem, you can always add a static JS file under the public folder and when built, this JS fill will be part of the build folder. Then you can use this file name as your content_script entry point. But then, this file will not be part of React / Webpack build pipeline so you either have to setup your own build or settle for a vanilla JS in here.

As another workaround, you can just eject out of create-react-app and modify the webpack configuration by hand to either retain the generated file name or create a separate entry point for content script.

Let's do this.

6. Ejecting create-react-app and configuring content scripts

First run yarn run eject on the command line. This will eject create-react-app and then will create all necessary build scripts inside your project folder. Once ejection is done, go to [PROJECT_HOME]\config\webpack.config.prod.js file and make the following changes in it:

Change the option entry to have multiple entry points. Here, our content script will be named as content.js.

entry: {
    app: [require.resolve('./polyfills'), paths.appIndexJs],
    content: [require.resolve('./polyfills'), './src/content.js']
},

Also, Search for .[contenthash:8] and remove it from both CSS and JS output file name. This will ensure the generated file will not have a random hash in it, thus we can mention the file name in our manifest JSON.

Once you made the above changes in the webpack.config.prod.js file, now its time to create the content script file. Create a file named content.js and content.css inside the src folder.

/* src/content.js */
import React from 'react';
import ReactDOM from 'react-dom';
import "./content.css";

class ContentReact extends React.Component {
    render() {
        return (
            <div className={'react-extension'}>
                <p>Hello From React Extension!</p>
            </div>
        )
    }
}

const app = document.createElement('div');
document.body.appendChild(app);
ReactDOM.render(<ContentReact />, app);
/* src/content.css */
.react-extension {
    position: absolute;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    display: grid;
    align-items: center;
    justify-items: center;
    background: rgba(0,0,0,.4);
}

.react-extension p {
    background-color: #fff;
    padding: 20px;
    border-radius: 5px;
}

Now that we have configured React build pipeline and created our content scripts, lets update manifest.json to pick up these files. Add the following code to manifest.json file.

"content_scripts" : [
    {
      "matches": ["https://reactjs.org/*"],
      "css": ["/static/css/content.css"],
      "js": ["/static/js/content.js"]
    }
  ]

Note that how we refer the JS and CSS file. Also, with matches option, we are injecting this content script only to the React JS website. But you can inject any website you want, just by adjusting the matching pattern.

Now build your app, go to chrome://extensions and reload the extension. After this, when you go to https://reactjs.org website you should see our extension injected model there.

Content Script injected as a React App

This brings the end of this article. Feel free to let me know if there are questions.