Thomas Guibert
Jul 21 · 5 min read

In this article, we are going to create a Chrome extension with TypeScript. We will be using React for the popup.

If you are used to creating Chrome extensions, you don’t have to read this, you can simply get the code. I wanted to expose a few problems I went through while creating this starter project.

It will be easier to understand this article if you have at least a basic knowledge of building apps with webpack and TypeScript, and if you have previously created a Chrome extension.

🚧 Create the Foundations

Open your terminal on the folder you want to use and init your project with the following command:

$ npm init -y

Create the first folders and files:

$ mkdir src && cd src && mkdir assets background contentscript
$ cd background && touch background.ts
$ cd ../contentscript && touch contentscript.ts contentscript.scss

ℹ️If you don’t know what the background and contentscript are for, read manage events with background scripts and content scripts.

Install the first batch of dependencies:

$ npm i -D @types/chrome @types/node awesome-typescript-loader css-loader mini-css-extract-plugin node-sass sass-loader ts-node tslint typescript webpack@4.29.6 webpack-cli

⚠️Note that, at the time of writing, Create-react-app uses webpack@4.29.6 and we have to use the same webpack version to successfully compile our app later. (Feel free to install the last version of Webpack to reproduce the bug and try to find a solution)

📦 Set Up webpack

In the root directory, create tsconfig.json and webpack.config.js.

$ touch tsconfig.json webpack.config.js

Fill the files as below:

I won’t explain what we are doing here, it’s a simple webpack configuration and pretty much self-explanatory.

We are telling webpack to get our background.ts and contentscript.ts, to transpile it to JavaScript, and convert .scss files to .css.

Let’s create a script in our package.json to use this Webpack configuration.

Now, running npm run watch and npm run build should generate some files in the ./dist folder.

Great! So far, we created a development and a production mode for our background and content scripts.

📋 Adding Manifest.json

The manifest.json will tell Chrome what to do with all our files and define some capabilities of our extension.

Let’s create it in the root directory.

$ touch manifest.json

Fill it with this content:

The ./dist folder will be the folder that we will import to Chrome later.

It needs to always have a copy of the manifest.json. To do this, we are going to add three new scripts to our package.json:

  • The clean script will clean the ./dist folder and provide it with a fresh copy of our manifest.json. And, as we are here, the script will also copy our ./assets folder to the ./dist folder.
  • The prebuild and prewatch scripts will be called every time we run the watch or build script (by doing npm run ...) and before the actual script is executed.

As you can see, I’m using the cpy-cli package to copy files. Don’t forget to install it:

$ npm i -D cpy-cli

🎟 Import to Chrome

Let’s try to import our extension to Chrome.

Run the watch script (npm run watch) and when the code is compiled, open Chrome.

In Menu > More tools > Extensions, click on Load Unpacked and select our ./dist folder.

Your extension should be added to Chrome and if you’ve added some content to the background.ts or contentscript.ts files (just console.log some stuff), you should see them in their respective consoles.

💣 Create the React-Based Popup

We are going to use create-react-app for simplicity’s sake.

If you don’t have it, install the package. Go to your ./src folder and create-react-app.

⚠️Please, use popup as a name, we are going to use this name later.

$ npm i -g create-react-app && cd src && create-react-app popup --typescript

ℹ️The last version of this package allows us to give a -- typescript flag so we have a TypeScript-ready application.

We will encounter our first issue here.

We need to put the compiled React code in the ./dist folder of the root directory. We can do that easily, but if we have to use npm run build every time we change a line of code to see if it works, it will be pretty tedious.

We can’t really use npm run start either, because we don’t have access to generated files so we cannot put them in the ./dist folder.

To be able to compile and watch our files at the same time, we are going to use a package named cra-build-watch.

In your terminal, go to the root directory and install that package:

$ npm i -D cra-build-watch

Now, open the package.json of the newly created React app (not the one in the root directory).

Add the following script, as well as the homepage’s settings (to make sure all the asset paths are relative to index.html):

Once again, open the package.json file in the root directory and add the following script which will be used to watch the file’s modification of our React app.

ℹ️Notice the prefix flag that allows us to go to another directory (with another package.json).

If you did everything well and tested with npm run watch in the main directory, the script should now compile your background, your content script, your React’s popup, and put everything in the ./dist folder.

Add one last script to the package.json, that will allow us to build our React popup:

As well as building the app, it will copy it to the ./dist folder of the root directory.

We have to update our Chrome extension’s manifest.json to let it know we now have a popup:

Now, you can npm run build and add the extension to chrome as before.

Chrome Error

If you click on the extension’s icon, you will see a white square… And not our promised React popup.

Do a right-click on the white square, inspect, and open the developer console.

You will see this error:

Chrome refusing to execute inline script

That’s our last issue. What’s happening here?

Chrome blocks inline scripts in extensions. The build version of our react-app does have some inline script in the compiled index.html file.

As written in the console, one of the workarounds is to copy the SHA in this message and add it to the manifest.json’s “content_security_policy” setting as below:

npm run build again and reload your extension in chrome. If you click on the icon of your extension, you should see our React app loading as on this screen:

It works! 🎉

🎁 Zipping Your Extension

If you want to use a script to zip your extension, install the bestzip package and add the following script to your main package.json file:

$ npm i -D bestzip


That’s it for today! Thank you for reading, I hope you’ve learned some things. You now have a Chrome extension, ready to be used with TypeScript and React!

🏎️ Get the full code here.

Better Programming

Advice for programmers.

Thomas Guibert

Written by

❤️ JavaScript.

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade