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

eslint-plugin-chrome-extensions

v2.8.0

Published

ESLint rules for Chrome extensions

Downloads

1,732

Readme

eslint-plugin-chrome-extensions

ESLint rules for Chrome extensions.


Installation

From your project root:

npm i -D eslint-plugin-chrome-extensions

ESLint

Add bundled configs to your eslint.config.*js for each file type you need:

...
import chromeExtensions from 'eslint-plugin-chrome-extensions'

export default [
    // other configs
    {
        files: ['**/extension/**/*.{cjs,js,jsx,mjs,ts,tsx}'],
        plugins: { 'chrome-extensions': chromeExtensions },
        rules: {
            ...chromeExtensions.configs.recommended.rules,
            'chrome-extensions/no-deprecated-apis': 'off',  // selectively disable rule
            'chrome-extensions/valid-version-field': ['error', { versionType: 'date' }]  // selectively tweak rule
        }
    },
    { files: ['**/*LICENSE*'],            ...chromeExtensions.configs.license  },
    { files: ['**/manifest.json'],        ...chromeExtensions.configs.manifest },
    { files: ['**/extension/**/*.css'],   ...chromeExtensions.configs.css      },
    { files: ['**/extension/**/*.html'],  ...chromeExtensions.configs.html     },
    { files: ['**/extension/**/*.json'],  ...chromeExtensions.configs.json     },
    { files: ['**/extension/**/*.json5'], ...chromeExtensions.configs.json5    },
    { files: ['**/extension/**/*.jsonc'], ...chromeExtensions.configs.jsonc    }
]

Oxlint

Add a JS bundle to your oxlint.config.ts (non-JS parsing unsupported by Oxlint):

...
import chromeExtensions from 'eslint-plugin-chrome-extensions'

export default defineConfig({
    // main config
    overrides: [
        // other overrides
        {
            files: ['**/extension/**/*.{cjs,js,jsx,mjs,ts,tsx}'],
            jsPlugins: ['eslint-plugin-chrome-extensions'],
            rules: {
                ...chromeExtensions.configs.recommended.rules,
                'chrome-extensions/no-deprecated-apis': 'off',  // selectively disable rule
                'chrome-extensions/no-insecure-messaging': ['error', { disallowWildcardTarget: false }]  // selectively tweak rule
            }
        }
    ]
})

Note: Rule config is either a severity string ('off', 'warn', 'error') or an array [severity, options] when the rule accepts options.


Available JS bundles: recommended, strict (everything), problem (deterministic bugs only), security

Available filetype bundles (ESLint only): manifest, license, css, html, json, json5, jsonc


  • spaces (default 2) – number of spaces per indent level
  • checkBraces (default true) – enforce 1 space after { and before } in single-line rules
  • checkBrackets (default true) – enforce 0 spaces after [ and before ] in attribute selectors
  • checkColons (default true) – enforce 0 spaces before and 1 space after : in declarations, and 1 space between value tokens
  • checkCombinators (default true) – enforce 1 space on each side of >, +, ~ in selectors
  • checkCommas (default true) – enforce 0 spaces before and 1 space after , in selector lists
  • checkParentheses (default true) – enforce 0 spaces after ( and before ) in functions and pseudo-classes
  • checkSemicolons (default true) – enforce 1 space after ; in single-line declaration blocks

Incorrect code

! .panel {   background :    blue }
! .panel+em { font-style: italic }
! :not( .bar ) { color: red }

Correct code

- .panel {   background :    blue }
+ .panel { background: blue }
- .panel+em { font-style: italic }
+ .panel + em { font-style: italic }
- :not( .bar ) { color: red }
+ :not(.bar) { color: red }

When not to use it

If you don't care about visual consistency in your CSS, or you already use Stylelint to format CSS.

Introduced in

v2.7.0 (October 17, 2026)

See also

[Back to index ↑][epce-rules]


  • order (default []) – manifest keys in custom order (e.g. ['name', 'manifest_version']) to selectively override built-in order with

Incorrect code

  /* eslint chrome-extensions/manifest-key-order: ['error', { order: ['name', 'manifest_version'] }] */

  {
!   "manifest_version": 3,
    "name": "My Extension",
    "version": "1.0.0"
  }

Correct code

  /* eslint chrome-extensions/manifest-key-order: ['error', { order: ['name', 'manifest_version'] }] */

  {
-   "manifest_version": 3,
    "name": "My Extension",
+   "manifest_version": 3,
    "version": "1.0.0"
  }

