---
title: How to Make a Chrome Extension
teaser: 'Go beyond the introductory tutorials and learn how to make a Chrome extension
  in JavaScript that actually does something.

  '
tags: web,javascript,chrome
author: Gabe Berke-Williams
published_on: 2015-01-23
---

If you're wondering how to make a Chrome Extension, Chrome's [extension
documentation] is great for basic implementations. However, to use more advanced
features requires a lot of Googling and Stack Overflow.  Let's make an
intermediate Chrome extension that interacts with the page: it will find the
first external link on the page and open it in a new tab.

[extension documentation]: https://developer.chrome.com/extensions/overview

## manifest.json

The [manifest.json] file tells Chrome important information about your
extension, like its name and which permissions it needs.

[manifest.json]: https://developer.chrome.com/extensions/manifest

The most basic possible extension is a directory with a `manifest.json` file.
Let's create a directory and put the following JSON
into `manifest.json`:

```json
{
  "manifest_version": 2,
  "name": "My Cool Extension",
  "version": "0.1"
}
```

That's the most basic possible `manifest.json`, with all required fields filled
in. The `manifest_version` [should always be 2], because version 1 is
unsupported as of January 2014. So far our extension does absolutely nothing,
but let's load it into Chrome anyway.

[should always be 2]: https://developer.chrome.com/extensions/manifestVersion

## Load your extension into Chrome

To load your extension in Chrome, open up `chrome://extensions/` in your browser
and click "Developer mode" in the top right. Now click "Load unpacked
extension..." and select the extension's directory. You should now see your
extension in the list.

When you change or add code in your extension, just come back to this page and
reload the page. Chrome will reload your extension.

## Content scripts

A [content script] is "a JavaScript file that runs in the context of web pages."
This means that a content script can interact with web pages that the browser
visits. Not every JavaScript file in a Chrome extension can do this; we'll see
why later.

[content script]: https://developer.chrome.com/extensions/content_scripts

Let's add a content script named `content.js`:

```javascript
// content.js
alert("Hello from your Chrome extension!")
```

To inject the script, we need to tell our `manifest.json` file about it.

Add this to your `manifest.json` file:

```json
"content_scripts": [
  {
    "matches": [
      "<all_urls>"
    ],
    "js": ["content.js"]
  }
]
```

This tells Chrome to inject `content.js` into every page we visit using the
special `<all_urls>` URL pattern. If we want to inject the script on only some
pages, we can use [match patterns]. Here are a few examples of values for
`"matches"`:

[match patterns]: https://developer.chrome.com/extensions/match_patterns

- `["https://mail.google.com/*", "http://mail.google.com/*"]` injects our script
  into HTTPS and HTTP Gmail. If we have `/` at the end instead of `/*`, it
  matches the URLs exactly, and so would only inject into
  `https://mail.google.com/`, not `https://mail.google.com/mail/u/0/#inbox`.
  Usually that isn't what you want.
- `http://*/*` will match any `http` URL, but no other scheme. For example, this
  won't inject your script into `https` sites.

Reload your Chrome extension. Every single page you visit now pops up an alert. Let's
log the first URL on the page instead.

## Logging the URL

jQuery isn't necessary, but it makes everything easier. First, download a
version of jQuery from [the jQuery CDN] and put it in your extension's folder. I
downloaded the latest minified version, `jquery-2.1.3.min.js`. To load it, add
it to `manifest.json` before `"content.js"`. Your whole `manifest.json` should look
like this:

[the jQuery CDN]: http://code.jquery.com/

```json
{
  "manifest_version": 2,
  "name": "My Cool Extension",
  "version": "0.1",
  "content_scripts": [
    {
      "matches": [
        "<all_urls>"
      ],
      "js": ["jquery-2.1.3.min.js", "content.js"]
    }
  ]
}
```

Now that we have jQuery, let's use it to log the URL of the first external link
on the page in `content.js`:

```javascript
// content.js
var firstHref = $("a[href^='http']").eq(0).attr("href");

console.log(firstHref);
```

Note that we don't need to use jQuery to check if the document has loaded. By
default, Chrome injects content scripts [after the DOM is complete].

[after the DOM is complete]: https://developer.chrome.com/extensions/content_scripts#run_at

Try it out - you should see the output in your console on every page you visit.

## Browser Actions

When an extension adds a little icon next to your address bar, that's a [browser
action]. Your extension can listen for clicks on that button and then do
something.

[browser action]: https://developer.chrome.com/extensions/browserAction

Put the [icon.png] from Google's extension tutorial in your extension folder and
add this to `manifest.json`:

[icon.png]: https://developer.chrome.com/extensions/examples/tutorials/getstarted/icon.png

