How to build browser extension

How to build chrome extension

In this article, I am going to provide you with all the information that you need in order to create browser extensions from important components of browser extensions to designing a simple todo browser extension and implementing it using plain html, css and js.

In a browser extension there are several independent component that work together to create a certain feature available for users. Each component has its capabilities and constraints.

Components of Browser extension

Popup

It is basically the popup window that opens when you click the extension icon aka action button. This popup is similar to other web pages only difference is it does not have the url bar plus it has additional access to Web Extension API’s. When action button is clicked the browser renders the popup and when it is closed the browser destroys It. So performing a long running request / task from popup is not ideal.

Options

It is similar to web page with additional access to Web Extension API’s. You can run a long running request / task on options page. It is usually used to provide user with options to change the settings / behaviour of your extension.

Content Script

It is basically used to inject html/css/js, read the DOM, modify DOM of the web page that the user is visiting. e.g grammerly extension injects widget to your browser text editors (could be textarea field or other fields). It has access to the DOM and can manipulate it. The only limitation for this is it has limited access to Web Extension API’s. So in order e.g to make network request it has to go through the background script as well as for authenticating user it has to go through background script.

Background script

It is aka nerve agent of browser extension since it is used to pass messages between different components of browser extension. It used to handle different events within your extension from installed, update to delete events. It is used to make network request on behalf of content scripts.

Devtools page

It's the only component of browser extension that has access to DevTools API’s which is mostly used by developers only. e.g frameworks like react, vue to provide additional info about the rendered web application that makes debugging and profiling easier.

So, that’s it about the components of browser extension now lets discuss about the TODO application that we’re going to build

  • CRUD todo in popup page;

  • Options page provide options to modify the behaviour of extension e.g changing font size, colors of completed task, changing icons etc.. Also possible to add tasks from extension page;

  • Use BG script to open options page when extension installed for the first time as well as for surveying when user deletes the extension;

So that’s the goal for this extension we’re not going to make use of any build tools or framework it is entirely going to be plain html,css and js.

Implementing a simple todo application

cd /go-to-your-project-folder
mkdir todo-browser-extension
code .

Create a simple manifest.json

touch manifest.json

Add the following data to it;

{
  "manifest_version": 3,
  "name": "Todo Extension",
  "version": "1.0.0",
  "description": "A simple todo extension"
}

Manifest version specifies which manifest you’re going to use. Currently the two major options are mv2 and mv3.

Now go to your browser extensions manager and turn on the developer mode.

chrome://extensions/

And then select a load unpacked options and choose the /todo directory.

Now you should have the following;

Notice title and description come from the manifest file also the extension icon is by default first character of the extension name you can modify it from manifest file as well.

Now whenever we make updates to the extension code we need to reload them using the reload icon to bottom right side.

Manifest file is the entry point of your browser extension that lists all the different components of your browser from background scripts, content scripts, options page, popup, and devtools. And other crucial info like required permissions for the extension etc.. Check out mdn reference for all fields of manifest.json file

Now let’s get back to building todo;

create a folder named popup and inside create a index.html and script.js file;

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="./style.css" />
    <title>TODO App</title>
  </head>
  <body style="background: gray; color: white; min-width: 500px">
    <main>
      <div>
        <input
          type="text"
          name="todo"
          placeholder="add your todo"
          id="todoInput"
        />
        <input type="button" value="add" id="addBtn" />
      </div>

      <ul id="todoList"></ul>

      <button id="openOptionsPage">Open options page</button>
    </main>
    <script src="./script.js"></script>
  </body>
</html>

We’ll need the storage permission to save todos, so update the manifest.json to include that

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

Now add script.js file which will do most of the task for us

let todos = [];

