Building a simple chrome extension

Aidan Breen
Sep 29, 2016 · 6 min read

I’ve been building websites and web apps for a while, but browser extensions have always seemed like black magic to me. But it turns out it’s not as complicated as I had feared! In fact, I managed to build and distribute my first chrome extension, rfrshr in under 2 hours. Here’s how.

(All of the code for rfrshr is available on github, so take a peek and follow along!)

[Edit from the future: I’ve written more about building extensions! Check out my posts about Squish and zwBlocker]

Rfrshr is a simple extension that lets the user store a single URL, and then load it, at any time, using the keyboard shortcut Alt-R.

Developer mode

You’ll need to test your app while you build it.

  • Start by opening up your extensions page by putting chrome://extensions/ in the address bar.

That’s it. You’re extension will now be installed locally, and you can test it directly in chrome. Even better, you can use the standard chrome developer tools to debug your extension by clicking the extension icon, right clicking and click inspect.

This extension uses a background page to listen to keyboard shortcuts, and you can debug that by clicking the background page link on the extensions page.

App structure

Chrome extensions, as it turns out, are really just simple web apps that run in a little page the appears when the user clicks the icon. As you would expect, this means there is a HTML file, and javascript files. The only difference, is that an extension requires a manifest file to define the structure, permissions, icons and other metadata.


The manifest is just a JSON file.

The first line defines the manifest version, which tells the web store the type of manifest we are using. There is an older, deprecated version 1, so we use the current version 2. This is not the version of our extension.

"manifest_version": 2,

Then we have some info about our app, for use in the web store.

"name": "rfrshr",
"description": "Press Alt+R to reload any given URL.",
"version": "1.1",

Next we define how our app will interact with the browser. The default_icon is a 19x19 pixel image and shows up in the browser. default_popup is the default page that is displayed when the user clicks the icon. Lastly, default_title is the tooltip that is displayed when the user hovers over the icon.

"browser_action": {    
"default_icon": "icon.png",
"default_popup": "popup.html",
"default_title": "Click to reload url"

The rest of the file is specific to this extension, and will change depending on what you want to achieve. This extension is super simple, so I’m not going to explain things in the order I built them, but rather file by file to keep things simple.

Rfrshr needs to update the current tab address with a stored address supplied by the user. We get access to these features using permissions. I had a look through the documentation and figure we need two permissions:

"permissions": [

Now, I wanted to monitor keyboard shortcuts, and after some googling, I discovered I could define which ones to listen to within the manifest file, and actually listen to them in a background page. A background page is really just a javascript file that is loaded as long as the extension is installed. But that’s not very efficient, so we can set “persistent”: false to only load the file when needed, freeing up memory when the page is idle. Background pages with “persistent”: false are called event pages. (See docs)

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

Next, we actually define the specific keyboard shortcuts, or commands to listen to. I’ve picked Alt+R because it doesn’t collide with anything I use, and I can remember “R” is to reload or refresh a page.

"commands": {         
"refresh_url": {
"suggested_key": {
"default": "Alt+R",
"mac": "Alt+R"
"description": "Refresh the defined url"

Here refresh_url is just a name I came up with. You can call your command anything.

Finally, I included this line to allow me to load google analytics tracking code from the CDN. This was the last line I added in the entire project!

"content_security_policy": "script-src 'self'; object-src 'self'"


This is the main entry point of the app, because we defined it in the manifest file. It’s just a simple HTML file which includes any css includes (I used google fonts), inline css, and any other javascript files (jquery for convenience, and my popup.js file for the front end logic.

For security reasons, inline javascript is not allowed.

My UI is really simple. Just a heading, subheading, a message span to let the user know if data was saved, and an input box and button to update the URL.

<h2>Press Alt+R</h2>
<span class="message"></span>
<div class="inputbox">
<input type="text" class="urlinput"></input>
<button class="urlbutton">Save</button>


This file is included in the popup.html file with a <script> tag.

Firstly, I added a helper function to update the message span:

function message(val){  $(".message").text(val); }

Lines 5–13 are setting up the google analytics code. I wont go into any detail here.

In the doc.ready function, I use the api, which I have access to thanks to the permissions in the manifest file, to get the value of a variable called “definedURL”. This demonstrates how you can retrieve data from a local data store using the storage API. This section itself, however, is really just a convenience to the user so they know what URL they will be loading.

//set input to previously saved value"definedURL", function(result){
$(".urlinput").attr("placeholder","type url here...");

Next, we want to actually let the user update the URL. Line 26 adds a listening to the click event of the button. Line 27 should have been deleted 😳. Next we store the value of the input box using the storage API, and let the user know it worked.

//handle update
$(".message").text($(".urlinput").val());{'definedURL': $(".urlinput").val()}, function() {
// Notify that we saved.
message('Settings saved');
//google tracking here

Lines 36–41 just allow the user the hit enter, rather than clicking the submit button.


This is where we actually listen to keyboard commands. This file is defined in the manifest as the background page, or event page.

Lines 1–9 are just google tracking.

Line 11 uses the commands API to setup a listening callback function. We have only set up one command in our manifest, so we don’t need to check what it is. I imagine if we had multiple commands, we’d need a few if statements to make sure we’re responding to the correct command.

We use the same storage API to access the URL the user has entered, stored in “definedURL”. Then, we use the tabs API to update the current tab URL, which reloads the page!

I’ve added a little if statement to check if the user has actually added a URL or not. If not, it just redirects them to a page which tells them to add a URL.

chrome.commands.onCommand.addListener(function(command) {"definedURL", function(result){
_gaq.push(['_trackEvent', 'refresh', result.definedURL]);


So, once the extension is complete, and it’s working on our local machine, what do we do next? I know the extension is silly, and probably of no use to anybody but myself, but I want to share it with the world!

Well, fear not. Distributing a chrome extension is super simple, with one caveat*.

  • Start by compressing your extension folder into a .zip file.

You’ll need to create a larger icon and you’ll probably want to add a larger screenshot image also.

  • Caveat: it’ll cost you. For some reason, google charge a verification fee of €5 before you put your first extension on the web store. This is a pain in the arse.

And that’s it!

Congratulations, you have now created and distributed a fully working browser extension. Now you just have to figure out how to get people using it. Oh, maybe write a medium post about building it!

I’ve written more about building extensions! Check out my posts about Squish and zwBlocker

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

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store