eslint-plugin-chrome-extensions 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,767 @@
1
+ <a id="top"></a>
2
+
3
+ # <img height=24 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/e9128375d4/assets/images/icons/eslint/purple/icon32.png"> eslint-plugin-chrome-extensions
4
+
5
+ <a target="_blank" href="https://www.npmjs.com/package/eslint-plugin-chrome-extensions">
6
+ <img alt="[Latest Build]" src="https://img.shields.io/npm/v/eslint-plugin-chrome-extensions?logo=icinga&style=for-the-badge&label=Latest%20Build&color=32fcee"></a>
7
+ <a href="#license">
8
+ <img alt="[License: MIT]" src="https://img.shields.io/badge/License-MIT-f1f20a.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge"></a>
9
+ <a target="_blank" href="https://eslint.org/">
10
+ <img alt="[Requires ESLint >=9.0.0]" src="https://img.shields.io/badge/Requires-ESLint_%3E%3D9.0.0-4B32C3?style=for-the-badge&logo=eslint"></a>
11
+ <br><br>
12
+
13
+ ESLint rules for Chrome extensions.
14
+
15
+ <img src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-chrome-extensions/0b5e3e49/assets/images/screenshots/vs-code/squiggles.png">
16
+
17
+ ---
18
+
19
+ ## <img height=30 width=27 align="top" src="https://api.iconify.design/ic/sharp-install-desktop.svg?color=%232b2b2a"> Installation
20
+
21
+ From your project root:
22
+
23
+ ```bash
24
+ npm i -D eslint-plugin-chrome-extensions
25
+ ```
26
+
27
+ ---
28
+
29
+ <h2 id="usage">
30
+ <img height=30 width=28 align="top" src="https://api.iconify.design/entypo/power-plug.svg?color=%232b2b2a">
31
+ Usage
32
+ </h2>
33
+
34
+ Add a bundled config to your `eslint.config.*js`:
35
+
36
+ ```js
37
+ ...
38
+ import chromeExtensions from 'eslint-plugin-chrome-extensions'
39
+
40
+ export default [
41
+ // other configs
42
+ {
43
+ files: ['**/*.js'], plugins: { 'chrome-extensions': chromeExtensions },
44
+ rules: {
45
+ ...chromeExtensions.configs.recommended.rules,
46
+ 'chrome-extensions/no-unused-permissions': 'off', // selectively disable rule
47
+ 'chrome-extensions/no-deprecated-apis': ['error', { target: 'mv3' }] // selectively tweak rule
48
+ }
49
+ }
50
+ ]
51
+ ```
52
+
53
+ Available bundles: `recommended`, `strict` (everything), `problem` (deterministic bugs only)
54
+
55
+ **Note:** Rule config is either a severity string (`'off'`, `'warn'`, `'error'`) or an array `[severity, options]` when the rule accepts options.
56
+
57
+ ---
58
+
59
+ <h2 id="supported-rules">
60
+ <img height=30 width=27 align="top" src="https://api.iconify.design/mdi/format-list-checks.svg?color=%232b2b2a">
61
+ Supported rules
62
+ </h2>
63
+
64
+ <h3 id="no-content-style-leak">
65
+ <code>no-content-style-leak</code>
66
+ <a title="Only included in strict config" href="#usage">
67
+ <img height=18 alt="[Strict only]" src="https://img.shields.io/badge/Strict_only-ff8c42?style=flat-square" align="middle"></a>
68
+ <a title="Copy rule URL" target="_blank"
69
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-content-style-leak">
70
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
71
+ </h3>
72
+
73
+ <details>
74
+
75
+ <summary>Disallows injecting <code>&lt;style&gt;</code> into the host document from a content script.</summary>
76
+
77
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
78
+
79
+ ```diff
80
+ ! document.head.append(document.createElement('style'))
81
+ ```
82
+
83
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
84
+
85
+ ```diff
86
+ - document.head.append(document.createElement('style'))
87
+ + await chrome.scripting.insertCSS({ files: ['content.css'] })
88
+ ```
89
+
90
+ #### When not to use it
91
+
92
+ If you already scope every injected style inside a `shadowRoot` you own.
93
+
94
+ #### Introduced in
95
+
96
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v1.0.0">v1.0.0</a> (October 2, 2026)
97
+
98
+ #### See also
99
+
100
+ - [`valid-service-worker`](#valid-service-worker)
101
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/scripting">Chrome Extensions — `chrome.scripting`</a>
102
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts">Chrome Extensions — Content scripts</a>
103
+
104
+ <div align="center">
105
+
106
+ [Back to index ↑][epc-rules]
107
+
108
+ </div>
109
+
110
+ ---
111
+
112
+ </details>
113
+
114
+ <h3 id="no-deprecated-apis">
115
+ <code>no-deprecated-apis</code>
116
+ <a title="Included in recommended config" href="#usage">
117
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
118
+ <a title="Accepts array w/ options" href="#usage">
119
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
120
+ <a title="Copy rule URL" target="_blank"
121
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-deprecated-apis">
122
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
123
+ </h3>
124
+
125
+ <details>
126
+
127
+ <summary>Disallows deprecated <code>chrome</code> API calls.</summary>
128
+
129
+ #### Options
130
+
131
+ - `target` (default `'mv3'`) – Chrome extension manifest version whose deprecations to validate against (`'mv3'`, `'mv2'`)
132
+ - `allowedAPIs` (default `[]`) – additional deprecated API namespaces to allow, as a single string or array
133
+
134
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
135
+
136
+ ```diff
137
+ /* eslint chrome-extensions/no-deprecated-apis: ['error', { target: 'mv3' }] */
138
+
139
+ ! chrome.pageAction.show(tabId)
140
+ ! chrome.extension.getBackgroundPage().doThing()
141
+ ```
142
+
143
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
144
+
145
+ ```diff
146
+ /* eslint chrome-extensions/no-deprecated-apis: ['error', { target: 'mv3' }] */
147
+
148
+ - chrome.pageAction.show(tabId)
149
+ + chrome.action.enable(tabId)
150
+ - chrome.extension.getBackgroundPage().doThing()
151
+ + await chrome.runtime.getContexts({ contextTypes: ['BACKGROUND'] })
152
+ ```
153
+
154
+ #### When not to use it
155
+
156
+ If you don't want deprecation validation at all. For individual deprecated APIs you still need, prefer `allowedAPIs` over disabling the rule.
157
+
158
+ #### Introduced in
159
+
160
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v1.0.0">v1.0.0</a> (October 2, 2026)
161
+
162
+ #### See also
163
+
164
+ - [`no-remote-code`](#no-remote-code)
165
+ - [`valid-mv3-schema`](#valid-mv3-schema)
166
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api">Chrome Extensions — API reference</a>
167
+
168
+ <div align="center">
169
+
170
+ [Back to index ↑][epc-rules]
171
+
172
+ </div>
173
+
174
+ ---
175
+
176
+ </details>
177
+
178
+ <h3 id="no-insecure-messaging">
179
+ <code>no-insecure-messaging</code>
180
+ <a title="Included in recommended config" href="#usage">
181
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
182
+ <a title="Copy rule URL" target="_blank"
183
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-insecure-messaging">
184
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
185
+ </h3>
186
+
187
+ <details>
188
+
189
+ <summary>Requires external message handlers validate <code>sender</code> + flags unfiltered <code>tabs.query()</code>.</summary>
190
+
191
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
192
+
193
+ ```diff
194
+ ! chrome.runtime.onMessageExternal.addListener((msg, sender, respond) => {
195
+ ! handle(msg) })
196
+
197
+ ! chrome.tabs.query({}).then(tabs => tabs.forEach(tab =>
198
+ ! chrome.tabs.sendMessage(tab.id, { action: 'ping' })))
199
+ ```
200
+
201
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
202
+
203
+ ```diff
204
+ - chrome.runtime.onMessageExternal.addListener((msg, sender, respond) => {
205
+ - handle(msg) })
206
+ + chrome.runtime.onMessageExternal.addListener((msg, sender, respond) => {
207
+ + if (sender.id != 'allowed-extension-id') return
208
+ + handle(msg)
209
+ + })
210
+
211
+ - chrome.tabs.query({}).then(tabs => tabs.forEach(tab =>
212
+ + chrome.tabs.query({ url: 'https://example.com/*' }).then(tabs => tabs.forEach(tab =>
213
+ chrome.tabs.sendMessage(tab.id, { action: 'ping' })))
214
+ ```
215
+
216
+ #### When not to use it
217
+
218
+ If you trust every external caller and open tab.
219
+
220
+ #### Introduced in
221
+
222
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v1.0.0">v1.0.0</a> (October 2, 2026)
223
+
224
+ #### See also
225
+
226
+ - [`no-deprecated-apis`](#no-deprecated-apis)
227
+ - [`no-missing-permissions`](#no-missing-permissions)
228
+
229
+ <div align="center">
230
+
231
+ [Back to index ↑][epc-rules]
232
+
233
+ </div>
234
+
235
+ ---
236
+
237
+ </details>
238
+
239
+ <h3 id="no-invalid-permissions">
240
+ <code>no-invalid-permissions</code>
241
+ <a title="Included in recommended config" href="#usage">
242
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
243
+ <a title="Accepts array w/ options" href="#usage">
244
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
245
+ <a title="Copy rule URL" target="_blank"
246
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-invalid-permissions">
247
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
248
+ </h3>
249
+
250
+ <details>
251
+
252
+ <summary>Disallows invalid or unsupported manifest permission names.</summary>
253
+
254
+ #### Options
255
+
256
+ - `target` (default `'mv3'`) – Chrome manifest version whose permissions to validate against (`'mv3'`, `'mv2'`)
257
+ - `allowedPermissions` (default `[]`) – additional permission names to allow beyond the built-in list, as a single string or array
258
+
259
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
260
+
261
+ ```diff
262
+ /* eslint chrome-extensions/no-invalid-permissions: ['error', { target: 'mv3' }] */
263
+
264
+ {
265
+ ! "permissions": ["browserAction", "tabs"],
266
+ ! "optional_permissions": ["history", "pageAction"]
267
+ }
268
+ ```
269
+
270
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
271
+
272
+ ```diff
273
+ /* eslint chrome-extensions/no-invalid-permissions: ['error', { target: 'mv3' }] */
274
+
275
+ {
276
+ - "permissions": ["browserAction", "tabs"],
277
+ - "optional_permissions": ["history", "pageAction"]
278
+ + "permissions": ["tabs"],
279
+ + "optional_permissions": ["history"]
280
+ }
281
+ ```
282
+
283
+ #### When not to use it
284
+
285
+ If you don't want permission validation at all. For custom permissions from a Chromium fork, prefer `allowedPermissions` over disabling the rule.
286
+
287
+ #### Introduced in
288
+
289
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v1.0.0">v1.0.0</a> (October 2, 2026)
290
+
291
+ #### See also
292
+
293
+ - [`no-missing-permissions`](#no-missing-permissions)
294
+ - [`no-unused-permissions`](#no-unused-permissions)
295
+
296
+ <div align="center">
297
+
298
+ [Back to index ↑][epc-rules]
299
+
300
+ </div>
301
+
302
+ ---
303
+
304
+ </details>
305
+
306
+ <h3 id="no-missing-permissions">
307
+ <code>no-missing-permissions</code>
308
+ <a title="Included in recommended config" href="#usage">
309
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
310
+ <a title="Copy rule URL" target="_blank"
311
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-missing-permissions">
312
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
313
+ </h3>
314
+
315
+ <details>
316
+
317
+ <summary>Flags <code>chrome</code> API calls used w/o a matching manifest permission.</summary>
318
+
319
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
320
+
321
+ ```diff
322
+ // manifest.json
323
+ {
324
+ ! "permissions": []
325
+ }
326
+
327
+ // background.js
328
+ ! chrome.cookies.get({ url: 'https://example.com', name: 'x' })
329
+ ! chrome.history.search({ text: '' })
330
+ ```
331
+
332
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
333
+
334
+ ```diff
335
+ // manifest.json
336
+ {
337
+ - "permissions": []
338
+ + "permissions": ["cookies", "history"]
339
+ }
340
+
341
+ // background.js
342
+ chrome.cookies.get({ url: 'https://example.com', name: 'x' })
343
+ chrome.history.search({ text: '' })
344
+ ```
345
+
346
+ #### When not to use it
347
+
348
+ If you use APIs through aliases or wrappers that obscure the original call site.
349
+
350
+ #### Introduced in
351
+
352
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v1.0.0">v1.0.0</a> (October 2, 2026)
353
+
354
+ #### See also
355
+
356
+ - [`no-invalid-permissions`](#no-invalid-permissions)
357
+ - [`no-unused-permissions`](#no-unused-permissions)
358
+
359
+ <div align="center">
360
+
361
+ [Back to index ↑][epc-rules]
362
+
363
+ </div>
364
+
365
+ ---
366
+
367
+ </details>
368
+
369
+ <h3 id="no-remote-code">
370
+ <code>no-remote-code</code>
371
+ <a title="Included in recommended config" href="#usage">
372
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
373
+ <a title="Accepts array w/ options" href="#usage">
374
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
375
+ <a title="Copy rule URL" target="_blank"
376
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-remote-code">
377
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
378
+ </h3>
379
+
380
+ <details>
381
+
382
+ <summary>Disallows remote or dynamically-evaluated code.</summary>
383
+
384
+ #### Options
385
+
386
+ - `patterns` (default `['eval', 'new-function', 'string-timer', 'dynamic-import', 'remote-script-src', 'execute-script-url']`) – detection paths to enable, as a single string or array
387
+ - `checkExecuteScriptClosures` (default `true`) – additionally flag <code>chrome.scripting.executeScript({ func })</code> bodies that capture outer-scope variables
388
+
389
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
390
+
391
+ ```diff
392
+ ! const run = eval('1 + 1')
393
+ ! setTimeout('doThing()', 1000)
394
+ ! await import('https://cdn.example.com/lib.js')
395
+ ! document.head.append(Object.assign(document.createElement('script'),
396
+ ! { src: 'https://cdn.example.com/remote.js' }))
397
+
398
+ const secret = 'abc'
399
+ ! chrome.scripting.executeScript({ func: () => alert(secret), target: { tabId } })
400
+ ```
401
+
402
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
403
+
404
+ ```diff
405
+ - const run = eval('1 + 1')
406
+ + const run = 1 + 1
407
+ - setTimeout('doThing()', 1000)
408
+ + setTimeout(doThing, 1000)
409
+ - await import('https://cdn.example.com/lib.js')
410
+ + await import('./lib.js')
411
+ - document.head.append(Object.assign(document.createElement('script'),
412
+ - { src: 'https://cdn.example.com/remote.js' }))
413
+ + document.head.append(Object.assign(document.createElement('script'),
414
+ + { src: chrome.runtime.getURL('inject.js') }))
415
+
416
+ const secret = 'abc'
417
+ - chrome.scripting.executeScript({ func: () => alert(secret), target: { tabId } })
418
+ + chrome.scripting.executeScript({ func: (s) => alert(s), args: [secret], target: { tabId } })
419
+ ```
420
+
421
+ #### When not to use it
422
+
423
+ Never (MV3 CSP blocks remote code at runtime).
424
+
425
+ #### Introduced in
426
+
427
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v1.0.0">v1.0.0</a> (October 2, 2026)
428
+
429
+ #### See also
430
+
431
+ - [`no-deprecated-apis`](#no-deprecated-apis)
432
+ - [`valid-mv3-schema`](#valid-mv3-schema)
433
+
434
+ <div align="center">
435
+
436
+ [Back to index ↑][epc-rules]
437
+
438
+ </div>
439
+
440
+ ---
441
+
442
+ </details>
443
+
444
+ <h3 id="no-unused-permissions">
445
+ <code>no-unused-permissions</code>
446
+ <a title="Only included in recommended + strict configs" href="#usage">
447
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
448
+ <a title="Copy rule URL" target="_blank"
449
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-unused-permissions">
450
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
451
+ </h3>
452
+
453
+ <details>
454
+
455
+ <summary>Flags manifest permissions whose APIs are never used.</summary>
456
+
457
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
458
+
459
+ ```diff
460
+ // manifest.json
461
+ {
462
+ ! "permissions": ["tabs", "downloads"]
463
+ }
464
+
465
+ // background.js
466
+ chrome.tabs.create({ url: 'https://example.com' })
467
+ ```
468
+
469
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
470
+
471
+ ```diff
472
+ // manifest.json
473
+ {
474
+ - "permissions": ["tabs", "downloads"]
475
+ + "permissions": ["tabs"]
476
+ }
477
+
478
+ // background.js
479
+ chrome.tabs.create({ url: 'https://example.com' })
480
+ ```
481
+
482
+ #### When not to use it
483
+
484
+ If you access the granted API through an alias or computed property rather than calling it directly.
485
+
486
+ #### Introduced in
487
+
488
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v1.0.0">v1.0.0</a> (October 2, 2026)
489
+
490
+ #### See also
491
+
492
+ - [`no-invalid-permissions`](#no-invalid-permissions)
493
+ - [`no-missing-permissions`](#no-missing-permissions)
494
+
495
+ <div align="center">
496
+
497
+ [Back to index ↑][epc-rules]
498
+
499
+ </div>
500
+
501
+ ---
502
+
503
+ </details>
504
+
505
+ <h3 id="require-mv3">
506
+ <code>require-mv3</code>
507
+ <a title="Included in recommended config" href="#usage">
508
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
509
+ <a title="Copy rule URL" target="_blank"
510
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23require-mv3">
511
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
512
+ </h3>
513
+
514
+ <details>
515
+
516
+ <summary>Requires <code>"manifest_version": 3</code>.</summary>
517
+
518
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
519
+
520
+ ```diff
521
+ // manifest.json
522
+ {
523
+ ! "manifest_version": 2,
524
+ "name": "My Extension",
525
+ "version": "1.0.0"
526
+ }
527
+ ```
528
+
529
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
530
+
531
+ ```diff
532
+ // manifest.json
533
+ {
534
+ - "manifest_version": 2,
535
+ + "manifest_version": 3,
536
+ "name": "My Extension",
537
+ "version": "1.0.0"
538
+ }
539
+ ```
540
+
541
+ #### When not to use it
542
+
543
+ If you intentionally target MV2 for a Chromium fork that still supports it.
544
+
545
+ #### Introduced in
546
+
547
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v1.0.0">v1.0.0</a> (October 2, 2026)
548
+
549
+ #### See also
550
+
551
+ - [`valid-mv3-schema`](#valid-mv3-schema)
552
+
553
+ <div align="center">
554
+
555
+ [Back to index ↑][epc-rules]
556
+
557
+ </div>
558
+
559
+ ---
560
+
561
+ </details>
562
+
563
+ <h3 id="valid-mv3-schema">
564
+ <code>valid-mv3-schema</code>
565
+ <a title="Included in recommended config" href="#usage">
566
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
567
+ <a title="Copy rule URL" target="_blank"
568
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23valid-mv3-schema">
569
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
570
+ </h3>
571
+
572
+ <details>
573
+
574
+ <summary>Validates the manifest against the MV3 schema.</summary>
575
+
576
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
577
+
578
+ ```diff
579
+ {
580
+ "manifest_version": 3,
581
+ ! "browser_action": {},
582
+ ! "page_action": {},
583
+ ! "background": { "scripts": ["bg.js"], "persistent": true },
584
+ ! "content_security_policy": "script-src 'self' 'unsafe-eval'"
585
+ }
586
+ ```
587
+
588
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
589
+
590
+ ```diff
591
+ {
592
+ "manifest_version": 3,
593
+ - "browser_action": {},
594
+ - "page_action": {},
595
+ - "background": { "scripts": ["bg.js"], "persistent": true },
596
+ - "content_security_policy": "script-src 'self' 'unsafe-eval'"
597
+ + "action": {},
598
+ + "background": { "service_worker": "sw.js" },
599
+ + "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'self'" }
600
+ }
601
+ ```
602
+
603
+ #### When not to use it
604
+
605
+ If you intentionally target MV2 for a Chromium fork that still supports it.
606
+
607
+ #### Introduced in
608
+
609
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v1.0.0">v1.0.0</a> (October 2, 2026)
610
+
611
+ #### See also
612
+
613
+ - [`require-mv3`](#require-mv3)
614
+ - [`no-deprecated-apis`](#no-deprecated-apis)
615
+
616
+ <div align="center">
617
+
618
+ [Back to index ↑][epc-rules]
619
+
620
+ </div>
621
+
622
+ ---
623
+
624
+ </details>
625
+
626
+ <h3 id="valid-service-worker">
627
+ <code>valid-service-worker</code>
628
+ <a title="Included in recommended config" href="#usage">
629
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
630
+ <a title="Copy rule URL" target="_blank"
631
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23valid-service-worker">
632
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
633
+ </h3>
634
+
635
+ <details>
636
+
637
+ <summary>Enforces valid service worker lifecycle and isolation.</summary>
638
+
639
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/gray.svg" align="bottom"> Incorrect code
640
+
641
+ ```diff
642
+ ! (function init() { chrome.runtime.onMessage.addListener(handler) })()
643
+ ! setInterval(() => {}, 5000)
644
+ ! const loc = window.location
645
+ ! localStorage.setItem('key', 'val')
646
+ ```
647
+
648
+ #### <img width=13 height=13 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/99f14247b2/assets/images/icons/square/with-symbol/green.svg" align="bottom"> Correct code
649
+
650
+ ```diff
651
+ - (function init() { chrome.runtime.onMessage.addListener(handler) })()
652
+ + chrome.runtime.onMessage.addListener(handler)
653
+ - setInterval(() => {}, 5000)
654
+ + chrome.alarms.create('tick', { periodInMinutes: 1 })
655
+ - const loc = window.location
656
+ + const loc = self.location
657
+ - localStorage.setItem('key', 'value')
658
+ + await chrome.storage.local.set({ key: 'value' })
659
+ ```
660
+
661
+ #### When not to use it
662
+
663
+ If your background context isn't a service worker.
664
+
665
+ #### Introduced in
666
+
667
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v1.0.0">v1.0.0</a> (October 2, 2026)
668
+
669
+ #### See also
670
+
671
+ - [`no-content-style-leak`](#no-content-style-leak)
672
+ - [`no-remote-code`](#no-remote-code)
673
+
674
+ <div align="center">
675
+
676
+ [Back to index ↑][epc-rules]
677
+
678
+ </div>
679
+
680
+ </details>
681
+
682
+ ---
683
+
684
+ ## <img height=30 align="top" src="https://api.iconify.design/boxicons/code-alt-filled.svg?color=%232b2b2a"> Shield embed
685
+
686
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#readme">
687
+ <img height=32 alt="[Linted by eslint-plugin-chrome-extensions]" src="https://img.shields.io/badge/Linted_by-eslint--plugin--chrome--extensions-black?logo=eslint&logoColor=white&labelColor=464646&style=for-the-badge"></a>
688
+
689
+ #### HTML
690
+
691
+ ```html
692
+ <a href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#readme">
693
+ <img height=32 alt="[Linted by eslint-plugin-chrome-extensions]" src="https://img.shields.io/badge/Linted_by-eslint--plugin--chrome--extensions-black?logo=eslint&logoColor=white&labelColor=464646&style=for-the-badge"></a>
694
+ ```
695
+
696
+ #### Markdown
697
+
698
+ ```md
699
+ [![[Linted by eslint-plugin-chrome-extensions]](https://img.shields.io/badge/Linted_by-eslint--plugin--chrome--extensions-black?logo=eslint&logoColor=white&labelColor=464646&style=for-the-badge)](https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#readme)
700
+ ```
701
+
702
+ ---
703
+
704
+ ## <img height=30 width=27 align="top" src="https://api.iconify.design/ic/baseline-account-balance.svg?color=%232b2b2a"> License
705
+
706
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/src/branch/main/docs/LICENSE.md">
707
+ <img height=85 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/9e820e5d51/assets/images/logos/mit-license/red-pixelated.png"></a>
708
+ <br><br>
709
+
710
+ <details>
711
+
712
+ <summary>Copyright © 2026 <a target="_blank" href="https://codeberg.org/adamlui"><b>Adam Lui</b></a> under the <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/src/branch/main/docs/LICENSE.md"><b>MIT license</b></a>.</summary><br>
713
+
714
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
715
+
716
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
717
+
718
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
719
+
720
+ </details>
721
+
722
+ ---
723
+
724
+ ## <img height=30 width=29 align="top" src="https://api.iconify.design/bxs/package.svg?color=%232b2b2a"> Related
725
+
726
+ <!-- eslint-plugin-constructors -->
727
+
728
+ ### <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#readme"><img height=15 width=auto src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/e9128375d4/assets/images/icons/eslint/purple/icon16.png"> eslint-plugin-constructors</a>
729
+
730
+ <details>
731
+
732
+ <summary>ESLint rules for JavaScript constructor usage.</summary><br>
733
+
734
+ > <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#readme"><img width=555 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-constructors/6eeba7b2cb/assets/images/screenshots/vs-code/squiggles.png"></a>
735
+
736
+ > <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#installation"><b>Install</b></a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#readme">Readme</a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-constructors/#supported-rules">Supported rules</a>
737
+
738
+ </details>
739
+
740
+ <!-- eslint-plugin-greasemonkey -->
741
+
742
+ ### <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme"><img height=15 width=auto src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/e9128375d4/assets/images/icons/eslint/purple/icon16.png"> eslint-plugin-greasemonkey</a>
743
+
744
+ <details>
745
+
746
+ <summary>ESLint rules for Greasemonkey userscripts.</summary><br>
747
+
748
+ > <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme"><img width=555 src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-greasemonkey/c9ac2985bb/assets/images/screenshots/vs-code/squiggles.png"></a>
749
+
750
+ > <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#installation"><b>Install</b></a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#readme">Readme</a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-greasemonkey/#supported-rules">Supported rules</a>
751
+
752
+ </details>
753
+
754
+ <!-- Footer -->
755
+
756
+ <img height=10 width="100%" src="https://cdn.staticdelivr.com/gl/adamlui/ai-web-extensions/d11d2ee/assets/images/separators/gradient-aqua.png">
757
+
758
+ <div align="center">
759
+
760
+ <picture><source media="(prefers-color-scheme: dark)" srcset="https://cdn.staticdelivr.com/gl/adamlui/js-utils/6b0d399/assets/images/icons/tag/white/icon16.svg"><img height=14 src="https://cdn.staticdelivr.com/gl/adamlui/js-utils/6b0d399/assets/images/icons/tag/dark-gray/icon16.svg"></picture>
761
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases"><b>Latest releases</b></a> / <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/issues/new">Report a bug</a> / <a target="_blank" href="https://www.npmjs.com/~adamlui">More packages by author</a> / [Back to top ↑](#top)
762
+
763
+ </div>
764
+
765
+ <!-- Links -->
766
+
767
+ [epc-rules]: #supported-rules