When not to use it

If you don't care about the order of manifest keys.

Introduced in

v2.0.0 (October 4, 2026)

See also

[Back to index ↑][epce-rules]


  • spaces (default 2) – number of spaces per indent level
  • checkColons (default true) – enforce 0 spaces before and 1 space after : in key/value pairs
  • checkCommas (default true) – enforce 0 spaces before and 1 space after , in inline objects
  • checkBraces (default true) – enforce 1 space after { and before } in inline objects
  • checkBrackets (default true) – enforce 0 spaces after [ and before ] in inline arrays

Incorrect code

  /* eslint chrome-extensions/manifest-spacing: ['error', { spaces: 2 }] */

  {
!     "manifest_version": 3,
!   "version" :       "1.0.0",
!   "permissions": [  "tabs","cookies" ],
!   "icons": {"16": "icon16.png"},
    "name": "My Extension"
  }

Correct code

  /* eslint chrome-extensions/manifest-spacing: ['error', { spaces: 2 }] */

  {
-     "manifest_version": 3,
+   "manifest_version": 3,
-   "version" :       "1.0.0",
+   "version": "1.0.0",
-   "permissions": [  "tabs","cookies" ],
+   "permissions": ["tabs", "cookies"],
-   "icons": {"16": "icon16.png"},
+   "icons": { "16": "icon16.png" },
    "name": "My Extension"
  }

When not to use it

If you don't care about visual consistency in your manifest.

Introduced in

v2.2.0 (October 12, 2026)

See also

[Back to index ↑][epce-rules]


Incorrect code

! document.head.append(document.createElement('style'))

Correct code

+ const host = document.createElement('div'),
+       root = host.attachShadow({ mode: 'open' })
+ document.body.append(host)
- document.head.append(document.createElement('style'))
+ root.append(document.createElement('style'))

When not to use it

If you intentionally interact w/ host page styles.

Introduced in

v1.0.0 (October 2, 2026)

See also

  •  Chrome for Developers — Content scripts
  •  MDN — Using shadow DOM

[Back to index ↑][epce-rules]


  • target (default 'mv3') – Chrome extension manifest version whose deprecations to validate against ('mv3', 'mv2')
  • allowedAPIs (default []) – additional deprecated API namespaces to allow, as a single string or array

Incorrect code

  /* eslint chrome-extensions/no-deprecated-apis: ['error', { target: 'mv3' }] */

! chrome.pageAction.show(tabId)
! chrome.extension.getBackgroundPage().doThing()
! chrome.tabs.executeScript(tabId, { code: 'doThing()' })

Correct code

  /* eslint chrome-extensions/no-deprecated-apis: ['error', { target: 'mv3' }] */

- chrome.pageAction.show(tabId)
+ chrome.action.enable(tabId)
- chrome.extension.getBackgroundPage().doThing()
+ await chrome.runtime.getContexts({ contextTypes: ['BACKGROUND'] })
- chrome.tabs.executeScript(tabId, { code: 'doThing()' })
+ chrome.scripting.executeScript({ target: { tabId }, func: doThing })

When not to use it

If you don't want deprecation validation at all. For individual deprecated APIs you still need, prefer allowedAPIs over disabling the rule.

Introduced in

v1.0.0 (October 2, 2026)

See also

  •  Chrome for Developers — API reference

[Back to index ↑][epce-rules]


Incorrect code

  {
    "manifest_version": 3,
!   "name": "Foo",
!   "name": "Bar",
!   "version": "1.0.0",
!   "version": "2.0.0"
  }

Correct code

  {
    "manifest_version": 3,
-   "name": "Foo",
-   "name": "Bar",
    "name": "My Extension",
    "version": "1.0.0"
-   "version": "2.0.0"
  }

When not to use it

If you rely on a Chromium fork resolving duplicate keys in a specific order.

Introduced in

v2.1.0 (October 5, 2026)

See also

[Back to index ↑][epce-rules]


  • flagCommonNames (default true) – flag well-known animation names (spin, fade-in, slide-in-right, etc.) that are most likely to collide w/ host pages
  • requiredPrefix (default undefined) – flag names not starting w/ this prefix

Incorrect code

! @keyframes spin { from { transform: rotate(0) } to { transform: rotate(360deg) } }
! @keyframes fade-in { from { opacity: 0 } to { opacity: 1 } }

Correct code

