Chrome Extension Tutorial — Replace Images in Any Website with Pikachu

This article was also published on Level Up Coding's gitconnected

What’s a better way to learn how to make a Chrome extension than making the web cuter with Pikachu images?

In this tutorial, I will show you how you can create a simple Chrome extension that replaces images on websites with Pikachu images. We will discuss topics like content scripts, background scripts, and messages between scripts in Chrome extensions.

The code for this tutorial is available here.

First thing you need to do when creating any chrome extension is start with creating a manifest.json file in the root of the extension’s directory. For now, this file will hold only the following basic information about your extension:

Here is what everything in this file means:

  1. name: The name of the extension. I named the extension “Pikachu Everywhere”
  2. version: This is the version of the extension. When you publish the extension in the Chrome Web Store and you need to update it, you will have to increase the version number so it’s good to start with a low number.
  3. description: (Optional) Description of your extension
  4. manifest_version: As of Chrome 18, developers are required to use manifest version 2, since manifest version 1 is now deprecated.
  5. icons: (Optional) Here we specify the extension’s icon. This icon will show up in your browser’s toolbar, the Chrome Web Store, and any other place that requires to show it. For that reason, it’s good to include different sizes of the icon. I have used an icon from Free Icons PNG and resized the icons using https://resizeimage.net/ (You can find the images in the GitHub repository). The paths specified for these images is relative to the root of the extension.

With this, we have the core of any Chrome extension. Next, we’ll add the extension to the browser.

Go to chrome://extensions. There, you can toggle developer mode at the top right corner. Once you do that, a new toolbar will show up with three buttons: “Load unpacked,” “Pack extension,” and “Update.” Click on “Load unpacked” and choose your extension’s directory.

Once you do that, you will see that your new chrome extension is now working. Yes, it does nothing, but it’s working.

In Chrome extensions, there are two types of scripts you can use. There are Background scripts and Content scripts. Background scripts run (obviously) in the background. They are loaded when needed and unloaded when idle. They are used to listen to certain events in your Chrome extension.

Content scripts are scripts loaded once you open a website and injected it into it. Once you open a website, they will start running just like any other script on that website. After you close the website, the content scripts on that website will stop running as well.

For our extension, we want to replace every image on every website with Pikachu images. In order to do that we need to specify a content script that works every time you open a web page.

Let’s create our content script in assets/js/contentScript.js and include the following script to test whether our extension is working or not:

One other thing we need to do is tell Chrome which script do we need to run as a content script. Add the following lines to manifest.json

Here we are using content_scripts as key with the value as an array. Here’s what everything in the array means:

  1. matches: here you can specify which URLs the content script should run on. In our case we want our content script to run on all pages. So, we use the placeholder “<all_urls>” to specify that.
  2. all_frames: this key is used to allow the extension to specify if JavaScript and CSS files should be injected into all frames matching the specified URL requirements or only into the topmost frame in a tab.
  3. js: here we specify the script we want to run as a content script. You can specify more than one script, so the value has to be an array

Now we’re ready to test our content script.

But before we do that we need to do one small step. In order to inform chrome that we updated our chrome extension, we need to go back to chrome://extensions, go to our extension, and press the reload icon.

To test if our content script is working, go to any web page you want, reload the page if it was opened before, then right-click and press Inspect. In DevTools, open the Console tab. You will see in your console as expected:

Console of a web page

So, we can verify that our content script is working. Try reloading the page, and the content script will execute again. Try opening another web page, and the content script will execute on that page independently.

Now, we know how to inject a script on any web page, and through that script we can do our work.

In our case, we need to replace every image in any website with random Pikachu images. To do that, we will use this API which will provide us with a JSON object containing the link of a Pikachu image. Each request to that API will look something like this:

Result of API call

With the help of this API, the steps to achieve what we need will be as follows:

  1. Get all image elements on the web page.
  2. Perform a GET request to the API to retrieve a random Pikachu image link.
  3. Replace the image elements’ src attributes with the links we retrieve.

This all sounds good and easy, but there is one complication: content scripts in Chrome extensions cannot perform Cross-Origin requests.

So how can we fetch the image URLs from the API? We will need to use a Background script.

As we specified earlier, Background scripts run in the background in Chrome. They are not dependent on any web page.

Scripts in Chrome extensions are isolated from each other. However, they share a mean of communication through the Chrome API.

