DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

How I Built and Published My First Firefox Extension

How I Built and Published My First Firefox Extension

I built Tab Manager & Session Saver as a side project. Here's what I learned about building and shipping a Firefox extension.


The Core APIs You Need

Firefox extensions use the WebExtension API, which is largely cross-compatible with Chrome. For a tab management extension, the main APIs are:

// Query all open tabs
browser.tabs.query({ currentWindow: true });

// Get all windows
browser.windows.getAll({ populate: true });

// Save data locally (persistent across restarts)
browser.storage.local.set({ sessions: [...] });
browser.storage.local.get('sessions');

// Open a URL in a new tab
browser.tabs.create({ url: 'https://example.com' });

// Close a tab
browser.tabs.remove(tabId);
Enter fullscreen mode Exit fullscreen mode

Firefox's API uses browser.* (with Promise-based async). Chrome uses chrome.* (with callbacks, though Chrome now also supports Promises in Manifest V3). For cross-browser compatibility, you can use the webextension-polyfill library.


The Manifest

The manifest.json is the entry point. For a popup extension that accesses tabs:

{
  "manifest_version": 2,
  "name": "Tab Manager",
  "version": "1.0",
  "description": "Save and restore named tab sessions",
  "permissions": [
    "tabs",
    "storage"
  ],
  "browser_action": {
    "default_popup": "popup.html",
    "default_icon": "icon.png"
  }
}
Enter fullscreen mode Exit fullscreen mode

Note on Manifest V3: Firefox supports both MV2 and MV3. Chrome is phasing out MV2. For extensions that need tabs and storage permissions (like a session manager), MV2 is still the simpler choice if you target Firefox. For cross-browser MV3 support, the API changes are mostly about background scripts (service_worker instead of scripts).


The Popup

The popup (popup.html) is the extension's UI. It's a regular HTML page that loads when the toolbar icon is clicked. For a sessions list:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div id="sessions-list"></div>
  <button id="save-btn">Save current tabs</button>
  <script src="popup.js"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
// popup.js
document.getElementById('save-btn').addEventListener('click', async () => {
  const tabs = await browser.tabs.query({ currentWindow: true });
  const session = {
    name: prompt('Session name:') || 'Unnamed',
    tabs: tabs.map(t => ({ url: t.url, title: t.title }))
  };

  const { sessions = [] } = await browser.storage.local.get('sessions');
  sessions.push(session);
  await browser.storage.local.set({ sessions });
  renderSessions();
});

async function renderSessions() {
  const { sessions = [] } = await browser.storage.local.get('sessions');
  const list = document.getElementById('sessions-list');
  list.innerHTML = sessions.map((s, i) =>
    `<div>${s.name} (${s.tabs.length} tabs) <button data-i="${i}">Restore</button></div>`
  ).join('');

  list.querySelectorAll('button').forEach(btn => {
    btn.addEventListener('click', async () => {
      const { sessions } = await browser.storage.local.get('sessions');
      const session = sessions[parseInt(btn.dataset.i)];
      for (const tab of session.tabs) {
        await browser.tabs.create({ url: tab.url });
      }
    });
  });
}

renderSessions();
Enter fullscreen mode Exit fullscreen mode

This is the core. Real implementations add: delete/rename sessions, open in new window, error handling for invalid URLs.


Local Development

Load the extension in Firefox without packaging:

  1. Go to about:debugging#/runtime/this-firefox
  2. Click "Load Temporary Add-on"
  3. Select your manifest.json

The extension loads and runs until Firefox is closed. Changes to popup HTML/JS take effect on the next click of the toolbar icon (no reload needed). Background script changes require "Reload" in about:debugging.


Submitting to AMO

AMO (addons.mozilla.org) is Mozilla's extension store. The review process:

  1. Create a developer account at addons.mozilla.org/developers
  2. Submit your extension ZIP (the extension folder compressed)
  3. Source code review: AMO reviews extensions for security and policy compliance. Extensions with minified or obfuscated code must include the original source for review.
  4. Wait for review (currently 1-4 weeks for self-hosted; faster for AMO-hosted)

Self-hosted vs AMO-hosted: AMO-hosted extensions get updates through AMO. Self-hosted extensions manage their own updates (you provide an update URL in the manifest). AMO-hosted is simpler for most use cases.

Policies to know:

  • Extensions can't collect user data without disclosure and consent
  • Remote code execution is prohibited
  • Extensions must do what the description says

What I'd Do Differently

Use Promises everywhere from the start: I initially used callback-style code in some places, which led to inconsistent error handling. The browser.* API is fully Promise-based — use async/await throughout.

Design the storage schema first: I changed my storage format twice. Session data is simple (name + list of tabs), but planning the schema upfront avoids migrations.

Write the README before the extension: Forces you to think about what the extension actually does and why, which sharpens the implementation.


The finished extension is Tab Manager & Session Saver on AMO. Source is available if you want to look at a real implementation.

Top comments (0)