Creating Your First Chrome Extension

Building tools to improve your workflow

Ferenc Almasi
Nov 1 · 9 min read

One of the best advantages of being a developer is that you can create your own set of tools to optimize your workflow. Creating chrome extensions is no exception from this.

We all use some from our day to day basis — probably most of you reading this article with an adblocker installed — but some of us have special workflows for which we don’t have appropriate tools.

Creating them is not much hassle; You make it once, use it forever. The amount of time they can save is invaluable so I decided to give it a go and write a tutorial about it.

They are built using existing web technologies such as HTML, CSS and JavaScript which means you don’t need to learn anything new, apart from what are the key components in a Chrome extension.

What will be creating?

I’ve created some simple designs in Figma. The extension can be used to import ingredients from Tasty, which you can later use in your shopping list. I chose this example so we can see how to implement a popup with various UI elements with logic behind it. We will also have a look at how we can interact with the current page to do DOM manipulation through the use of content scripts. Last but not least, we will see how we can store information inside our extension for later use.

The extension will work in the following way: first we want to check if the user is on the correct website. In case they are not, we display a message to them to visit a recipe in order to start importing. If they are on the correct site, we present the user with an import button that will fetch the ingredients from the page and creates a checklist.

Getting started

To get started, create an empty directory which will hold all the required files for the extension. All extensions need to have a file. This is a configuration file that will hold information about our extension, such as the file used for the popup window, the scripts we are using for it or even the permissions we want to request for the extension.

We can get away using only four properties initially. At this stage, we can already import the extension to Chrome so we can check any further updates. To do so, navigate to and enable developer mode then click on the “load unpacked” button and select the folder of the extension to import it.

Enable Developer mode and select load unpacked to load your extension
Enable Developer mode and select load unpacked to load your extension

The extension is now installed and you should see a new icon appearing next to the address bar. Right at this stage it looks pretty boring. We have the name, the version and the description displayed, but we don’t have an icon, so let’s add that right now.

The extension shown in Google Chrome
The extension shown in Google Chrome

Create four different sized png icons: , , and . You can do it with the help of online tools or with your favourite design tool, I use Figma throughout this tutorial.

Create an folder for them and pull it into your project, so now you have a folder structure similar to the following:

The project folder structure
The project folder structure

To use the images in the extension, we can expand the file with an property, holding the references to them:

If you go back to the extensions tab and refresh the extension, you’ll see we have an icon now.

Adding the popup

With everything set up, let’s add the user interface to the extension. Create a file in the root of your project. Just as for the images, we need to reference it in the file:

This can be done by adding the property. The holds all of the UI elements displayed in the extension. Right at this stage it only includes a generic message for all sites:

This is all the HTML we will need. For the styles, I’ve created a folder and put all my rules into the file. The shopping list — which holds the name of the recipe, the import button and the ingredients — is hidden through CSS. Once we are on the right page, we will show them to the user. The recipe’s name and its ingredients are also empty as they will be populated through JavaScript.

Showing the import button

Right now, even if you are on, the same message is presented to the user. We want to hide the disclaimer and show the import button if the user is viewing a recipe.

To do so, we need to add a script to our file. Create an in your root directory and reference it inside , right before the closing of the body tag.

We’re going to be using the API. To have access to everything, we need to request a permission for it which we can do in our file. Expand it with the following line:

Now we can start writing out the functionality for showing the import button to the user:

We use the method of API which returns the currently active tab. If the tab URL matches the path we defined on line:2, we hide the disclaimer and show the shopping list to the user.

After you’ve made the changes, remember to refresh the extension to see the results.

Pulling in ingredients with content scripts

By clicking the button, we would like to get the list of ingredients from the page and populate the node inside our extension. As you can see from the code example above, the node represents the HTML of the extension and not the document of the page. To get around this, we will use content scripts.

Content scripts have access to the page’s DOM, but have no access to the extension’s HTML.

Extensions in Chrome are event based, so to make things work when the user clicks the import button, we need to:

  • Send a message to the content script telling it to fetch the ingredients
  • Get the ingredients from the DOM inside the content script
  • Send the response back to our
  • Populate the shopping list with the retrieved data

