Build and Publish a Chrome Extension | Primathon

Chrome extensions are everywhere. They change the whole chrome browsing and development experiences like using adblocker to block ads on any website or dark mode everywhere or website CSS inspector. Ever wondered how to build such extensions? Well, it is very easy to get started on building an extension.

We are going to build an extension that will save any web URL and store them in chrome storage.

What is a chrome extension:

Extensions are small software programs that customize the browsing experience. They enable users to modify Chrome functionality and behavior to individual needs or preferences. An extension should complete a single task like saving the web links or show unread emails. It can contain multiple components and a long-range of functionalities.

They are built on web technologies such as HTML, JavaScript, CSS and the chrome APIs.

Let’s get started on building an extension.

Step 1: Adding manifest.json

Every extension has a JSON formatted manifest file called manifest.json, which acts as an entry point of extension.
It provides important information like extension version, user permissions, external resources. So, it is a registration file for everything we add in the extension like using a remote server file or using chrome storage.

Create a directory and add the manifest.json file in the root of it.

{
    "name": "Article Saver",
    "version": "1.0.0",
    "description": "Extension to save your links or articles",
    "manifest_version": 2
 }

This is the basic configuration stating the name of extension, description, version, and manifest_version which will be 2 as described by chrome.

Step 2: Add this extension in chrome

This basic manifest file can be added to chrome extensions in development mode by following 3 easy steps:

  1. Go to extension home in your chrome browser.
  2. Enable developer mode by clicking the toggle switch on the top right corner.
  3. Click the load unpacked button in the top header and select the extension directory.
Snapshot 1: Adding extension in chrome developer mode

Step 3: Adding our User Interface:

Create an HTML in the root of the directory and link a script file to it.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Article Saver</title>
    <style>
      body {
        min-width: 300px;
        height: 200px;
      }
      button {
        border: none;
        width: 100px;
        height: 30px;
        background-color: rgb(1, 100, 139);
        color: white;
        margin-bottom: 10px;
      }
      section {
        display: flex;
        flex-direction: column;
        align-items: flex-start;
      }
      a {
        width: 100%;
        text-overflow: ellipsis;
        white-space: nowrap;
        overflow-x: hidden;
        color: rgb(1, 100, 139);
        text-decoration: unset;
        padding: 5px 0;
      }
      a:hover {
        background-color: rgb(228, 228, 228);
      }
      </style>
</head>
<body>
    <button id="link">Save link</button>
    <section id="list"></section>

    <script src="popup.js"></script>  
</body>
</html>

Register this HTML in manifest.json. Here we need to use browser action. Browser action is a button that your extension adds to the browser’s toolbar. The button has an icon and optionally popup. Now we can listen to any click on it, that will eventually open popup.

{
   ... 
   "browser_action": {
      "default_popup": "popup.html"
    }
}

Step 4: Add Icons

We can easily add our custom icon rather than default provided by chrome. So, there is are three places for an extension where we can add icons.

  • Toolbar
  • Extension home
  • Chrome web store

To add icons we need three sizes of icons 16*16, 48*48, and 128*128. Then references these icons in the manifest.

    "browser_action": {
        "default_popup": "popup.html",
        "default_icon": {
            "16": "icon1.png",
            "48": "icon2.png",
            "128": "icon3.png"
        }
    },
    "icons": {
        "16": "icon1.png",
        "48": "icon2.png",
        "128": "icon3.png"
    },

To reflect the latest changes we don’t have to remove the extension and reload it again. We can directly reload the extension using the refresh icon on the extension home page.

Snapshot 2: Reload extension

Then click on our extension, a popup will show the UI containing a save button.

Snapshot 3: Initial User Interface

At this stage, this extension is not saving any link.
Next, we will add the logic to save links and store them in chrome storage.

Popup script [concept]

  • popup.js is a script file that runs in the context of our UI
  • It can send messages to the background script.
  • It can also listen to messages from the content script.

    Below is the basic code for adding an event listener to the button which will store dummy links.
document.getElementById("link").addEventListener("click", () => {
    const linkContainer = document.getElementById("list");
    const newLink = document.createElement('a');
    newLink.href = "demo link";
    linkContainer.appendChild(newLink);
});

Background script [concept]

  • The background script listens to browser events and runs in the context of the chrome.
  • It has access to every chrome API like browserAction, tabs, runTime, storage, etc. but cannot access the current page.
  • It listens when a content script or our extension sends a message.
  • It can be used for performing a network request.

In our extension, if we didn’t add HTML and then you click on our extension button we could listen to that click via browserAction API in background script.

Register background.js in the manifest.json:

{
    ...
    "background": {
        "scripts": ["background.js"]
    },
}

Content script [concept]

  • This script runs in the context of the web page.
  • It has access to the current page and some chrome APIs like runTime, storage, but not all the APIs.
  • It can listen for a message from the background script.
  • It can send messages to background script or any other part of extension like popup.js.

Register content.js in the manifest.json:

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

Message passing [concept]

Since popup script can run only in the context of our extension UI, background script can’t access the current page context, and the content script has limited API access. We need some kind of communication between these parts so that can use these script to make our extension which will save our articles and sync them.

