Getting Started with Browser Extensions: A Quick Guide


This week, I’ve had the pleasure of diving into browser extensions, particularly focusing on creating a tool to help users identify potential bias on any webpage.

See the “finished” extension here (feel free to install and give it a like):

The process has been mostly straightforward, so I wanted to share a quick guide on how to get started with your first browser extension.

TLDR; here is a repo with a simple example of a browser extension: Browser Extension Template

The How: Building Your First Extension

Getting Started

To kick things off, you can use a simple and easy-to-follow template available on GitHub: Browser Extension Template. This template includes the essential files needed for a basic extension which showcases how to communicate between the popup and the current webpage.

Sample browser extension: watch for when text is selected on the current webpage

Key Files Explained

A browser extension generally contains the following key files:

  • manifest.json – Tells the browser what files are included with your extension and what permissions your extension needs.
  • popup.html – Optional. If you need to display a popup window when someone clicks your extension button in the toolbar.
  • content.js – Optional. The js to be injected into the current browser page so that you can access the DOM.
  • background.js – Optional. A place to add code that can respond to events, even when your popup is not showing.

The github repo contains more files than the ones listed above.

  1. manifest.json: This is the blueprint of your extension. It includes metadata such as the extension’s name, version, description, and the permissions it requires.
  "manifest_version": 3,
  "name": "Browser Extension Template",
  "version": "0.1.0",
  "description": "This is a template extension project for a browser extension.",
  "icons": {
    "16": "icon.png",
    "32": "icon.png",
    "48": "icon.png",
    "128": "icon.png"
  "side_panel": {
    "default_path": "popup.html"
  "permissions": ["activeTab", "sidePanel"],
  "action": {
    "default_title": "Browser Extension Template",
    "default_popup": "popup.html"
  "background": {
    "service_worker": "background.js",
    "type": "module"
  "web_accessible_resources": [
      "matches": ["<all_urls>"],
      "resources": ["icon.png"]
  "content_scripts": [
      "all_frames": false,
      "matches": ["<all_urls>"],
      "js": ["content.js"],
      "css": ["popup.css"]
  1. popup.html: This is the HTML file for the popup interface that appears when the extension icon is clicked.
<!DOCTYPE html>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <title>Page Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" type="text/css" media="screen" href="popup.css" />
    <script src="popup.js" type="module"></script>
  <body style="min-height: 400px">
    <button id="clickme">
      Click me to scrape all content from current page
    <div class="response-text" id="response"></div>
  1. popup.js: This JavaScript file handles the logic for the popup interface.
(async () => {
    console.log('popup.js loaded');

    const getTabSelectedText = async () => {
        console.log('Popup: getTabSelectedText clicked');
        // Send a message to the content script to get the selected text
        const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
        if (tabs.length === 0) return; // Exit if no active tab found
        chrome.tabs.sendMessage(tabs[0].id, { type: 'GET_PAGE_CONTENT' }, (response) => {
            console.log('Popup: Received response:', response);
            const responseElement = document.getElementById('response');
            if (responseElement) responseElement.innerText = response?.content || 'No content';

    document.addEventListener('DOMContentLoaded', () => {
        // Add event listener for the getTabSelectedText button
        const clickMeButton = document.getElementById('clickme');
        if (clickMeButton) clickMeButton.addEventListener('click', getTabSelectedText);

    chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
        if (request.type === "SELECTED_TEXT") {
            // The page html content
            const content = request.content;
            const responseElement = document.getElementById('response');
            if (responseElement) responseElement.innerText = content;
        return true; // Indicate that sendResponse will be called asynchronously
  1. content.js: This script runs in the context of the webpage and contains the logic for detecting bias.
  (async () => {
    "use strict";

    console.log('browser extension content.js loaded');

    const sendSelectedText = () => {
        const selectedText = window.getSelection()?.toString();
        if (selectedText) {
            console.log('SENDING SELECTED_TEXT message:', selectedText);
            chrome.runtime.sendMessage({ type: 'SELECTED_TEXT', content: selectedText }, (response) => {
                if (chrome.runtime.lastError) {
                    console.error('Error sending message:', chrome.runtime.lastError.message);

    document.addEventListener('mouseup', function (event) {
        console.log('mouseup event');

        function (request, sender, sendResponse) {
            if (request.type === "GET_PAGE_CONTENT") {
                // Extract the page's text content and the current selection
                const content = document.body.innerText;
                const selectedText = window.getSelection()?.toString() || '';
                sendResponse({ type: 'SEND_PAGE_CONTENT', content, selectedText });
            return true; // Keep the message channel open for asynchronous response
  1. background.js: This script handles background tasks and manages the extension’s lifecycle.
   // Handles background tasks for the extension
(() => {
    console.log('background.js loaded');

    // Ensure the sidePanel API is available before attempting to use it
    if (chrome.sidePanel) {
        chrome.runtime.onInstalled.addListener(() => {
            // Automatically open the side panel when the extension's action is clicked
            chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
    } else {
        console.warn('This version of Chrome does not support sidePanel API.');

    // Define a function to handle incoming messages
    const handleMessage = (request, sender, sendResponse) => {
        console.log('Message received:', request);
        switch (request.type) {
            case 'SELECTED_TEXT':
                // Process the selected text, e.g., save it or call an API
                console.log('Background: Selected text:', request.content);
            // Add more cases as needed
                console.warn('Unhandled message type:', request.type);
        // Indicate that sendResponse will be called asynchronously
        return true;

    // Listen for messages from content scripts or popup

Build & Debug

To run the code, just follow these steps.

  1. Open your browser and navigate to chrome://extensions/.
  2. Enable Developer Mode.
  3. Click on Load unpacked and select the cloned repository of your extension.
  4. To make changes, edit the relevant JS or HTML files, then refresh the extension and the webpage to see your updates in action.
  5. To see debug messages, open the Chrome dev tools for the target webpage, popup and/or service/worker.

Store Submission

  1. Update the manifest.json file with the appropriate details (name, version, description, etc.).
  2. Zip the contents of your extension’s directory.
  3. Go to the Chrome Web Store Developer Dashboard.
  4. Click on Add new item.
  5. Upload the zip file.
  6. Add the necessary details and click on Publish.


Creating a browser extension can be a fun and rewarding project, opening up numerous possibilities to enhance user interactions on the web. With this guide, you should have a solid foundation to build your own extensions and even publish them for others to use. Happy coding!