The Ultimate Guide to Building a Chrome Extension

Build a Chrome extension, quickly and easily

Prateek Surana
Feb 20 · 13 min read
Photo by Valdemaras D. from Pexels

This tutorial will help you to build the mental model you need to create a Chrome extension. I’ll be covering the core concepts as we walk through building an extension.

We’ll be building, from scratch, a Chrome extension that lets you replace any piece of text in a Medium article. This is what the final result will look like:

I recommend that you build the extension in your favorite text editor as you read. I’ll keep adding links to the code on GitHub after every key concept we cover, so that you can keep track of what we’ve built so far.


I have divided this article into five main concepts that you’ll need to know while building any extension, namely — The Manifest file, Content Scripts, Background Scripts, Message passing, and The Extension Popup.

I also build an extension from scratch as we cover every topic mentioned above. You can find the final code for the extension here.


I’m assuming that you’re familiar with basic HTML, CSS, and JavaScript, and have at least a vague understanding of HTML DOM APIs.

Initial Setup

You’ll need to create a new folder where all of the files related to the extension will live, a code editor, and a Chrome or Chromium browser for developing and testing the extension.

The Manifest File

Every extension needs to have a manifest file to install and run. This file is like the blueprint of your Chrome extension. It tells the extension what it is, what it’s allowed to do, and when it’s allowed to do it.

Create a new file called and paste the following code snippet into it:

Let’s break it down:

  • The property is the primary identifier of the extension and will be used in the extension management UI and the Chrome web store. It has a limit of only 45 characters.
  • Next is the key, which is used to describe your extension. It’s also used both in extension management UI and the Chrome web store. It has a slightly greater character limit of 132 characters.
  • The key defines the current version of your extension (we are starting with 0.1). Whenever you want to release a new version of your extension, you'll need to update this field.
  • Last but not least, specifies the manifest specification of our package targets. We should use version two because version one was deprecated a long time ago. If you're curious about the changes from version one to version two, you can find the changelog here.

And guess what? We already have enough to load our extension in the browser:

  • Just go to and turn on developer mode from the top right-hand corner:
  • Then click on and select the folder containing the manifest file that we just created:
  • There you have it, our extension is up and running in a flash.

But this extension still looks a bit weird without a proper icon, so let’s add one.

  • Create a folder called images and download and extract these images to it.
  • Then paste this into your manifest file:
  • Now go back to the extension management page and click the reload button on the extension card. Voila, our extension is now starting to look like an actual extension!

So how does this work and why are two different keys required here, even though they have the same value?

The images folder I gave you has different file sizes for the same image. We’re using size as the key and path of images of the corresponding size as value.

The key under is used to specify the icons that are to be used for the browser toolbar, whereas the key specifies the icons that are to be used everywhere else, such as the extension management page and the Chrome web store.

Note that only , and the extension are required keys. Also, all the paths that are used here are relative to the manifest file.

You can learn more about different keys and their roles used in the manifest file here. Also, the code for this step is available here.

Content Scripts

In the last step, we got the extension up and running in our browser. Now let’s replace some text.

This is where content scripts come in. They let you modify the webpage DOM, working in isolation from the original webpage JavaScript.

An extension on a webpage can execute multiple content scripts. Each of these scripts would run in their own isolated world.

Each isolated world has its JavaScript environment that ensures libraries don’t conflict with each other.

The DOM is shared between all the content scripts and the original webpage’s JavaScript.

For our extension we also need access to the DOM to be able to modify the content of the webpage, so we need a content script.

You can inject the script programmatically (from a background or popup file) or declaratively (execute the script automatically on a particular page).

Since we want this script to be executed for Medium articles, we’ll use the declarative method for now.

Add the following snippet to your manifest file:

"name": "Replacely",
"content_scripts": [
"matches": ["*"],
"js": ["contentScript.js"]

The is an array of objects in which each object has the following properties:

  • - An array of regex strings that specifies the pages the content script will be injected.
  • - An array of strings that represent the files that should be executed on matching pages.

There are a few other properties as well, which you can find here.

So, we create a new file called and add this log to it:

console.log("Content script running...");

Now reload the extension, open any of your favorite Medium articles, and check the console. If everything’s running as expected, you’ll see the following log in the console.

Note: you’ll need to reload the extension every time we make a change in the extension.

Since we have the content script ready now, let’s add the code for which we created this extension:

This code selects all the different kinds of tags on a Medium page, finds the string in their text content, and replaces it with the string.

The content script in action, replacing the word ‘the’ with ‘replacely’. Fun isn’t it ;).

Try playing along with the code by using different values for and variables for different Medium articles.

To summarize this section, we learned how content scripts give us access to a webpage’s DOM, allowing us to manipulate it in isolation with the webpage JavaScript. You can find the code up to this point here.

Background Script

This script listens to specific events in the background, such as changing tabs, URL updates, and adding a bookmark, and it does a variety of other things to affect how the browser as a whole behaves.

Unlike content scripts, it works as a whole for the browser instead of having separate instances in separate tabs. Although we can have multiple background scripts for an extension.

Also, background scripts get executed as soon as the browser launches, whereas content scripts get executed when the webpage on which the script is to be executed loads.

To create a background script add the following snippet to your :

