DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

What I Learned Building Two Firefox Extensions from Scratch

What I Learned Building Two Firefox Extensions from Scratch

I've recently shipped two Firefox extensions: Tab Manager & Session Saver and NightOwl. Both are live on AMO. Here's what I learned building them that the official docs gloss over.

1. browser.storage.local vs browser.storage.sync — Think Before You Choose

The docs present sync as the aspirational choice (data synced across devices!) and local as the fallback. In practice, sync has a 100KB total quota with per-item size limits that will bite you.

For Tab Manager, each saved session is a JSON object containing tab URLs, titles, and timestamps. A user with 20 sessions of 30 tabs each is already pushing toward the sync quota. We went with local exclusively.

For NightOwl, per-domain settings are small key-value pairs. Still, if a user has visited hundreds of sites, sync could get crowded. Also: do you want to sync dark mode overrides across devices? Maybe not always. local keeps it per-machine.

Lesson: Default to local. Only reach for sync if cross-device sync is a core feature promise, and be explicit about the quota constraints.

2. The Manifest V3 Migration Is Real But Manageable

Firefox has been slow-rolling MV3 adoption (it still supports MV2) but the direction is clear. A few things that tripped us up:

Background scripts: In MV3, you use a background.service_worker key instead of background.scripts. The service worker can be terminated at any time. If you have state in memory, it will be lost. Move anything important to browser.storage.local.

Host permissions: MV3 separates host permissions from API permissions more cleanly. Read the permission docs carefully — you don't need <all_urls> for most use cases, and requesting fewer permissions builds user trust.

Content scripts: Still work the same in MV3. NightOwl's CSS injection lives in a content script and the MV2→MV3 migration required zero changes there.

3. The AMO Review Process Is Faster Than Expected

I was braced for a multi-week wait. Both extensions cleared review in under 3 days. The review team does look at your code — you'll get a rejection if you're using eval(), obfuscating code, or requesting permissions you don't use.

Things that helped get approved quickly:

  • Clean, readable code (no minification of the source)
  • Clear description of what each permission is used for
  • No external network requests except what's explicitly described
  • Source code that matches the described functionality exactly

4. Content Script Injection Timing Matters

For NightOwl's CSS injection, we inject at document_start to prevent the flash of unstyled (white) content before the dark mode kicks in:

"content_scripts": [{
  "matches": ["<all_urls>"],
  "js": ["content.js"],
  "run_at": "document_start"
}]
Enter fullscreen mode Exit fullscreen mode

document_start fires before the DOM is parsed, which means no white flash. If you use document_idle (the default), users will see a flicker on each page load. For a dark mode extension, that's unacceptable.

5. Tab API Quirks

For Tab Manager, we iterate over tabs using browser.tabs.query({}). A few things to know:

  • browser.tabs.query returns tabs in the order they appear in their windows, but doesn't guarantee cross-window ordering. If you're saving multiple windows, store the window context separately.
  • The url property in tab objects requires the tabs permission. Without it, you get undefined for URLs even though other tab properties work.
  • Pinned tabs (tab.pinned) are worth special-casing — users typically want them handled differently from regular tabs.

6. The Popup vs Sidebar Decision

Most simple extensions use a popup (the icon click opens a small floating UI). For Tab Manager, we used a sidebar — a persistent panel on the left side of the browser that stays open across tab switches.

Sidebars are better for tools where you're switching between the panel and page content frequently. The tradeoff: sidebars have more constrained UX options and some users find them surprising.

Popup if: the extension does a one-time action (toggle dark mode, save current session)
Sidebar if: the extension is a persistent tool you interact with while browsing


Both extensions are small and focused. The main lesson across both: scope aggressively, ship something minimal, and see what users actually want before building more. The extensions that stick are usually the ones that do one thing well — not the ones trying to be full productivity suites.

Top comments (0)