Cross-Browser Extension Development (WebExtension API) – One Codebase for All

Imagine you've built a Chrome extension—it works flawlessly. Then you port it to Firefox, and half the APIs behave differently. On a financial aggregator project, `browser.storage.local` in Firefox returned undefined without an error, causing all user settings to reset. We had to rewrite 40% of the

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1419
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    984
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1248
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    984
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    998

Imagine you've built a Chrome extension—it works flawlessly. Then you port it to Firefox, and half the APIs behave differently. On a financial aggregator project, browser.storage.local in Firefox returned undefined without an error, causing all user settings to reset. We had to rewrite 40% of the code. Each browser adds two extra days of debugging. We've developed a systematic approach: a single codebase, WebExtension API, polyfills, and conditional builds. A unified codebase slashes the budget by 40–60% compared to developing separate extensions for each browser. Time savings of 3-4x translate to a 30–50% budget reduction. The cost is determined after a thorough analysis of your requirements.

How We Solve the API Compatibility Problem

The webextension-polyfill

Mozilla developed the webextension-polyfill—it converts chrome.* (callback) to browser.* (Promise) and smooths out differences between browsers. According to the WebExtensions specification, installation is a single command:

npm install webextension-polyfill 
import browser from 'webextension-polyfill'; const tabs = await browser.tabs.query({ active: true, currentWindow: true }); await browser.storage.local.set({ data: 'value' }); const result = await browser.storage.local.get('data'); 

The polyfill reduces adaptation time by 3-4x compared to manual wrapping—proven on financial and e-commerce projects.

Browser API Compatibility

API Chrome Firefox Edge Opera Safari
storage.local MV2/3 MV2/3 MV2/3 MV2/3 14+
storage.sync 15+
tabs 14+
scripting (MV3) 101+ 92+ 15.4+
declarativeNetRequest 113+ 92+ 15.4+
sidePanel 114+
webRequest MV2 only MV2 only MV2 only

Differences exist—for instance, sidePanel is only available in Chrome 114+. For such cases, we use conditional paths and fallback implementations. The principle: the polyfill covers 90% of typical operations; the rest is manual adaptation, which is more reliable than proliferating wrappers.

Why We Use Separate Manifests?

Chrome with MV3 requires a service_worker, Firefox with MV2 uses background.scripts. Maintaining both manifests in parallel is standard practice for maximum coverage. We build the final manifest via a script from manifest.base.json plus target-specific settings per browser:

// scripts/build.js const fs = require('fs'); const target = process.env.TARGET_BROWSER; const manifests = { chrome: { manifest_version: 3, background: { service_worker: 'background.js' }, action: { default_popup: 'popup.html' }, }, firefox: { manifest_version: 2, background: { scripts: ['background.js'], persistent: false }, browser_action: { default_popup: 'popup.html' }, browser_specific_settings: { gecko: { id: '[email protected]', strict_min_version: '109.0' }, }, }, safari: { manifest_version: 3, background: { service_worker: 'background.js' }, action: { default_popup: 'popup.html' }, browser_specific_settings: { safari: { strict_min_version: '15.4' }, }, }, }; const base = JSON.parse(fs.readFileSync('./manifest.base.json', 'utf8')); const merged = { ...base, ...manifests[target] }; fs.writeFileSync('./dist/manifest.json', JSON.stringify(merged, null, 2)); 

MV2 vs MV3 Manifest Comparison

Aspect MV2 MV3
Background background.scripts + Browser Action Service Worker + Action
API webRequest Full support Only declarativeNetRequest
API tabs Full Limited (not in service worker)
Security Arbitrary code execution eval() prohibited, remote scripts restricted
Installation Manual or automatic Requires signing and updates via store

Choosing MV2 or MV3 depends on target browsers and functionality. For example, an extension heavily using webRequest still needs MV2 in Chrome, though Firefox already works with MV3 via declarativeNetRequest. We help determine the optimal strategy.

How to Avoid Messaging Errors?

Strict message typing between contexts prevents silent bugs. We use a discriminated union in TypeScript:

// shared/messages.ts export type Message = | { type: 'GET_PAGE_DATA'; url: string } | { type: 'SET_BADGE'; count: number; color?: string } | { type: 'OPEN_OPTIONS' } | { type: 'EXTRACT_TEXT'; selector: string }; export type MessageResponse<T extends Message> = T extends { type: 'GET_PAGE_DATA' } ? { title: string; meta: Record<string, string> } : T extends { type: 'EXTRACT_TEXT' } ? { text: string } : void; export async function sendMessage<T extends Message>( message: T ): Promise<MessageResponse<T>> { return browser.runtime.sendMessage(message) as Promise<MessageResponse<T>>; } export function onMessage<T extends Message['type']>( type: T, handler: (msg: Extract<Message, { type: T }>, sender: browser.runtime.MessageSender) => Promise<MessageResponse<Extract<Message, { type: T }>>> ) { browser.runtime.onMessage.addListener((msg, sender, sendResponse) => { if (msg.type === type) { handler(msg, sender).then(sendResponse); return true; } }); } 

This approach ensures the handler receives exactly the expected message type. The compiler won't allow mismatches. Typed messages are standard across all our projects, including financial and e-commerce extensions with hundreds of thousands of users.

What’s Included in the Work

We deliver a turnkey solution:

  • Architecture and design—stack selection, typing, file structure.
  • Implementation and testing—coding, unit tests, E2E tests with Playwright.
  • Build and CI/CD—Vite configuration, build scripts, GitHub Actions for publication.
  • Documentation—README, API description, build instructions.
  • Publication—upload to Chrome Web Store, Firefox AMO, Edge Add-ons, Opera Addons (Safari on request).
  • Support—30-day warranty period after delivery, bug fixes, adaptation to new browser versions.

We have over 5 years of experience and 30+ successful projects in the financial, e-commerce, and SaaS sectors. Contact us to evaluate your project—we’ll help choose the optimal architecture.

Timeline

A cross-browser extension (Chrome + Firefox + Edge) with a single codebase, typing, automated builds, and publication to three stores takes 10–16 working days depending on functionality. Adding Safari (Xcode wrapper + App Store) adds 5–8 days. Store review times are separate and parallel.

Order cross-browser extension development—we’ll evaluate your task within one working day. Get a consultation by phone or chat to discuss functionality and timeline.