npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

browser-extension-compat-data

v0.2.2

Published

Validate WebExtensions manifest (and optionally permissions/APIs) against MDN browser-compat-data

Readme

Validate WebExtensions manifest (and optionally permissions/APIs) against MDN browser-compat-data

browser-extension-compat-data Version Downloads workflow provenance

MDN WebExtensions compatibility data (manifest fields, APIs, and permissions) in one lean package. Query whether a feature is supported in a browser/version (lookup), lint a whole extension across multiple targets (validate), or wire it into ESLint.

Install

npm i browser-extension-compat-data

Lookup: "is this supported?"

import {
  isSupported,
  getSupport,
  getMinVersion,
  getMdnUrl,
} from 'browser-extension-compat-data'

isSupported('api', 'runtime.sendMessage', 'safari') // => true
isSupported('manifest', 'side_panel', 'firefox') // => false
isSupported('api', 'scripting.executeScript', 'chrome', '88') // version-gated

getMinVersion('manifest', 'action', 'chrome') // => "88"
getMdnUrl('permissions', 'tabs') // MDN url (explicit or derived)

getSupport('api', 'runtime.sendMessage')
// { chrome: { supported: true, versionAdded: "26" }, safari: { supported: true, ... }, ... }

First arg is the domain ('manifest' | 'api' | 'permissions'), second the dotted BCD key. An unknown browser throws (a typo can never read as a silent pass); the canonical set is in BROWSERS.

Validate a whole extension, across targets

import { analyzeExtension } from 'browser-extension-compat-data'

const report = await analyzeExtension('./my-extension', [
  { browser: 'chrome', version: '116' },
  { browser: 'firefox', version: '115' },
  { browser: 'safari', version: '16.4' },
])

report.ok // false if any target has findings
report.scannedFiles // sources resolved from the manifest (background, content scripts)
for (const { target, findings } of report.targets) {
  // findings: { kind, key, reason, browser, message?, file?, loc?, mdnUrl?, support? }
}

It reads manifest.json, checks every field/permission MDN knows about (including host_permissions and optional_permissions), applies Manifest V2/V3 structural rules, and scans referenced source for chrome.*/browser.* usage. That scan includes scripts inside HTML entry-points (popup, options, devtools, sidebar, new-tab overrides), both external <script src> and inline blocks.

Usage resolution follows destructuring, webextension-polyfill imports, and aliasing, with file:line:column on every finding.

reason is one of not-supported | removed | partial | flag | manifest-version | no-compat-data.

Lower-level validators

import {
  getUnsupportedManifestFields,
  getUnsupportedAPIsFromFile,
} from 'browser-extension-compat-data'

await getUnsupportedManifestFields('./manifest.json', {
  browser: 'safari',
  version: '17',
})
await getUnsupportedAPIsFromFile('./background.js', {
  browser: 'firefox',
  scanMode: 'accurate',
})

scanMode: 'accurate' (default for analyzeExtension) parses the AST and is alias-aware; 'fast' is a quick regex heuristic.

CLI

npx browser-extension-compat-data ./my-extension --targets chrome116,firefox115,safari16.4
manifest: ./my-extension/manifest.json
scanned 2 source file(s): bg.js, content.js

✓ chrome 116: no issues
✗ firefox 115: 3 issue(s)
    [not supported] manifest: side_panel
    [not supported] api: offscreen.createDocument (bg.js:3:0)
    [no compat data] permission: offscreen

Exits non-zero when anything is reported. --scan all scans every script in the directory; --mode fast; --no-permissions; --json.

ESLint plugin

// eslint.config.js
import { eslintPlugin } from 'browser-extension-compat-data'

export default [
  {
    plugins: { 'webext-compat': eslintPlugin },
    rules: {
      'webext-compat/compat': [
        'warn',
        { targets: ['firefox115', 'safari16.4'] },
      ],
    },
  },
]

Flags chrome.*/browser.* calls unsupported by your targets, inline in the editor. It is alias-aware (destructuring, webextension-polyfill, aliasing), the same resolution the analyzer uses.

Typed keys

The generated key unions ship with the package, so consumers can opt into compile-time safety:

import type {
  ApiKey,
  ManifestKey,
  PermissionKey,
} from 'browser-extension-compat-data'

const used = ['runtime.sendMessage', 'tabs.query'] satisfies ApiKey[] // typo => compile error

The runtime functions accept any string (so dynamic keys and custom datasets still work); the unions are opt-in.

API surface

type Domain = 'manifest' | 'api' | 'permissions'
type Browser = 'chrome' | 'edge' | 'firefox' | 'firefox_android' | 'opera' | 'safari' | 'safari_ios'

// Lookup
isSupported(domain, key, browser, version?) // throws on unknown browser
getSupport(domain, key) / getBrowserSupport(domain, key, browser)
getMinVersion(domain, key, browser) / getMdnUrl(domain, key) / hasFeature(domain, key) / listKeys(domain)

// Validate
analyzeExtension(input, targets, options?) -> ExtensionReport
getUnsupportedManifestFields(path, options) / getUnsupportedAPIsFromFile(path, options)
evaluateManifest(manifestObject, browser, version?, checkPermissions?)

// Browsers / targets / helpers
BROWSERS, isKnownBrowser, assertBrowser, parseTarget, parseTargets
compareVersions, buildMdnUrl, runCli, eslintPlugin

// Data source (tests / custom datasets)
getIndex, setIndex, setIndexFromFile, resetIndex

Browser & bundler use

The default entry (.) reads the index from disk and pulls in fs/acorn, so it is Node-only. For the web, CDNs, or an in-browser compiler, import the fs-free subset from browser-extension-compat-data/browser. It excludes the file scanners (getUnsupportedManifestFields, getUnsupportedAPIsFromFile, analyzeExtension), the CLI and the ESLint plugin, and it starts from an empty index — bring your own data with setIndex:

import {evaluateManifest, setIndex} from 'browser-extension-compat-data/browser'
// Inline the ~700 KB dataset (bundlers resolve the JSON), or fetch it at runtime.
import index from 'browser-extension-compat-data/index.json'

setIndex(index)
evaluateManifest(manifestObject, 'firefox') // -> UnsupportedItem[]

Data & provenance

The package ships one precomputed file, src/generated/index.json, copied to dist/index.json at build time and read lazily (not inlined into the bundles). It flattens MDN's webextensions/{manifest,api,permissions} into key -> { u?: mdnUrl, s: { browser: { a, r?, p?, f? } } } (a version_added, r version_removed, p partial, f behind-a-flag). getIndex().v reports the upstream BCD version.

A daily GitHub Action re-syncs MDN BCD, rebuilds the index + src/generated/keys.ts, and commits only when the data changed.

# rebuild locally against a checkout of mdn/browser-compat-data
node scripts/build-index.mjs path/to/browser-compat-data/webextensions src/generated/index.json

Node-only at runtime (reads the JSON asset via fs).

Related projects

License

MIT (c) Cezar Augusto.