web browser

How to Create a Browser Extension

Creating a browser extension is a fun way to add new features to your web browser, like changing how pages look, adding buttons, or automating tasks. This guide will walk you through how to build a basic extension that works in both Chrome and Firefox.

Project Structure

my-extension/
├── manifest.json
├── icons/
│   ├── icon16.png
│   ├── icon48.png
│   └── icon128.png
├── popup/
│   ├── popup.html
│   ├── popup.css
│   └── popup.js
├── background.js
├── content.js
└── polyfill.js       ← optional but recommended

Manifest File (manifest.json)

{
  "manifest_version": 3,
  "name": "My Cross-Browser Extension",
  "version": "1.0",
  "description": "A correct, Manifest V3 extension for Chrome and Firefox.",
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "permissions": [],
  "host_permissions": [],
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "popup/popup.html",
    "default_icon": {
      "16": "icons/icon16.png",
      "48": "icons/icon48.png"
    }
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ],
  "web_accessible_resources": [
    {
      "resources": ["popup/popup.html", "popup/popup.js"],
      "matches": ["<all_urls>"]
    }
  ]
}

Key Notes:

  • default_popup is set → action.onClicked will not fire on icon click.
  • If you want to listen to clicks, remove default_popup and use chrome.action.onClicked.
  • web_accessible_resources lists specific files, not wildcards.

Popup UI (popup/popup.html, popup.css, popup.js)

popup/popup.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <link rel="stylesheet" href="popup.css" />
  <!-- Optional: Load polyfill if using webextension-polyfill -->
  <script src="../polyfill.js"></script>
</head>
<body>
  <div class="container">
    <h1>Extension Popup</h1>
    <button id="greet">Say Hello</button>
    <button id="triggerBg">Trigger Background</button>
  </div>
  <script src="popup.js"></script>
</body>
</html>

popup/popup.css

body {
  width: 300px;
  padding: 16px;
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.container {
  text-align: center;
}
button {
  margin: 8px;
  padding: 8px 16px;
  font-size: 14px;
}

popup/popup.js

document.getElementById('greet').addEventListener('click', () => {
  // ✅ Safe: alert works in popup context
  alert('Hello from the popup!');
});

document.getElementById('triggerBg').addEventListener('click', () => {
  // Send message to background script
  browser.runtime.sendMessage({ type: 'open_popup' });
});

Background Service Worker (background.js)

No DOM access here! Use chrome.scripting.executeScript or open a popup instead of alert().

// Listen for messages from popup
browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.type === 'open_popup') {
    // Open the popup programmatically (only works if no default_popup is set)
    // But since we have default_popup, this won't open it automatically.
    // Instead, you could open a new window or do background logic.
    console.log('Popup requested via background script.');
  }
});

// Optional: Listen to install
browser.runtime.onInstalled.addListener(() => {
  console.log('Extension installed.');
});

To receive icon clicks, remove "default_popup" and use:

browser.action.onClicked.addListener((tab) => {
  browser.action.openPopup(); // or do something else
});

Content Script (content.js)

// Runs in the context of web pages
document.body.style.backgroundColor = "#f0f8ff";

Cross-Browser Compatibility: Use webextension-polyfill

Install via npm:

npm install webextension-polyfill

Or download from GitHub.

In your scripts (e.g., popup.js, background.js):

import browser from 'webextension-polyfill';

browser.runtime.onMessage.addListener(...);
browser.action.openPopup();

Include the polyfill in your HTML if not bundling:

<script src="polyfill.js"></script>

Testing Your Extension

For Firefox: Use web-ext

  1. Install web-ext:
   npm install -g web-ext
  1. Run:
   web-ext run

Firefox opens with your extension loaded and auto-reloads on changes.

For Chrome

  1. Go to chrome://extensions/
  2. Enable Developer mode
  3. Click Load unpacked
  4. Select your extension folder

When You Want to Listen to Icon Clicks (No Popup)

Remove "default_popup" from manifest.json, then use:

// background.js
browser.action.onClicked.addListener((tab) => {
  // Open a new popup window manually
  browser.windows.create({
    url: browser.runtime.getURL('popup/popup.html'),
    type: 'popup',
    width: 320,
    height: 240
  });
});

Now onClicked will fire and you can control the popup behavior.

Showing UI from Background Script (No Popup)

If you can’t use a popup, inject a script into the page:

browser.action.onClicked.addListener((tab) => {
  browser.scripting.executeScript({
    target: { tabId: tab.id },
    func: () => {
      alert('Hello from background via injected script!');
    }
  });
});

Avoid alert() in production. Use a content script to inject a custom UI instead.

Publishing to Stores

Best Practices Summary

PracticeWhy
Use webextension-polyfillWrite once, run on both browsers
Avoid alert() in backgroundUse content scripts or popup
List exact files in web_accessible_resourcesSecurity and clarity
Test with web-ext and Chrome dev toolsCatch browser-specific issues early
Prefer browser.* over chrome.*Future-proof and cross-browser
Remove default_popup if you need onClickedAvoid confusion

Further Reading

Comments

No comments yet. Why don’t you start the discussion?

    Leave a Reply

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