It may sound overly complicated at first but bear with me, each step can be done with only a couple of lines.

To add a content script, navigate back to your and extend it with the node:

This is how our file looks like. The property takes in an array of objects with two properties:

  • tells the extension where to inject the script into the page
  • tells the extension which file to inject into the page

Inside the property we can use regexes. With we tell it to match for every page that starts with . Inside the property, we are referencing so create that in your root directory.

Debugging content script through the Sources tab
Debugging content script through the Sources tab
To debug, change the “Page” on the left hand side to Content scripts under the Sources tab

If everything done correctly, you should see your content script loaded only for recipe pages inside Tasty. To debug content scripts, inspect the page and go to your “Sources” tab, then change “Page” on the left hand side to “Content scripts”. There you will see your extension.

Inside we are going to add a listener for the event when the user clicks the import button:

Later on, we are going to send a message which triggers `addListener`

To add the event listener we use . Inside its callback, we check if the request is coming from our app and if it does, we send back the name of the recipe alongside with the ingredients in an object.

To send this event, we need to go back to our file and add the event listener to our button.

Here we use with the id of the current tab and the same action we are listening for inside . After extending with the lines above, clicking on the import button should grant you the information we are longing for.

The received response from our content script
The received response from our content script

All that’s left to do for us is populating the DOM inside the extension with the information we got from our content script.

Populating the extension with the ingredients

Inside when the response comes back, we want to populate the extension with the title and the ingredients.

Starting with the title we can get it done using one single line:

Doing the ingredients will be a little bit more tricky but nothing impossible. Since we have multiple elements, we need to loop over them. We will also have checkboxes inside the list item so we need to create additional DOM elements as well.

Here we first get the list, we want query it only once before the loop. Next we loop over using . Inside each ingredient, we create the and the , we set the and append the checkbox to the list item and the name of the ingredient after. Then we append the whole thing to the .

Last but not least, we can hide the import button to prevent the user to keep importing the same list again and again.

Now if we refresh the extension and test it out on a site, you should see the ingredients getting pulled in, but if we try to check one of the items, nothing happens. That’s because we haven’t attached any listener to the checkboxes yet.

Importing works, but checking doesn’t
Importing works, but checking doesn’t

Checking the checkboxes

To add the functionality to the checkboxes, lets create an event listener next to the import buttons event listener. Now we can’t just attach listeners to the elements, as initially they don’t exist in the DOM. Instead, we can attach an event listener to the parent container which is the .

To check whether the input has been clicked inside the list item we check for . Giving the items a strike through is just a matter of switching a class based on whether the checkbox is checked or not.

Checkboxes with event listeners
Checkboxes with event listeners
Now it’s looking good

Storing previously fetched data

Of course when we close the extension and reopen it later, we need to import the same recipes again as everything is lost. To get around this, let’s add some functionality to store previously imported data.

For that, we’re going to use the API, so we need to request a permission for it in our .

To store the response returned from the content script, we can call after we clicked the import button and the response is returned:

To retrieve the response, we can call right after we check if we are or Since we would populate the same way we do for the import button, we can extract the code into a function and call it for both importing and .

We can also introduce a clear button that calls to get rid of previously stored data.

This is how our whole file will look in the end, with everything in place:


We looked at how we can initialize a Chrome extension with a manifest file, how we can request permissions to various APIs such as or , and we also looked at creating a popup from scratch and using content scripts to retrieve data from a website.

This is just the tip of iceberg. If you would like to learn more, the official Chrome documentation is pretty extensive, you can find different pages for the different APIs, I would like to encourage you to take a look and experiment with different setups and try to extend the extension created in this tutorial with various funcionality. 🌶️

JavaScript in Plain English

Learn the web's most important programming language.

Ferenc Almasi

Written by

Frontend dev with a passion for beautiful code and simple yet attractive designs. Get in touch by saying or visit directly 👋

JavaScript in Plain English

Learn the web's most important programming language.

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