What we will do is that we will listen for a message in the background script sent from content scripts. Every time a web page opens, our content script will run, send a message to the background script requesting an image URL. The background script will then perform the asynchronous call to the Pikachu API, retrieve the link and send it back to the content script.

This may sound complicated at first, but it’s actually done in a very simple manner.

Let us first start by creating the background script and see it in action. Create assets/js/background.js with the following content:

Now, we need to tell Chrome that the file we just created will be our background script. In order to do that, we need to go back to our manifest.json and add the following:

Here is what everything means:

  1. scripts: An array that holds the paths of the scripts we will use as background scripts. In our case, it’s just one.
  2. persistent: should always be false unless the extension uses Chrome’s Web Request API to block or modify network requests.

Now, Chrome will know about our background script. It will load it when it’s needed, and unload it when it becomes idle.

To test our background script, we need to reload our extension as we did earlier.

Once you do that, you will see a new link appear in your extension’s info box with the text “inspect views background page”:

If you click on it, a new DevTool will open and in the console you will see:

We can now confirm that our background script is working. Even if you close any other web page or reload any web page, nothing will change in the background script.

Let’s go back to our objective again. We now need to listen to messages in our background script, and on receiving a new message fetch the URL from the Pikachu API.

In order to listen to messages, we will use the following method in Chrome’s API:

We need to pass a function to addListener. This function will be executed on receiving any new message. The function takes three parameters:

  1. message: the message sent of type any.
  2. sender: of type MessageSender
  3. senderResponse: Callback function to call (at most once) when you have a response. This function is sent by the sender on sending the message.

So this is what our background.js file should look like now:

Now our background script is ready to receive messages, but there are no messages being sent.

As we said earlier, each time a page opens, our content script will loop through image elements in the web page, and send a message to the background script to fetch the Pikachu image link.

In order to send messages, we will need to use the following method from Chrome’s API:

The parameter’s being sent are as follows:

  1. extensionId: (Optional) the ID of the extension we are sending the message to. If it’s omitted, the message will be sent to our own extension.
  2. message: The message we are sending of type any.
  3. options: Optional
  4. responseCallback: The function to call after receiving the message. This is the senderResponse parameter in the previous method we saw.

So, we will use this method in our content script to send a message to the background script to fetch the Pikachu URL from the API. Our content script should now look like this:

Here’s what we’re doing in the above code:

  1. Line 1: get all image elements on the web page.
  2. Line 2–6: Loop through the image elements. On each element, we send a message to the background script. As you can tell, we are omitting the optional extensionId and options parameters. The first parameter is the message we are sending, and the second parameter is the callback function we want the background to execute after it fetches the image’s link.

We are sending the object {msg: ‘image’, index: i} to our extension. This object has two properties. The first one is to specify what our message is about. In our extension, it might not be very helpful, but when you have an extension that uses different types of messages, it’s a good idea to differentiate what each message is for. The second property is to specify the index of the image the link is for in the images array. The reason behind this is that the callback function replacing the image will be called asynchronously, so i might point at another index at the time it’s being executed.

The callback function receives an object that has two properties. data will be the data received from the API call, and index will be the index of the image in the array images. Once it’s called, it will set the image at index in images to the link property is data.

Now, we need to fetch the data from the API in our background script whenever the content script sends a message. We can do this as follows:

Here’s what’s going on here:

  1. Line 1: Listen to messages in our extension
  2. Line 2: Check if the message is to get the image URL. This will use the msg attribute we passed when we sent the message in contentScript
  3. Line 3–8: If the condition in Line 2 is true, fetch the data from the API. In the first Promise we get the response text, then in the second Promise we parse the JSON object and call senderResponse which is the callback function specified by the sender of the message. We pass with it an object with the parameters data (the data received through the API call) and index (the index of the image, sent by the sender).
  4. Line 9: simple handling of any error that might occur.
  5. Line 10: If the sender specifies a callback function, it has to be called before the listener stops executing or else it will throw an error. In our case, we have to wait until we receive a response from the API which will happen asynchronously, so we return true to specify that it will be called asynchronously.

Okay, our scripts are ready. Only one thing left — we need to give permission to the API in our manifest.json:

All you need to do now is reload the extension in chrome://extensions, then open any page and see Pikachu everywhere!

Of course, this extension will not replace background images. This can be done by looping through elements, checking if they have background images, and replacing them the same way.