webextension-polyfill
A lightweight polyfill that gives Chrome extensions Firefox's Promise-based browser API namespace.
Repository Health
Technical Analysis
webextension-polyfill is Mozilla’s small runtime library that lets browser-extension developers write one codebase against the Promise-based browser.* namespace instead of juggling Chrome’s callback-based chrome.* APIs. It wraps every documented WebExtension API method in a Proxy that either forwards to the native browser object (a no-op on Firefox and Safari, which already expose it) or synthesizes Promise-returning wrappers around Chrome’s callback APIs using a metadata table of argument counts and callback shapes.
Because it ships as pure runtime code with no build step required from consumers, it drops into a manifest.json background/content script list, an ES module import, or a bundler require() call unchanged. The library has shipped since 2016 and was the de facto standard for cross-browser extension code until Chrome itself added native browser namespace support in 2026, at which point Mozilla marked the project feature-complete and moved it into maintenance mode.
For teams maintaining extensions that still need to support older Chrome releases, Edge, or Safari alongside Firefox, it remains the reference implementation for translating callback-shaped APIs into async/await-friendly code without hand-writing promisify wrappers for dozens of namespaces.
What You Get
- A single
browser-polyfill.jsUMD bundle (plus minified build) that defines the globalbrowserobject when loaded in a background page, content script, or extension HTML page - Promise-based wrappers, generated from
api-metadata.json, for dozens of WebExtension namespaces (tabs,storage,runtime,bookmarks,notifications,alarms, and more) so every async call can beawaited instead of passed a callback - Automatic no-op passthrough on browsers (Firefox, Safari) that already implement the native
browsernamespace, so the same script works everywhere without feature-detection code in the extension itself - Promise-aware wrapping of
runtime.onMessageandwebRequest.onRequestFinishedlisteners, so message handlers can simplyreturna value or a Promise instead of calling asendResponsecallback - Both CommonJS (
require("webextension-polyfill")) and native ES module entry points, so it fits bundler-based extension builds as well as raw<script>/manifest.jsonloading
Common Use Cases
- Cross-browser extension codebases - writing background and content scripts once against
browser.*and shipping the same source to the Chrome Web Store and Firefox Add-ons without per-browser branches - Promise/async-await extension code - replacing nested
chrome.*callbacks withawait browser.storage.local.get()-style calls throughout an extension’s business logic - Two-way messaging between background and content scripts - implementing
runtime.onMessagehandlers that simply return a value or a Promise as the reply, instead of manually callingsendResponse - Migrating a legacy Chrome-only extension to also support Firefox/Edge - dropping the polyfill script in front of existing chrome.* call sites as an incremental compatibility layer
- Bundler-based extension builds - importing the library via webpack/browserify so
browseris available inside modules without a separate script tag, using the module bundler examples in the README
Under The Hood
Architecture
The library is a single file, src/browser-polyfill.js, structured around one wrapping engine: wrapObject recursively walks the native chrome (or browser) object tree and returns a Proxy that intercepts property access, replacing any method with either a caller-supplied special-case wrapper or an auto-generated Promise wrapper built by wrapAsyncFunction. The wrapping decision for every method comes from api-metadata.json, a build-time-included JSON tree keyed by namespace and method name that records minArgs/maxArgs and callback-shape flags (singleCallbackArg, noCallback, fallbackToNoCallback) rather than hand-written per-API code, so adding support for a new WebExtension method is a metadata edit, not new wrapper logic. Two additional wrapping paths, wrapEvent and dedicated onMessage/onRequestFinished wrappers, handle the listener-based (rather than call-and-callback) parts of the API surface, using a DefaultWeakMap to keep a stable identity between an application’s original listener function and its generated wrapper so removeListener still works. The whole engine is a closure entered only if browser isn’t already defined, making the cost close to zero on browsers that need no polyfilling.
Tech Stack
Runtime code is dependency-free vanilla JavaScript compiled to a UMD bundle via a Grunt pipeline (Gruntfile.js): grunt-replace inlines api-metadata.json into the source at the {/* include(...) */} marker and stamps package name/version/timestamp, a custom Babel plugin (scripts/babel-transform-to-umd-module) wraps the result as a UMD module exposing both a browser global and an AMD/CommonJS export, and grunt-terser produces the minified browser-polyfill.min.js with an appended MPL license banner via grunt-contrib-concat. Tests run on Mocha with Chai assertions and jsdom-based DOM/browser mocking for unit coverage, plus a separate Selenium WebDriver (chromedriver/geckodriver) integration suite that loads real extensions into live Chrome and Firefox instances via tape. Linting is ESLint with a flat config (eslint.config.mjs).
Code Quality
The project has a substantial and deliberately layered test suite: test/test-async-functions.js, test/test-proxied-properties.js, and test/test-runtime-onMessage.js unit-test the wrapping engine’s Promise/callback translation and Proxy property-forwarding behavior in isolation, test/test-onRequestFinished.js and test/test-browser-global.js cover the event-wrapper and no-op-on-Firefox paths specifically, and test/integration/ runs the built bundle inside real browser instances rather than only mocks. Source is thoroughly JSDoc-commented (parameter types, return shapes, and behavior notes on nearly every exported function), uses strict-mode JS with const/arrow functions consistently, and is enforced by ESLint plus a CI pipeline (CircleCI) that runs lint, unit tests, and the module-bundler smoke tests on every change. There is no TypeScript in this repo itself; typing is delegated to the separately maintained @types/webextension-polyfill package referenced in the README.
What Makes It Unique
Rather than hand-writing a Promise wrapper per WebExtension API method (the naive approach most ad-hoc “promisify chrome” snippets take), the library generates all of them from a single declarative metadata file plus one generic wrapAsyncFunction/wrapObject engine, which is what let it stay correct across dozens of namespaces and hundreds of methods for a decade with comparatively little maintenance. Its wrapObject Proxy also transparently falls through to the native object for any property it has no metadata for, so unknown or newly added Chrome APIs remain usable (just without Promise sugar) instead of breaking. That no-op detection is itself a deliberate design choice: on Firefox and Safari, which already expose a native browser object, the entire wrapping engine is skipped, so the same script has effectively zero runtime cost on the browsers that don’t need it — the project explicitly designed for its own eventual obsolescence as browsers converged on the API it polyfills.
Used by 4 apps in this directory
Nanobrowser
Productivity · Automation · AI Assistants
Open-source Chrome extension for AI-powered web automation — run multi-agent browser workflows locally using your own LLM API keys, free from cloud subscriptions.
Postiz
Social Media · Automation
The agentic social media scheduler — AI-powered content creation, 33-platform posting, and team workflows, all self-hosted.
Responsively App
Design Tools · Developer Tools
Preview and interact with your responsive web design across every device size simultaneously — no more manual resizing.
Standard Notes
Note Taking
End-to-end encrypted notes and files — private by design, built to last, and fully self-hostable.