So you want to build a Chrome extension...

Mike Crittenden, Software Architect
#Development | Posted

...but you don't know where to start? Fear not! In this post I will guide you through the steps of putting together a Chrome extension from scratch, packaging it, and uploading it to the Webstore.
Step 1: Install Yeoman and friendsYeoman

 is a "scaffolding tool for modern web apps" which means it'll set you up with a nice little framework and some helpers for whatever you're building. Lucky for us, there's even a Yeoman generator for Chrome extensions so we can go ahead and install that.

First things first, make sure you have NodeJS and npm installed. If you don't, you'll have to install them. Then you can install Yeoman like so:

$ npm install -g yo

Then you can install the Chrome extension generator for it:

$ npm install -g generator-chrome-extension

All set! Let's create a new project.

Step 2: Generate a new Chrome extension

Firstly, create an empty directory for your project and open it up in a terminal. For the purposes of this tutorial, we'll say we're creating an extension named "Pupperator" which, I don't know, randomly adds puppies to the page or something. Sure.

  1. $ mkdir ~/Code/pupperator
  2. $ cd ~/Code/pupperator

Using Yeoman, you can now generate some boilerplate for your Chrome extension:

$ yo chrome-extension

Or, if you want to use Compass (and Sass), you can include the --compass flag:

$ yo chrome-extension --compass

This will prompt a series of questions:

  1. What would you like to call the extension? If you leave this empty, it will guess based on the directory you're in, but you probably want to type in a more human readable version, such as "Pupperator".
  2. How would you like to describe this extension? Just type in a description of it here, in a few words.
  3. Would you like to use UI Action? This is where some Chrome extension API lingo comes into play. You can choose "Browser", "Page", or "None", based on whether it's useful for your extension or not.
  4. Would you like more UI Features? This lets you select as many of the available extra features as you'd like to use in your extension.
    • "Options Page" means it will generate a framework for a settings page for your extension, which means that an "Options" link will appear under your extension on the Extensions page.
    • "Content Scripts" means that it will create a sample content script for you, which is just a fancy name for JS that gets run in the context of the actual web page you're viewing (like a bookmarklet or Greasemonkey script), as opposed to as a separate background process
    • "Omnibox" means that it will generate a bit of sample code demonstrating how you can register a keyword with Chrome's Omnibox.
  5. Would you like to use permissions?  This just tells Yeoman if you already know that you're going to be needing any of a few common permissions available to extensions. If you don't immediately know, then you can just leave this empty and add the permissions later, since doing so is dead simple.

After that, you'll see a bunch of "create" statements fly by which means that it's generating files for you, and then you'll see some things getting installed, and then the process should complete, meaning you're ready to develop.

Step 3: Enable your extension

Now that you have some code, you can go ahead and enable it in Chrome.

  1. Open up chrome://extensions
  2. Click "Developer mode"
  3. Click "Load unpacked extension"
  4. Navigate to your extension's directory and click OK
  5. Profit! Your extension should be enabled.

Step 4: Take a look at your code

Before we buckle down and start coding, let's look at what Yeoman gave us. Your exact codebase will likely be slightly different based on what options you chose when generating it, but you should get the idea.

  • .gitignore - As we all know, this lets you tell git which files/paths to ignore. Yeoman ships with some sane defaults for this.
  • .bowerrc - Simple configuration for bower that you can ignore. By default, it just tells bower where to put downloaded modules.
  • .editorconfig - Simple editorconfig for Chrome extensions
  • .jshintrc - Configuration for jshint. Yeoman turns a lot of checks on, some of which you might end up wanting to disable, depending on your personal style.
  • bower.json - Keeps track of modules downloaded using bower.
  • package.json - Generated by npm, this keeps track of your NodeJS modules and their versions.
  • Gruntfile.js - Sets up grunt tasks and what they do, such as telling grunt to run LiveReload as part of "grunt debug", etc. This has good defaults, but you are welcome to edit or create your own tasks.
  • test/ - Includes a framework for creating test runners for your code, using Mocha. Lots to love here, if you're into that, but it's outside the scope of this post.
  • node_modules/ - Self explanatory. Node modules which Yeoman requires go here. Can be ignored by git and regenerated from package.json by running "npm install".
  • app/ - This is your actual Chrome extension
    • bower_components/ - Self explanatory. Downloaded bower components get put here.
    • images/ - Used for your app's icons (which you should replace with custom icons) as well as any other images you might need in your app
    • scripts/ - All of your app's custom JS should go here.
    • styles/ - All of your app's custom CSS (or SCSS if you prefer) should go here.
    • manifest.json - This is the entry point of your extension. It tells Chrome which permissions it needs, what it's called, which files it includes and where, etc. The docs go into much more detail on this file.
    • options.html - If you chose to include options when creating the extension, then this file gets used as your options page. Edit as you wish.
    • popup.html - If you chose to include a browser action when creating the extension, then this file gets used as the popup that appears when you click it. Edit as you wish.

