Learn how to make your first Chrome extension — Part 2

Jackson Prince
Sep 19 · 16 min read

This article is part two of two on learning how to build browser extensions. The first part is available here: Replace Everything With Pikachu Chrome Extension.

The first article includes an in-depth explanation of what a browser extension is, how it’s useful, and what its basic components are.

This article is not that. We will be coding from start to finish. We’ll work in Vanilla JavaScript, HTML, and CSS to create a Pokemon-encountering browser extension.

Don’t let the length of this article be a measure of difficulty. I write in super slow-mo with a lot of pictures. The process of creating an extension is simpler than you’d believe.

Our road map:

  1. Setup Chrome extension to display popup.
  2. Fetch Pokemon in background script.
  3. Display fetched Pokemon in popup.
  4. Catch Em All! (using localStorage).

If all goes according to plan, we’ll be able to do this:

That’s it – let’s get started!


Note: If you have questions about this initial setup and haven’t yet read the previous article, go back and give it a look. This first section moves a bit quicker.

Create a new directory and basic manifest.

$ mkdir pokeCatcherExtension 
$ cd pokeCatcherExtension
$ touch manifest.json
// Within manifest.json => {
"name": "Poke Catcher",
"version": "1.0",
"manifest_version": 2

Note: Chrome requires you use manifest version 2 to load your extension. Inexplicable, but harmless.

Your directory should look like this:

Bring in a square .png image to use as our extension’s icon:

Include this image in the manifest…

Create a background.js file to handle browser events.

$ touch background.jsWithin background.js =>console.log('background script runnin runnin and...')

Include the new file in our manifest:

We are now ready to load this code into chrome as an extension. Navigate to the URL chrome://extensions. The page below should appear. Make sure Developer mode (top right) is switched on.

Click Load unpacked (top left) and select the directory you just created. A new extension card should appear in your extension management page.

Click background page to access background page’s console.

Check your toolbar to see a Pokeball icon in the rightmost position…

If all three of those results were yours too, then you’re up and running!

Whereas our goal in part 2 was to click the Pokeball icon to change image elements on the page, our goal in this article is to display a popup page.

Let’s create that page now. Call it whatever you want. popup.html is nice and self-explanatory.

$ touch popup.html// Within popup.html

<h1> You done made it. </h1>

Update our manifest to display this new HTML page by default when the extension icon is clicked:

Chrome requires you use their specific syntax when you write your manifest. For questions and (decent at best) clarification, check the documentation.