"name": "Replacely",
"background": {
"scripts": ["background.js"],
"persistent": false

Similar to the content scripts, the key also takes an array of scripts to be executed via the key.

The key is required to be set to true when your extension uses chrome.webRequest API to block or modify network requests. When the persistent key is false, the browser automatically suspends the script after a few seconds of inactivity.

Create a new file called , add the following log to it, then reload the extension.

console.log("Background script running...");

Now open your favorite Medium article again and check the console:

Wait, what? This is the console we added in the content script. So where does the background script get logged?

Remember, as mentioned, it works as a whole for the browser — you won’t be able to view the logs on the webpage’s console. Instead, it has its own set of dev tools. Go to your extension management page and click on the “Inspect views background page” link.

There’s our sweet little log:

Now that we know what a background script is let’s try out these some events to play with our extension. For starters, we’ll grey out the extension icon in the browser toolbar for non-Medium URLs.

For that, we’ll need to use the API that lets us take actions depending on the content or URL of a page, without requiring permission to read the page's content.

Most APIs like the one we’re going to use need to be registered under the key in the manifest file for extensions to use them:

"name": "Replacely",
"permissions": ["declarativeContent"],

Now add the below snippet to :

This might seem a bit intimidating, but it’s actually not. We created an array called which contains all the rules for our extension. We have only one rule right now.

Every rule is an object with two keys: and . These specify the conditions with the corresponding actions to be executed when those conditions are met.

In our case, the only condition is that the host should be equal to and the action to be executed is to show the page action, which is an active toolbar icon, for that page.

In the following lines, we remove all the existing rules when the page is changed by passing to . In the callback we pass the rules that we just created using the API.

On reloading the extension and visiting any non-Medium URL, you should see this in the browser toolbar:

And on Medium URLs you should see this:

To summarise, in this section we saw how background scripts help us listen for browser events and take action using them.

You can learn more about background scripts here and find the code to this point here.

In the next section we are going to see how to combine the powers of background script and content script via message passing.

Message Passing

So far, we saw how we could manipulate a webpage’s data using the content script and take some browser-level actions by listening to browser events in background scripts.

But what if you want to manipulate a webpage by listening to some browser events or vice-versa? That’s where message passing comes in.

With message passing you can communicate between the content script and the extension. Either side can listen for messages sent from the other end and respond on the same channel. A message can contain any valid JSON object (null, boolean, number, string, array, or object).

There are two types of message passing APIs available — one for simple one time requests, and a more complex one for long-lived connections where you want a shared state between your content script and the extension.

For our example, we’re going to use the simple one-time request message passing.

Up till now, the extension replaces the text as soon the Medium article loads, now let’s replace the text only when we click on the extension icon in the browser toolbar.

To achieve this we need to listen for a click event on the extension icon in the background script, and then send a message to the content script to replace the text when that message is received.

Add the snippet below to your file:

chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {action: "REPLACE_TEXT"});

The API takes a callback which is fired whenever the extension icon in the browser toolbar is clicked.

Since there can be multiple tabs open in the browser, we only want to send the message to the currently active tab, so we use the API to find the currently active tab in the current window. It takes a callback, which contains an array of tabs satisfying the above query as its argument.

We then use the API inside the above callback to send a message to the active tab. It takes the tab ID as the first argument and the message as the second.

In the file add the snippet below to listen for any messages:


It is evident that the callback to gets executed whenever it receives a message, with the first parameter being the message it receives.

Now reload the extension, open a Medium article, and click on the extension icon. If everything is correct you should see this message in the browser console every time you click the icon:

Now we just need to replace the text only when we receive the above message. Replace the code in your file with the below snippet:

We just wrapped our replacer logic in a function called and are executing it only when we receive a message that has an action property with the value .

Now reload the extension to see the magic:

In this section, we saw how you can use the combined power of the content script and background script with message passing. You can find the code up to this point here.

Extension Popup

Up till now, our extension can replace text when we click the icon, but we still have to make changes in the code if we need to control the and strings.

We need a user interface so that the user can decide the find and replace strings. Popups are one of several types of user interface that a Chrome extension can provide.

If you’ve ever installed a Chrome extension the chances are you already know what a popup is. They usually appear upon clicking the extension icon in the browser toolbar.

Popups of some popular extensions

For this add the line below to in the manifest file:

"page_action": {
"default_popup": "popup.html",

Now create a file called and paste the snippet below in it:

Then create a file called and paste this CSS in it.

Now, on reloading the extension and clicking on the icon in the browser toolbar, you’ll be able to see the popup that we just created.

You’ll also notice that the text-replacing action we added in the previous section stops working. That is because the API only works when there is no popup in the extension. So, you can remove that code from the file since as it’s now useless.

At the bottom of we’ve also included a script called — this will contain the code that needs to be executed on form submit.

Create a file called with the following code in it:

We attached the handler to the form and, in that handler, we sent a message to the content script — similar to what we did in the previous section. However, this time we also added the and text in the message with the texts entered in the respective inputs as their value.

Finally, we also need to modify the a little, since previously we had hardcoded the find and replace texts. Paste the following snippet in the content script file:

We’ve updated the function, which now takes two arguments containing the and strings, which were previously hardcoded.

Now reload the extension one final time to view the extension in action (you might also need to reload the Medium article this time).


If you’ve reached this far, I hope by you have a pretty good understanding of how Chrome extensions work, what are they capable of and, most importantly, how you can create one. If you have any doubts, feel free to note it down in the comments below or ping me on twitter.

Also, if you want to explore more than you can try to persist the find and replace changes using the API and add a clear button in the popup to clear all the current changes. Open a PR against the GitHub repo, and I'll review and merge it if it's good enough.

Thanks for reading!

Better Programming

Advice for programmers.

Thanks to Zack Shapiro

Prateek Surana

Written by

A young Jedi in a galaxy far away.

Better Programming

Advice for programmers.

More From Medium

More from Better Programming

More from Better Programming

More from Better Programming

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