- @keyframes spin { from { transform: rotate(0) } to { transform: rotate(360deg) } }
+ @keyframes myext-spin { from { transform: rotate(0) } to { transform: rotate(360deg) } }
- @keyframes fade-in { from { opacity: 0 } to { opacity: 1 } }
+ @keyframes myext-fade-in { from { opacity: 0 } to { opacity: 1 } }

When not to use it

If your CSS is scoped inside a Shadow DOM root.

Introduced in

v2.7.0 (October 17, 2026)

See also

[Back to index ↑][epce-rules]


  • allowedAttributes (default []) – inline event handler attributes to permit (e.g. ['onafterprint']), as a single string or array

Incorrect code

! <button onclick="alert('hi')">Click</button>
! <body onload="init()">
! <img src="logo.png" onerror="this.hidden = true">

Correct code

- <button onclick="alert('hi')">Click</button>
+ <button id="myButton">Click</button>
- <body onload="init()">
+ <body>
- <img src="logo.png" onerror="this.hidden = true">
+ <img src="logo.png" id="logo">

  // popup.js
+ document.getElementById('myButton').addEventListener('click', () => alert('hi'))
+ document.addEventListener('DOMContentLoaded', init)
+ document.getElementById('logo').addEventListener('error', event => event.target.hidden = true)

When not to use it

If your HTML is a sandbox page (where the CSP is intentionally relaxed). For individual handlers you still need, prefer allowedAttributes over disabling the rule.

Introduced in

v2.8.0 (October 20, 2026)

See also

[Back to index ↑][epce-rules]


  • allowedSrcPrefixes (default ['chrome-extension://']) – additional src prefixes to permit, as a single string or array

Incorrect code

! <script>alert('hi')</script>
! <script src="https://cdn.example.com/lib.js"></script>
! <script src="//cdn.example.com/lib.js"></script>
! <script src="data:text/javascript;base64,YWxlcnQoMSk="></script>

Correct code

- <script>alert('hi')</script>
+ <script src="popup.js"></script>
- <script src="https://cdn.example.com/lib.js"></script>
+ <script src="./vendor/lib.js"></script>
- <script src="//cdn.example.com/lib.js"></script>
+ <script type="module" src="popup.js"></script>
- <script src="data:text/javascript;base64,YWxlcnQoMSk="></script>
+ <script src="chrome-extension://abc123/popup.js"></script>

When not to use it

If your HTML is a sandbox page (where the CSP is intentionally relaxed).

Introduced in

v2.8.0 (October 20, 2026)

See also

  • no-remote-code
  •  Chrome for Developers — content_security_policy
  •  MDN — CSP script-src
  •  MDN — <script> element

[Back to index ↑][epce-rules]


  • allowedSelectors (default []) – selectors to explicitly allow (e.g. ['body.dark']), as a single string or array
  • disallowUniversal (default true) – flag *
  • disallowStructural (default true) – flag html, body, :root
  • disallowBareElements (default true) – flag elem types (e.g. div, a) that lack class/id/attribute

Incorrect code

! * { box-sizing: border-box }

! body { margin: 0 }
! html { font-family: sans-serif }
! button:hover { opacity: 0.8 }

Correct code

- * { box-sizing: border-box }
+ :host { box-sizing: border-box }

- body { margin: 0 }
+ :host { margin: 0 }
- html { font-family: sans-serif }
+ :host { font-family: sans-serif }
- button:hover { opacity: 0.8 }
+ .myext-button:hover { opacity: 0.8 }

When not to use it

If you intentionally style the host page. For individual selectors you still need, prefer allowedSelectors over disabling the rule.

Introduced in

v2.7.0 (October 17, 2026)

See also

[Back to index ↑][epce-rules]


Incorrect code

! fetch('http://api.example.com/data')
! xhr.open('GET', 'http://cdn.example.com/lib.js')

Correct code

- fetch('http://api.example.com/data')
+ fetch('https://api.example.com/data')
- xhr.open('GET', 'http://cdn.example.com/lib.js')
+ xhr.open('GET', 'https://cdn.example.com/lib.js')

When not to use it

If you intentionally target HTTP endpoints.

Introduced in

v2.1.0 (October 5, 2026)

See also

  •  RFC 9110 — HTTP Semantics
  •  Chrome for Developers — Cross-origin network requests
  •  MDN — XMLHttpRequest.open()
  •  MDN — fetch()

[Back to index ↑][epce-rules]


  • disallowWildcardTarget (default true) – flag postMessage() calls that use '*' as targetOrigin

