Over a million developers have joined DZone.

Javascript Can Boost Your Daily Productivity — A DIY Guide to Writing Your Own Chrome Extension

· Web Dev Zone

Start coding today to experience the powerful engine that drives data application’s development, brought to you in partnership with Qlik.

Every day I find myself in need of tracking four things: Github pull requests, Flowdock messages, continuous integration build status (by Travis-ci), and emails.

When I discovered a cool Chrome extension for Gmail, I realized the potential extensions have in increasing my productivity. I decided to search for more extensions that will do the same. 

Unfortunately, it was not an easy task.

Either an extension did not exist or it did not display the data in a way that helped much.

However, all the services I use have an API that is accessible to me. So, I decided to take a small journey and investigate how hard would it be to write my own Chrome extension that would suit my needs.

Turns out that after a small learning curve you can feel right at home since chrome extensions use JS, HTML, and CSS. Plus, the Chrome API is very easy and simple to get started with.

While there are many ways to actually write an extension, I will focus on one that suited me best and only mention the others briefly.

We will use the Flowdock’s example for the sake of this post. The code is available at: https://github.com/guy-mograbi-at-gigaspaces/flowdock-chrome-extension

Here at Cloudify we've actually done some more work on Flowdock.  You can find the code on Github.

The Three Pages of a Chrome Extension

Every Chrome extension has three pages available for its implementation:

  • Popup - the page displayed when you press the extension icon
  • Options - the page displayed when you right click the icon and click "options"
  • Background - a page which simply runs in the background and is only accessible from the "manage extensions" section in chrome by clicking the "inspect <your background page name>"

The three pages can communicate by messages, and keep data by using the Chrome API (or some online service).

Choose Your Favorite Toolset