In extension context, we can communicate between background.js, popup.js and content.js easily using runTime API.

We will use runtime.sendMessage for sending message and runtime.onMessage for listening of messages.

Snapshot 4: Scripts context and message passing:

Step 5: Steps to our logic for saving links:

  • When the user clicks on the save link button: we will listen for that event.
  • Then we will create an anchor tag.
  • Then to get the title and URL of the current page, we will send a message from popup.js to background.js
  • Then background.js will use tabs API to get the id of the active tab. Then we will send a message to the content script to get the URL and title of the active/current page.
  • The content script runs in the context of the web page, so it will give us the title and URL. From here we will send a message to popup.js which can listen for messages from content.js.
Snapshot 5: Message passing between all three scripts
Final code for all three scripts.
1. Popup.js
document.getElementById("link").addEventListener("click", () => {
    const linkContainer = document.getElementById("list");
    const newLink = document.createElement('a');
    chrome.runtime.sendMessage({message: "get_url"});
    chrome.runtime.onMessage.addListener((request) => {
        if( request.message === "updated_url" ) {
            newLink.setAttribute('href', request.url);
            newLink.setAttribute('target', "_blank");
            newLink.innerText = request.title;
        }
      }
    );
    newLink.href = "fetching...";
    linkContainer.appendChild(newLink);
});
2. Background.js
chrome.runtime.onMessage.addListener((request) => {
    if (request.message === "get_url") {
        // Send a message to the active tab
        chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
            var activeTab = tabs[0];
            if (activeTab) {
                chrome.tabs.sendMessage(activeTab.id, { "message": "fetch_current_url" });
            }
        });
    }
});
3. Content.js
chrome.runtime.onMessage.addListener((request) => {
  if (request.message === "fetch_current_url") {
    const currentUrl = location.href;
    const currentTitle = document.title;
    chrome.runtime.sendMessage({ "message": "updated_url", "url": currentUrl, title: currentTitle });
  }
}
);

If we click the extension icon popup will open. Now click on the save button will save the current web URL to our list.

Snapshot 6: Extension adding current URL in our UI

Right now if we close the tab and want to use the extension on any other page we can’t see the last stored link and also if you close the extension links list goes away from UI.
For that, we will use storage to store and sync our data across chrome.

Step 6: Storage

We can use Chrome Storage API to store, retrieve, and track changes to user data.
We will use storage.sync to store and retrieve the data. It also provides us the automatically synced data feature in chrome. It is asynchronous i.e non-blocking and faster than localStorage API.

First, register storage permission in the manifest.json.

{
   ...    
   "permissions": ["storage"],
}

Note: All other permissions like access active tab or bookmark permission are declared in this array.

Below is the updated popup.js to get the last stored links and sync them in chrome.

document.getElementById("link").addEventListener("click", () => {
    // get the list element and create anchor tag 
    const linkContainer = document.getElementById("list");
    const newLink = document.createElement('a');

    // send message to background.js
    chrome.runtime.sendMessage({ message: "get_url" });

    // listen for message from content.js
    chrome.runtime.onMessage.addListener((request) => {
        if (request.message === "updated_url") {
            newLink.setAttribute('href', request.url);
            newLink.setAttribute('target', "_blank");
            newLink.innerText = request.title;

            // get the existing url and push the new link in the array
            chrome.storage.sync.get(['urlList'], function (result) {
                let updatedUrlList;
                const urlObject = { url: request.url, title: request.title };
                if (result.urlList && result.urlList.length > 0) {
                    updatedUrlList = result.urlList.concat([urlObject]);
                } else {
                    updatedUrlList = [urlObject];
                }

                // set the updated array in storage
                chrome.storage.sync.set({ 'urlList': updatedUrlList });
            });
        }
    }
    );

    newLink.href = "fetching...";
    linkContainer.appendChild(newLink);
});

// get data from storage to display existing urls
chrome.storage.sync.get(['urlList'], function (result) {
    const existingUrls = result.urlList;
    if (existingUrls && existingUrls.length > 0) {
        existingUrls.forEach(el => {
            const linkContainer = document.getElementById("list");
            const newLink = document.createElement('a');
            newLink.setAttribute('href', el.url);
            newLink.setAttribute('target', "_blank");
            newLink.innerText = el.title;
            linkContainer.appendChild(newLink);
        });
    }
});

Yeah! We have our working extension ready to save our important links and store them in chrome. By building this extension we have covered all the fundamentals of chrome extension.

Step 7: Publish it in the chrome web store

Our final directory structure will look like this:

Snapshot 7: Final Directory Structure

Make the zip of the root folder and head over to chrome developer console. If you visiting the first time it will ask for one time registration fee. After that, you can click on the “New Item” button and select your zip file or you can drag and drop too.


In 7 steps, we have built and published a chrome extension. You can find the whole code on GitHub.
For more on Chrome extensions, you can visit Chrome docs.

For more tech blogs and information do follow us on LinkedIn and Twitter.

Thanks for reading. Enjoy!

Leave a Reply

Your email address will not be published. Required fields are marked *