document.addEventListener("DOMContentLoaded", function () {
  chrome.storage.sync.get(["fontSize", "bgColor"], function (data) {
    if (data.fontSize) {
      document.body.style.fontSize = data.fontSize;
    }
    if (data.bgColor) {
      document.body.style.backgroundColor = data.bgColor;
    }
  });

  loadTodos();
  document.getElementById("addBtn").addEventListener("click", addTodo);
  document.getElementById("openOptionsPage").addEventListener("click", () => {
    chrome.runtime.openOptionsPage();
  });
});

function loadTodos() {
  chrome.storage.sync.get("todos", function (data) {
    if (data.todos) {
      todos = data.todos;
      renderTodos();
    }
  });
}

function saveTodos() {
  chrome.storage.sync.set({ todos: todos });
}

function renderTodos() {
  const todoList = document.getElementById("todoList");
  todoList.innerHTML = "";
  todos.forEach((todo, index) => {
    const li = document.createElement("li");
    li.textContent = todo.value;

    const deleteButton = document.createElement("button");
    deleteButton.textContent = "Delete";
    deleteButton.addEventListener("click", () => deleteTodo(index));

    const editButton = document.createElement("button");
    editButton.textContent = "Edit";
    editButton.addEventListener("click", () => editTodo(index));

    li.appendChild(deleteButton);
    li.appendChild(editButton);
    todoList.appendChild(li);
  });
}

function addTodo() {
  const todoInput = document.getElementById("todoInput");
  const todoText = todoInput.value.trim();

  if (todoText !== "") {
    const todo = {
      value: todoText,
      completed: false,
      id: Date.now(),
      createdAt: new Date(),
    };
    todos.push(todo);
    saveTodos();
    renderTodos();
    todoInput.value = "";
  } else {
    alert("Please enter a task!");
  }
}

function deleteTodo(index) {
  todos.splice(index, 1);
  saveTodos();
  renderTodos();
}

function editTodo(index) {
  const newTodoText = prompt("Edit the task:", todos[index].value);
  if (newTodoText !== null) {
    todos[index].value = newTodoText.trim();
    saveTodos();
    renderTodos();
  }
}

Reload your chrome extension and you should be able to perform the CRUD operations.

Now lets’ add the options page where we’ll provide user to the option to change the font size, background color of the popup pages. We’ll use storage to have a preferences for our application.

Again create a folder options and index.html, script.js files inside of it;

Also update the manifest.json to include options page into extension;

"options_ui": {
    "page": "options/index.html",
    "open_in_tab": true
  },
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Todo List Options</title>
  </head>
  <body>
    <h1>Todo List Options</h1>
    <label for="fontSize">Font Size:</label>
    <select id="fontSize">
      <option value="16px">Small</option>
      <option value="25px">Medium</option>
      <option value="35px">Large</option></select
    ><br /><br />
    <label for="bgColor">Background Color:</label>
    <input type="color" id="bgColor" /><br /><br />
    <button id="saveBtn">Save</button>

    <script src="./script.js"></script>
  </body>
</html>
document.addEventListener("DOMContentLoaded", function () {
  loadPreferences();
  document.getElementById("saveBtn").addEventListener("click", savePreferences);
});

function loadPreferences() {
  chrome.storage.sync.get(["fontSize", "bgColor"], function (data) {
    if (data.fontSize) {
      document.getElementById("fontSize").value = data.fontSize;
    }
    if (data.bgColor) {
      document.getElementById("bgColor").value = data.bgColor;
    }
  });
}

function savePreferences() {
  const fontSize = document.getElementById("fontSize").value;
  const bgColor = document.getElementById("bgColor").value;
  chrome.storage.sync.set(
    { fontSize: fontSize, bgColor: bgColor },
    function () {
      alert("Preferences saved successfully!");
    }
  );
}

Finally let’s add background.js file which will initialize the fontSize and bgColor on installed event of extension.

chrome.runtime.onInstalled.addListener(function () {
  chrome.storage.sync.set({ fontSize: "16px", bgColor: "gray" });
});

That’s it now we’ve a simple todo extension application.

Did you find this article valuable?

Support Engineering with Bishal by becoming a sponsor. Any amount is appreciated!