My favorite combo is Angular, Sass, and Grunt, but you can actually use anything you like.  (We've  actually spoken about this toolset in the past in our Javascript workflows post).

I also really like Yeoman and its generators, they really help kickstart a project, so I wrote one for my future Chrome extensions. It is available at: https://github.com/guy-mograbi-at-gigaspaces/generator-angular-chrome-extension

I also used the following libraries:

  • Materialize.css for its exquisite taste in design and ease of use. Thanks materialize!
  • Lodash for processing data

I strongly recommend using Angular as it saves tons of time thanks to its amazing two way binding, plus it is really easy to set up for small projects.

Define Your Manifest File (manifest.json)

In order to have a full work cycle, you first need to declare your files in the extension manifest.

    "name": "my app name",
    "version": "0.0.374",
    "manifest_version": 2,
    "description": "my app description",
    "icons": {
        "16": "images/icon-16.png",
        "19": "images/icon-19.png",
        "38": "images/icon-38.png",
        "128": "images/icon-128.png"
    "default_locale": "en",
    "background": {
        "page": "background.html"
    "permissions": [
    "options_page": "options.html",
    "browser_action": {
        "default_icon": {
            "16": "images/icon-16.png",
            "19": "images/icon-19.png",
            "38": "images/icon-38.png",
            "128": "images/icon-128.png"
        "default_title": "my first chrome extension",
        "default_popup": "popup.html"

Not much happening here, pretty generic. The important thing to note is the definition of the three files mentioned above: options, popup, and background.

The Popup Page (popup.html)

<!doctype html>
<html ng-csp ng-app="myapp">
    <meta charset="utf-8">
    <!-- build:css styles/vendor.css -->
    <link href="bower_components/materialize/dist/css/materialize.css" rel="stylesheet"/>
    <!-- endbuild -->
    <!-- build:css styles/main.css -->
    <link href="styles/main.css" rel="stylesheet">
    <!-- endbuild -->
  <body id="popup" ng-controller="PopupCtrl">

  <div ng-repeat="msg in page track by $index" class="card-panel teal lighten-5">
      <a class="blue-text lighten-1" target="_blank" href="http://www.flowdock.com">{{msg.message.content}}</a>
    <!-- build:js scripts/vendor.js -->
    <script src="bower_components/angular/angular.js"></script>
    <!-- endbuild -->

    <!-- build:js scripts/popup.js -->
    <script src="scripts/chrome.js"></script>
    <script src="scripts/popup.js"></script>
    <!-- endbuild -->

As you can see, I have set up a regular HTML file with an Angular app. There are also comments for the Grunt build process to minimize files.

The special part is in:

  • scripts/chrome.js - this is a mock for chrome’s api which only works if the API is unavailable.
  • scripts/popup.js - this is the actual Angular app

The page will paint messages from Flowdock on the page in a simple list.

Background and options HTML files will actually look the same, so I will allow myself to skip them. (the code is available on Github).

The popup.js Script

'use strict';

angular.module('myapp', ['chrome']);

angular.module('myapp').controller('PopupCtrl', ['$http', '$scope', '$timeout', '$log', 'chrome', function PopupCtrl($http, $scope, $timeout, $log, chrome) {
    function onUpdate(request, sender, sendResponse) {
        if (request.type === 'data') {
            $scope.page = request.data;


    $http.get('/dev/mockData.json').then(function success(result){
        onUpdate( { 'type' : 'data', data:result.data });

    chrome.sendMessage({type: 'update-please'});

On the surface this looks like a regular Angular app, but there are several interesting things going on here:

  • We are using the Chrome API through a wrapper, injected as an Angular service. This service will actually work outside the extension environment as it mocks the API when needed.
  • We are listening for messages and then update the data when a message with data arrives.
  • We send a message requesting to get an update—this code will run once a popup is opened.
  • We are also loading mockData for development.

So far, things are pretty straightforward. Let's have a look at the options script.


'use strict';


angular.module('myoptions').controller('MyOptionsCtrl',['chrome','$log', '$scope', '$timeout','$http',function(chrome,$log, $scope, $timeout, $http){

    $scope.details = { };
    // Saves options to chrome.storage
    function save_options() {
        try {
            $scope.details.version = new Date().getTime();
            $log.info('saving', controller.details);
            $log.error('unable to save',e);

    function restore_options() {
        try {
            // Use default value color = 'red' and likesColor = true.
            chrome.readConfig(null, function (items) {
                $log.info('items is', items);
                $scope.details = items;
            $log.error('unable to restore',e);

    $scope.save = save_options;


Thanks to Angular taking care of two-way binding, all we are left with is loading the data when the page loads, and saving it with a save click.

The HTML will define some input fields that will save stuff on scope.

The background.js Script

'use strict';

angular.module('background', ['flowdock', 'chrome']);

angular.module('background').controller('BackgroundCtrl', ['chrome', 'Flowdock', '$scope', '$interval', '$log', function BackgroundController(chrome, Flowdock, $scope, $interval, $log) {

    chrome.setDefaultConfig({'version': 1, 'apiToken': chrome.getParameterByName('api_token')});

    $scope.config = {};

    chrome.readConfig().then(function (config) {
        if (config.version !== $scope.config.version) {
            $scope.config = config;

    function sendData() {
        if ( $scope.data ) {
            chrome.setBadgeText({text: $scope.data.length});
            chrome.sendMessage({type: 'data', data: $scope.data});

    $scope.$watch('data', sendData, true);

    chrome.onMessage(function (request) {
        if (request.type === 'update-please') {

    $scope.getData = function () {
        var flowdock = new Flowdock($scope.config);
        flowdock.getNotifications().then(function( result ){
            $scope.data = result.data;

    $interval($scope.getData, 1000 * 60 * 2); // poll every 10 minutes.. there's a very low rate limit


The background.js differs from the others in that it sets an $interval to run every X time.

What Chrome does is starts a process for each extension and runs the background page on that process. So, if your background page does a lot of computations, you will feel it. Make sure to make it do something every now and then, but mostly rest.

I have set it to get my messages from Flowdock every 10 minutes... sounds harmless to me.

The method used to read Flowdock’s message is out of the scope for this post. you are welcome to check the code in Github.

The Chrome Extension API

So far, all we’ve seen are regular HTML and JS files that given a magical API for messages, badge text and save/read configuration look pretty normal.

The glue to make everything work together is the Chrome service.

'use strict';

angular.module('chrome', []);

angular.module('chrome').service('chrome',['$timeout','$q',function ChromeService($timeout, $q ){

    // use callback because multiple times
    this.onMessage = function (callback) {
        try {
                function (request, sender, sendResponse) {
                    $timeout(function () {
                        callback(request, sender, sendResponse);
                    }, 0);
        }catch(e){ console.log('added listener');}

    this.setBadgeText = function(opts){
            if ( opts.text !== null && typeof(opts.text) !== 'undefined'){
                opts.text = '' + opts.text; // convert to string

        }catch(e){ console.log('setting badge', opts, e); }

    this.sendMessage = function( opts ){
        try {
            console.log('sending message',opts);

    var me = this;

    this.getParameterByName = function(name) {
        name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
        var regex = new RegExp('[\\?&]' + name + '=([^&#]*)'),
            results = regex.exec(location.search);
        return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));

    this.setDefaultConfig = function(defaults){
        me.defaults = defaults;

    this.saveConfig = function(config){
        var deferred = $q.defer();
        try {
            chrome.storage.sync.set(config, deferred.resolve);
            console.log('saving config', config);
        return deferred.promise;

    this.readConfig = function(){
        var deferred = $q.defer();
        try {
            // Use default value color = 'red' and likesColor = true.
            chrome.storage.sync.get(null, function(data){
        return deferred.promise;


This file basically wraps the Chrome API and defaults to either log prints or some naive implementation.

For example, saveConfig will keep data in Chrome using chrome.storage.sync.set, but if that’s not available it will use the local storage. The same goes for readConfig.

This gives me a way to experience an almost complete flow in my extension while running it from a regular HTTP server.

The Gruntfile.js

I will spare you the reading of Grunt’s lengthy configuration. The Gruntfile basically does the following:

  • Lints my js files with jshint
  • Compiles my sass files when they change
  • Minimizes my javascripts
  • Copies files to dist folder
  • Compresses dist folder to zip

It also updates the version in the manifest file.

Basically, everything it does on your everyday web project, plus some manifest maintenance for Chrome.

Putting it All Together

Once you finished writing your extension, all you need to do is compress it, open the “manage extensions”, then drag and drop your zip file there, and you will get a new icon on your extensions bar.


If you run into any problems, you can easily debug your scripts by inspecting each page.

Popup and options are pretty straight forward to inspect, while background is available for inspection from the "extensions" section in Chrome (as shown above).

Pain and Gain

This task is all gain and no pain. Perhaps some pain in making the Flowdock API work, but that’s actually my fault!

Chrome’s API is so intuitive, by putting an effort of 10 hours of learning curve—and that’s a serious over-exaggeration now—and a couple of hours of coding the extension, you save yourself hours of work every week.

And, now that I have a generator and working code samples, case closed! Just give me more things to track and monitor.

Create data driven applications in Qlik’s free and easy to use coding environment, brought to you in partnership with Qlik.

javascript,chrome extension

Published at DZone with permission of Cloudify Community, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}