Incorrect code

  chrome.runtime.onMessageExternal.addListener((msg, sender, respond) => {
!     handle(msg)
  })

  window.addEventListener('message', event => {
!     handle(event.data)
  })

! chrome.tabs.query({})
      .then(tabs => tabs.forEach(tab =>
          chrome.tabs.sendMessage(tab.id, { action: 'ping' })))

! window.postMessage(payload, '*')

Correct code

  chrome.runtime.onMessageExternal.addListener((msg, sender, respond) => {
+     if (sender.id != 'allowed-extension-id') return
      handle(msg)
  })

  window.addEventListener('message', event => {
+     if (event.origin != 'https://allowed-origin.com') return
      handle(event.data)
  })

- chrome.tabs.query({})
+ chrome.tabs.query({ url: 'https://allowed-origin.com/*' })
      .then(tabs => tabs.forEach(tab =>
          chrome.tabs.sendMessage(tab.id, { action: 'ping' })))

- window.postMessage(payload, '*')
+ window.postMessage(payload, 'https://allowed-origin.com')

When not to use it

If you trust every external caller and open tab, or you intentionally broadcast to any origin.

Introduced in

v1.0.0 (October 2, 2026)

See also

  •  Chrome for Developers — runtime.onMessageExternal
  •  Chrome for Developers — tabs.query()
  •  MDN — window.postMessage() security concerns

[Back to index ↑][epce-rules]


  • allowedLocales (default []) – additional locale codes to allow, as a single string or array

Incorrect code

  // manifest.json
  {
    "manifest_version": 3,
!   "default_locale": "pt-BR"
  }

! // _locales/pt-BR/messages.json

Correct code

  // manifest.json
  {
    "manifest_version": 3,
-   "default_locale": "pt-BR"
+   "default_locale": "pt_BR"
  }

- // _locales/pt-BR/messages.json
+ // _locales/pt_BR/messages.json

When not to use it

If your _locales/ is externally populated at build time. If you target a Chromium fork w/ additional supported locales, prefer allowedLocales over disabling the rule.

Introduced in

v2.6.0 (October 16, 2026)

See also

[Back to index ↑][epce-rules]


  • target (default 'mv3') – Chrome manifest version whose permissions to validate against ('mv3', 'mv2')
  • allowedPermissions (default []) – additional permission names to allow beyond the built-in list, as a single string or array

Incorrect code

  /* eslint chrome-extensions/no-invalid-permissions: ['error', { target: 'mv3' }] */

  {
!   "permissions": ["browserAction", "tabs"],
!   "optional_permissions": ["history", "pageAction"]
  }

Correct code

  /* eslint chrome-extensions/no-invalid-permissions: ['error', { target: 'mv3' }] */

  {
-   "permissions": ["browserAction", "tabs"],
+   "permissions": ["tabs"],
-   "optional_permissions": ["history", "pageAction"]
+   "optional_permissions": ["history"]
  }

When not to use it

If you don't want permission validation at all. For custom permissions from a Chromium fork, prefer allowedPermissions over disabling the rule.

Introduced in

v1.0.0 (October 2, 2026)

See also

[Back to index ↑][epce-rules]


  • ignoreKeys (default []) – keys to skip when checking locales
  • checkAllLocales (default false) – also flag keys missing from any locale

Incorrect code

  // manifest.json
  {
    "manifest_version": 3,
    "default_locale": "en",
    "name": "__MSG_extName__"
  }

  // _locales/en/messages.json
  {
    "extName": { "message": "My Extension" }
  }

  // _locales/de/messages.json
  {
    "extName": { "message": "Meine Erweiterung" },
!   "welcomeMsg": { "message": "Willkommen" }
  }

Correct code

  // manifest.json
  {
    "manifest_version": 3,
    "default_locale": "en",
    "name": "__MSG_extName__"
  }

  // _locales/en/messages.json
  {
    "extName": { "message": "My Extension" },
+   "welcomeMsg": { "message": "Welcome" }
  }

  // _locales/de/messages.json
  {
    "extName": { "message": "Meine Erweiterung" },
    "welcomeMsg": { "message": "Willkommen" }
  }

When not to use it

If your _locales/ is externally populated at build time.

Introduced in

v2.4.0 (October 13, 2026)

See also

  •  Chrome for Developers — default_locale
  •  <a target="_blank" href="https://developer.chrome.com/docs/extensions/referen