How to inject a React app into a Chrome Extension as a content script

A Chrome Extension with a React app injected as content script

Chrome Extensions are great tools that give let you interact with the browser and websites in a variety of ways.

  • Browser and Page actions which appear as icons in the browser toolbar and can show an optional popup window.
  • New Tab pages which override the default new tab
  • Options pages for configuring Chrome Extensions
  • Chrome context menus which appear with a right click
  • Notifications, which slide in at the top right of the screen
  • Content scripts, which inject JavaScript into a web page and can alter its user interface

This article is about loading a React app in a content script. Install create-react-app first if you don’t have it. Here is the repository if you just want to see the code.

Create a new React app

  • create-react-app react-content-script

Update the manifest file

  • Find it in /public/manifest.json
/public/manifest.json

The manifest.json file is required to provide important information for the Chrome Extension. The manifest_version, name, and version are required for the Chrome Extension to be valid. The content_scripts array is what we will inject our React app into https://www.google.com/.

You can inject a content script into a specific page as we are doing here or you can use the match key to specify the urls that you want your code to load in. For example ["<all_urls>"] loads the content script matches any valid url scheme.

Update the App component

Remove import logo from './logo.svg'; and replace the src for the image tag with "https://facebook.github.io/react/img/logo.svg".

/src/App.js

Update App.css to be more presentable.

/src/App.css

Append the app to the DOM

/src/index.js

This is where we actually inject the content script into the DOM on https://www.google.com. First examine the DOM of the website (or websites) that you want to inject a content script into. Then select an element based on where you want you want to append your code to the DOM.

I chose the <div class="ctr-p" id="viewport">, but you might have to experiment to find what works best for you. You might not even need to inject any HTML at all.

Selecting where to inject the content script

Deploy

  • Next build the app for production by running the command yarn run build form /package.json.
  • Open the Extensions tab under more tools in the Customize and control Google Chrome menu (with the three vertical dots at the far right of the browser toolbar) or navigate to chrome://extensions.
  • Check the Developer mode checkbox.
  • Click on Load unpacked extension… then find and select the build folder in react-content-script/src/ to load the extension.
content script load error

The extension failed to load because the "css" and "js" filenames we specified in the manifest are different than the ones generated by Webpack.

Webpack generated CSS and JS filenames

Modify the build output filenames

You could just rename main.css and main.js every time there are changes in your build e.g. main.cacbacc7.css and main.f0a6875b.js in this case, but a better solution is to modify the Webpack production configuration output filenames so you don’t have to update the manually when you want to load your extension. In order to access /config/webpack.config/prod, you have to eject from Create React App.

  • Eject from Create React App with yarn run eject and type y to confirm.
  • Remove the generated hashes from the the CSS and JavaScript output filenames. Compare ORIGINAL to UPDATED in /config/webpack.config/prod.
// ... (Other configuration)
// Note: defined here because it will be used more than once.
// ORIGINAL // const cssFilename = 'static/css/[name].[contenthash:8].css';
// UPDATED
const cssFilename = 'static/css/[name].css';
// ... (Other configuration)
output: {
// The build folder.
path: paths.appBuild,
// Generated JS file names (with nested folders).
// There will be one main bundle, and one file per asynchronous chunk.
// We don't currently advertise code splitting but Webpack supports it.
// ORIGINAL // filename: 'static/js/[name].[chunkhash:8].js',
// UPDATED
filename: 'static/js/[name].js',
// ... (Other configuration)

Rebuild and reload and verify that the filenames are correct before loading the build into Chrome.

React app injected into Chrome Extension content script

That’s it. Thanks for reading and let me know if you have any questions.