For most extensions, you can get by by only touching app/manifest.json as well as the CSS/JS/HTML files that exist in /app, but it's nice to have an understanding of the other files in case you want to configure things a bit.

Step 5: Start developing

Firstly, you'll want to fire up "grunt" using:

$ grunt debug

Doing so does a few very nice and very helpful things:

  • Automatic CSS compilation for any .scss file in the app/styles directory.
  • LiveReload integration for free (Yeoman bundles its own version of LiveReload), meaning your extension will reload itself whenever you save any changes to it. Otherwise, you have to go to the extensions page and hit reload every...single...time which gets old really fast. CSS changes even get applied to the pages you're viewing automatically, although you still have to refresh those pages to pick up JS changes.
  • JSHint runner, which stops the rest of the process if it finds anything to complain about, forcing you to keep nice, hinted code.
  • Once the process completes, keeps watching your files and starts over the next time you save a change.

Now you're finally ready to write some code. The code that you right depends heavily on what you're trying to do.

If you're doing some fancy stuff behind the scenes that doesn't need to inject as much code into individual pages, you'll probably be looking more at the background.js script (or a renamed version of it). Background scripts get a lot of power which is not available in content scripts, because they are treated more like first class citizens by Chrome's extensions API. Content scripts, for example, can't use the chrome.* API functions (take a moment to skim that link before moving on) that background scripts have access to.

If you're mainly targeting a browser or page action button, you'll be dealing with popup.html and popup.js. This is really quite simple, since the popup is nothing more than a regular old .html file which references CSS or JS as needed, and is displayed in a small window. Regular front end development rules apply here, and you're only limited by your imagination, so you can do fancy stuff like AJAX calls or responsive layouts without jumping through any special hoops. Browser actions also give you the ability to add text in front of the button, using the setBadgeText() function, or do something besides show a popup HTML page when clicked. They're pretty flexible little guys.

If you're trying to alter the behavior of existing web pages, by adding JS or CSS to them, then you're going to be spending most of your time in content scripts. You'll probably want to rename the default "contentscript.js" file (both the file itself and the reference to it in manifest.json), and/or create some new files so that you can better organize your code. Content scripts are very easy to grasp because they literally just run code inside the web pages that you give them access to. The only difference between them and actual JS files included in the page is that content scripts have a couple limitations (see the next section).

Limitations of Content Scripts

Since content scripts seem to be the most commonly edited files in Chrome extension, I'd like to talk a bit about some special things to keep in mind for them.

The main thing that trips people up is that content scripts don't have access to global objects, like the "window" object, or global objects defined by the JS in the page itself. This can be a problem that is tough to work around.

The other stumbling point is the fact that content scripts can't trigger events like regular JS. So if you manually change an input's value, but a "change" event handler doesn't run, and you try to manually trigger the change event, you're out of luck.

In both cases, there's a (hacky, but useful) workaround which involves manually building a <script> tag and adding it to the page. Here's a function to do that:

  1. /**
  2. * Builds a script tag with the given JS code and injects it into a page.
  3. *
  4. * This allows us to run JS that isn't strictly allowed in Chrome extension,
  5. * such as .trigger()ing things like click and change, or accessing global vars.
  6. *
  7. * @param {string} jsString
  8. */
  9. var injectJs = function(jsString) {
  10. var s = document.createElement('script');
  11. s.textContent = jsString;
  12. s.onload = function () {
  13. this.parentNode.removeChild(this);
  14. };
  15. document.head.appendChild(s);
  16. };

Just put the JS string you need to inject into a variable and run that function to dump it into the page.

Installing components/libraries

If you find that you need to pull in some commonly used JS or CSS library, then "bower" (which Yeoman includes by default) is your friend. Search for what you need and if bower has a package for it, then you can add it to your extension using:

bower install thingname --save

Then you can just point to the files that you need in the app/bower_components directory. You can also ignore these files with git so that you're not using git to keep track of 3rd party dependencies. Instead, just let git track bower.json (which itself tracks version dependencies), so anyone can clone the repo and run "bower install" at any time to pull everything in.

Step 6: Packing and sharing your extension

Once you are happy with your extension and you deem it ready to upload to the Webstore, you need only run one simple command:

$ grunt build

This will do a lot of things, such as minifying your code, hinting your JS, bumping the version in your manifest.json, etc., but the important end result is that you get a .zip file dump into the package/ directory which is ready to upload to the Webstore.

Once you have that, mosey on over to the Webstore Developer Dashboard, click the "Add New Item" button, and upload your file.

After that, you'll have to enter a bunch of information about it and upload at least one screenshot, but that's all that stands between you and publishing your extension to the world.

With that, you should be off to the races. The Chrome extension API docs should pick up where this posts leave off, and they are well written and helpful, so make that your first stop for questions. If you are interested in learning more about using Grunt, learn from the pro, Steven Merrill in his blog post "Combining Tasks with Grunt."

Happy extension writing!

Mike Crittenden

Software Architect