Important: Because we made changes to our extension’s structure, return to the extension management page (chrome://extensions) and reload pokeCatcherExtension.

Guess what? You just made a Chrome extension popup.

Before we move onto the next section, let’s stylize our popup.html and add an image of the Pokemon logo:

Create your stylesheet.

$ touch style.css

Add these basic rules (feel free to play around and get funky with your style–I’ll keep it plain for simplicity’s sake).

Since we didn’t change the underlying structure of our extension (i.e. the manifest did not change, only styling changed), we do not need to reload our extension.

We can go right back to our icon and click to see this beautiful blank page…

Nice! We now have a running background script and functional popup. On to the next…

Fetch Pokemon in the Background

In this section, our first goal is to retrieve Pokemon in the background. We’ll then use some Chrome-Speak (a.k.a the Chrome API) to retrieve a new Pokemon every time we load a new web page.

Note: I will refer to the Chrome API as Chrome-Speak for the duration of this article, because that’s what it is.

First, the fetch

Open the background script developer console. As a reminder (I often forgot while starting out), this console is available by navigating to your extensions manager chrome://extensions and clicking background page

The console is a great place to practice fetches. It’s quickest and harmless to fail in a console. Console fetches are rapid prototyping at its best.

We will be using the world-famous PokeAPI to retrieve our Pokemon data. Check it out, get a feel for what you can do (an enormous amount).

We’ll start by fetching Pikachu…

URL: https://pokeapi.co/api/v2/pokemon/pikachu

Here is the fetch script, ready to go in our console:

New to fetch? Reminder: We’re accessing a website pokeapi.co that, instead of rendering a web page, renders a string of information. That particular string, in this case, will be all of Pikachu’s information. The .then()s allow us to see that information, then use it in our app.

Press enter.

Nice. The call was successful. Now dig inside the returned object and retrieve whatever information you want. Be warned, this object is no joke…

Thankfully, it’s well organized. We want the Pokemon’s name and a small image. You’ll see name can be found like so…


Note that forms is an array containing a single object. Hence the [0].

And we can find image like so:


We should test our theory in the console. Save the returned object as a global variable (via right-click) and perform those methods.

When we store as global variable, chrome auto-generates temp1 in reference to the object. Use temp1 to test those methods.

Nice! We have the name (in red) and the URL (underlined)… Feel free to check that URL. You should find you-know-who on the other end:

Now that we know we’re fetching the correct data, let’s insert those lines into our background script.

Reload your extension in the management page to witness the full effect of your work. When you return to the background page console, you should see the full object we just practiced retrieving…

Now that we have our object, let’s create a function that logs the name and image of our retrieved Pokemon…

…reload your extension again, return to the console, and you should see…

Nice! That’s a proper fetch.

Fetch new Pokemon on page load

Phase two of this section is enacting a new fetch every time we load a new page. To do this, we will employ Chrome-Speak.

Translated: “chrome” get me the current window’s “tabs,” and “listen” for when they’re “updated” (reloaded) – when one of them is updated, please log the current tab’s “url”.

Important: You should get an error!

There is one important step we’ve yet to take and it involves our manifest.

Giving an extension the power to dictate what happens with tabs is no small deal. Chrome requires us to explicitly permit access to the tabs.onUpdated() function.

Update your manifest like so:

Now, reload the extension, load a web page, bring up your background page console, and you should see something like this:

It’s Google! Load a new webpage and see the new URLs logged to the console.


Our goal for this section was first to fetch a Pokemon, then to perform that action on each new page we load. We’ve built out both sides of the equation (our fetch and our page listener). Let’s combine them.

Reload your extension in the extension management page and browse a few webpages. Your console should fill up with Pikachu fetch information…

Excellent. But, as cool as that is, we don’t want to see Pikachu for every page we visit. We want a random Pokemon. Let’s write a quick function to retrieve any random Pokemon from the PokeAPI.

We know there are 807 total Pokemon to choose from. With a quick look at the PokeAPI documentation, we learn that we can search by ID instead of name. That is, /api/v2/pokemon/pikachu is the same as api/v2/pokemon/25 .

Feel free to create a random number generator from 1 to 807. My heart lies with the original 151 Pokemon, so I’ll limit my random number generator to numbers between 1 and 151.

We will then interpolate that number into the last space of our fetch call’s URL…

Random number generator: const pokemonId = Math.ceil(Math.random() * 151)

Insert pokemonId into our fetch and interpolate it into the fetch URL.

Reload your extension and surf the web a bit. You should see the background script fill up with new Pokemon info every time!!

If you’ve got that working, then hell yes! We’ve just completed this section’s goals: to fetch new Pokemon in the background script with each new site visited. Next step: get that information to the popup.

Send Pokemon to the Popup

In the last article, we were able to send messages from the background script to the content script. We can’t do that here. Popups don’t actually exist until you click the icon and force the mini extension window into existence.

To get a message to the popup, then, we must call from the popup to the background script.

Here is the flow of information:

  1. Visit a new webpage, the background script fetches a random Pokemon.
  2. Store that information in a changing variable.
  3. Open the popup and call for that stored information.
  4. Background script receives the call, and sends back the Pokemon it stored.
  5. Popup receives the Pokemon and changes its appearance accordingly.

We completed #1 in the previous section. Let’s quickly tackle #2.

Store the fetched Pokemon

Instead of logging our name and imageUrl to the console, let’s create two variables that are constantly being filled and replaced with new Pokemon.

(Let’s also trim the fat and get rid of our console log…)

Reload your extension, then load a new webpage or reload the current webpage and check to see that both variables change as you browse.

Nice. Now let’s move to the popup.

Call for Pokemon from the popup

You’ll notice the only file related to the popup right now is popup.html. That’s no place for logic. Let’s create a popup.js file, link it to our HTML, and test it with a console log.

$ touch popup.js// Within popup.js =>console.log('popup script runnin runnin and...')

Include the new script in our HTML…

You might think we need to include this script in our manifest. We do not!

The two are indirectly related through the extension’s HTML page. (Same as our stylesheet.) So we’re good. As long as Chrome knows about popup.html, it knows about all related stylesheets and scripts.

You’re now off and running. Click the Pokeball icon. Right-click directly on the HTML and navigate to inspect to retrieve the popup’s developer console.

We’ll immediately see the runnin runnin and… log we wrote…

Which means we’re ready to send a message to our background script. To do this, we’ll employ some Chrome-Speak.

Translated: “chrome”, as soon as the HTML page loads, while the popup is “running”, “send” a “message” to the background script.

That message will be “Poke, please” and if you get a response from the background script, run the function logPokemon, which will console log whatever message the response contains.

If we click the extension icon, nothing will happen. Our popup script requires a response from the background script. Let’s create that response now.

In the previous section, we created two variables: name and imageUrl. To pass these values neatly over the Chrome network, we must send them within an object. Note that we created an object currentPokemon to achieve this.

We’ve used this exact form of Chrome-Speak in the previous article. If you want a translation, seek that article out.

The important thing is that our background script is now actively listening for messages. When it receives a message, it will run the function sendPokeBack, which sends the current Pokemon object back to the sender.

The entire background script now looks like this:

Note that we change the syntax of our variable assignment in the function setAttributes. That function now cooperates with our newly reconfigured currentPokemon object.

Go ahead and reload the extension, refresh the page, click the popup icon, and inspect your popup’s console. You should see:

Repeat that process (minus reloading the extension), and you should get another Pokemon…

Hell yes! We just retrieved two separate Pokemon for two different websites. Now, the finishing touch of this section — display them on the screen!

A wild Pokemon appears!

Let’s start by updating our HTML file so it’s ready to receive a name and an image.

We created an h2 and an image tag in our body.

Note: I use a class for the img and an id for the header.

Now, let’s write the script popup.js to fill in values for both of these tags when the popup opens.

I changed the name of our function from logPokemon to showPokemon, which is more demonstrative. And, instead of logging text, we’re finding the header and image, and filling them in with the appropriate Pokemon info.

The final result, loading two different pages:

That’s awesome. Even better, this marks an important checkpoint.

We’ve accomplished the difficult background tasks. We’re communicating between the popup script and background page seamlessly. Catching a Pokemon only requires a bit of local storage magic.

But before we move on, let’s do some quick styling. In our CSS:

And in our JS:

…which gives us something a bit more appropriate…

Nicely done.

Catch ‘Em All!

At this point, we’ve done the entirety of our foundational work. The extension is up and running. You can run into random Pokemon all day. But what would a Pokemon catching extension be without the catch?

In this section, we’ll use our extension’s localStorage to store Pokemon we encounter in the wild.

First, let’s write a Catch! button in our HTML.

Move to popup.js and write a script that will store the current Pokemon in localStorage when you click the newly minted Catch! button…

If we click the button, the wild Pokemon will save to local storage and the text on screen will change to reflect the catch!

Even better, that Pokemon won’t get away. Local storage persists across webpages. Even though you encounter new Pokemon with each new page you visit, your localStorage will remain the same until tampered with.

Here’s how our popup.js looks in full…

When you open your popup, you should see…

Hit that button!

Check your localStorage by inspecting the popup.

Reload the page, run into a new Pokemon, and see that your butterfree persists!

Now, click catch! again.

Cloyster is yours!

This data persistence opens up a world of opportunities. There are a million directions in which we could take this. We could literally start building out Pokemon in our browser… the PokeAPI is robust enough.

But for now, let’s just add some gravy and call it a day. Here are the remaining features we should want to add to our Pokemon-catching extension:

  • I want all Pokemon to have levels. (background.js)
  • I want to see my current Pokemon. (popup.js)

Generating levels

Let’s start by generating levels between 1 and 100 in the background script.

We can use the exact same formula for random number generation.

If you’re wondering why we generate this information in the background script — we do so to keep a Pokemon’s level from changing each time we open the popup.

All information received from the background is fixed until a new page loads. Otherwise, we’d be able to stay on the same page, repeatedly opening and closing our popup until a level 100 appears. No good.

With the new level information, our background script amounts to this…

The information is there. Now we need to display it. In our popup.html, add a span tag beneath the h2 header.

We’ll change the inner text of this span to reflect the Pokemon’s level. We do this in our popup.js file.

One new line was added to each of sections of our popup.js: a new const at the top of the page, an additional line to create the span’s text in our showPokemon function and a new value to set in our localStorage.

Your popup should now render the following:

Excellent. Now for the last step in our project: show our current Pokemon.

Show My Stored Pokemon

Here’s how we’ll solve that:

  • Add a new button in our HTML.
  • Add a hidden area beneath the button, holding the current Poke’s info.
  • Write a function in popup.js that unhides our Poke when we click the button.

Let’s start with popup.html

We created a <button>, container <div>, <h3>, and <img>. We gave our container div the class of hide, which we need to create in our CSS.

We also created show class while we’re at it.

We now need to instruct our extension to toggle between hiding and showing the current Pokemon container when the new button is pressed. We’ll do this in our popup.js with this new set of functions and constants.

Note: the (!!localStorage.pokemon) condition merely checks to see if a Pokemon is stored or not.

Add this code to the rest of our popup.js file and we get the following:

And that’s it! Time to celebrate by seeing the fruits of your labor in action:


If you made it this far, I applaud you. I hope that working through this tutorial has filled you with ideas for future projects.

Over the course of two articles, we learned how to communicate between our popup, background, and webpages. We fetched and displayed information from an API in our extension window. And we used localStorage to temporarily persist our Pokemon encounters. I’d say that’s a pretty good start.

If you need a nudge, here are some fun ideas to play around with:

  • Try making a localStorage Pokedex, logging every Pokemon you encounter in the wild.
  • If you want to go big, try permanently persisting your Pokemon encounters by creating your own API and posting to it while you browse – localStorage loses its info when you quit Chrome, so your Pokemon are liable to disappear in the current format.

As for me, I’m currently working on an extension that makes a website’s privacy policies easily seen and understood as you browse the web. It’s called DataTrust. Keep your eyes peeled!

Many thanks for reading. Feel free to check out the GitHub repo for this project.

Happy coding,


Better Programming

Advice for programmers.

Thanks to Zack Shapiro

Jackson Prince

Written by

Full Stack Engineer. Film enthusiast. Twitter: @JPrinceDev

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