```json
"browser_action": {
  "default_icon": "icon.png"
}
```

In order to use the browser action, we need to add message passing.

## Message passing

A content script has access to the current page, but is limited in the APIs it
can access. For example, it cannot listen for clicks on the browser action.  We
need to add a different type of script to our extension, a background script,
which has access to every Chrome API but cannot access the current page. As
Google [puts it]:

[puts it]: https://developer.chrome.com/extensions/content_scripts "limitations of Content Scripts"

> Content scripts have some limitations. They cannot use `chrome.*` APIs, with
> the exception of `extension`, `i18n`, `runtime`, and `storage`.

So the content script will be able to pull a URL out of the current page, but
will need to hand that URL over to the background script to do something useful
with it. In order to communicate, we'll use what Google calls [message passing],
which allows scripts to send and listen for messages. It is the only way for
content scripts and background scripts to interact.

[message passing]: https://developer.chrome.com/extensions/messaging

Add the following to tell `manifest.json` about the background script:

```json
"background": {
  "scripts": ["background.js"]
}
```

Now we'll add `background.js`:

```javascript
// background.js

// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function(tab) {
  // Send a message to the active tab
  chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    var activeTab = tabs[0];
    chrome.tabs.sendMessage(activeTab.id, {"message": "clicked_browser_action"});
  });
});
```

This sends an arbitrary JSON payload to the current tab. The keys of the JSON
payload can be anything, but I chose `"message"` for simplicity. Now we need to
listen for that message in `content.js`:

```javascript
// content.js
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if( request.message === "clicked_browser_action" ) {
      var firstHref = $("a[href^='http']").eq(0).attr("href");

      console.log(firstHref);
    }
  }
);
```

Notice that all of our previous code has been moved into the listener, so that
it is only run when the payload is received. Every time you click the browser
action icon, you should see a URL get logged to the console. If it's not
working, try reloading the extension and then reloading the page.

## Opening a new tab

We can use the [`chrome.tabs`] API to open a new tab:

[`chrome.tabs`]: https://developer.chrome.com/extensions/tabs

```javascript
chrome.tabs.create({"url": "http://google.com"});
```

But `chrome.tabs` can only be used by `background.js`, so we'll have to add some
more message passing since `background.js` can open the tab, but can't grab the
URL. Here's the idea:

1. Listen for a click on the browser action in `background.js`. When it's
   clicked, send a `clicked_browser_action` event to `content.js`.
1. When `content.js` receives the event, it grabs the URL of the first link on the
   page. Then it sends `open_new_tab` back to `background.js` with the URL to
   open.
1. `background.js` listens for `open_new_tab` and opens a new tab with the given
   URL when it receives the message.

Clicking on the browser action will trigger `background.js`, which will send a
message to `content.js`, which will send a URL back to `background.js`, which will
open a new tab with the given URL.

First, we need to tell `content.js` to send the URL to `background.js`. Change
`content.js` to use this code:

```javascript
// content.js
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if( request.message === "clicked_browser_action" ) {
      var firstHref = $("a[href^='http']").eq(0).attr("href");

      console.log(firstHref);

      // This line is new!
      chrome.runtime.sendMessage({"message": "open_new_tab", "url": firstHref});
    }
  }
);
```

Now we need to add some code to tell `background.js` to listen for that event:

```javascript
// background.js

// Called when the user clicks on the browser action.
chrome.browserAction.onClicked.addListener(function(tab) {
  // Send a message to the active tab
  chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    var activeTab = tabs[0];
    chrome.tabs.sendMessage(activeTab.id, {"message": "clicked_browser_action"});
  });
});

// This block is new!
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    if( request.message === "open_new_tab" ) {
      chrome.tabs.create({"url": request.url});
    }
  }
);
```

Now when you click on the browser action icon, it opens a new tab with the first
external URL on the page.

## Wrapping it up

The full `content.js` and `background.js` are above. Here's the full `manifest.json`:

```json
{
  "manifest_version": 2,
  "name": "My Cool Extension",
  "version": "0.1",
  "background": {
    "scripts": ["background.js"]
  },
  "content_scripts": [
    {
      "matches": [
        "<all_urls>"
      ],
      "js": ["jquery-2.1.3.min.js", "content.js"]
    }
  ],
  "browser_action": {
    "default_icon": "icon.png"
  }
}
```

And here's the full directory structure:

```tree
.
├── background.js
├── content.js
├── icon.png
├── jquery-2.1.3.min.js
└── manifest.json
```

## More on how to make a Chrome extension

For more information, try the [official Chrome extension documentation].

[official Chrome extension documentation]: https://developer.chrome.com/extensions/overview
