eslint-plugin-chrome-extensions 1.0.1 → 2.1.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 CHANGED
@@ -7,12 +7,12 @@
7
7
  <a href="#license">
8
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
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>
10
+ <img alt="[Requires ESLint >=9.6.0]" src="https://img.shields.io/badge/Requires-ESLint_%3E%3D9.6.0-4B32C3?style=for-the-badge&logo=eslint"></a>
11
11
  <br><br>
12
12
 
13
13
  ESLint rules for Chrome extensions.
14
14
 
15
- <img src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-chrome-extensions/0b5e3e49/assets/images/screenshots/vs-code/squiggles.png">
15
+ <img src="https://cdn.staticdelivr.com/gl/adamlui/eslint-plugin-chrome-extensions/04d2f53b22/assets/images/screenshots/vs-code/squiggles.png">
16
16
 
17
17
  ---
18
18
 
@@ -31,26 +31,35 @@ npm i -D eslint-plugin-chrome-extensions
31
31
  Usage
32
32
  </h2>
33
33
 
34
- Add a bundled config to your `eslint.config.*js`:
34
+ Add bundled configs to your `eslint.config.*js`:
35
35
 
36
36
  ```js
37
37
  ...
38
38
  import chromeExtensions from 'eslint-plugin-chrome-extensions'
39
+ import json from '@eslint/json'
39
40
 
40
41
  export default [
41
42
  // other configs
42
43
  {
43
- files: ['**/*.js'], plugins: { 'chrome-extensions': chromeExtensions },
44
+ files: ['extension/**/*.js'],
45
+ plugins: { 'chrome-extensions': chromeExtensions },
44
46
  rules: {
45
47
  ...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
+ 'chrome-extensions/no-deprecated-apis': 'off' // selectively disable rule
49
+ }
50
+ },
51
+ {
52
+ files: ['extension/manifest.json'], language: 'json/json',
53
+ plugins: { 'chrome-extensions': chromeExtensions, json },
54
+ rules: {
55
+ ...chromeExtensions.configs.manifest.rules,
56
+ 'chrome-extensions/valid-version-field': ['error', { versionType: 'date' }] // selectively tweak rule
48
57
  }
49
58
  }
50
59
  ]
51
60
  ```
52
61
 
53
- Available bundles: `recommended`, `strict` (everything), `problem` (deterministic bugs only)
62
+ Available bundles: `recommended`, `strict` (everything), `problem` (deterministic bugs only), `security`, `manifest`
54
63
 
55
64
  **Note:** Rule config is either a severity string (`'off'`, `'warn'`, `'error'`) or an array `[severity, options]` when the rule accepts options.
56
65
 
@@ -61,6 +70,77 @@ Available bundles: `recommended`, `strict` (everything), `problem` (deterministi
61
70
  Supported rules
62
71
  </h2>
63
72
 
73
+ <h3 id="manifest-key-order">
74
+ <code>manifest-key-order</code>
75
+ <a title="Only works w/ 'json/json' config block" href="#usage">
76
+ <img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-32fcee?style=flat-square" align="middle"></a>
77
+ <a title="Accepts eslint --fix" target="_blank" href="https://eslint.org/docs/latest/use/command-line-interface#--fix">
78
+ <img height=18 alt="[Auto-fixable]" src="https://img.shields.io/badge/Auto--fixable-3b5bdb?style=flat-square" align="middle"></a>
79
+ <a title="Doesn't prevent bugs" href="#manifest-key-order">
80
+ <img height=18 alt="[Stylistic]" src="https://img.shields.io/badge/Stylistic-af68ff?style=flat-square" align="middle"></a>
81
+ <a title="Accepts array w/ options" href="#usage">
82
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
83
+ <a title="Copy rule URL" target="_blank"
84
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23manifest-key-order">
85
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
86
+ </h3>
87
+
88
+ <details>
89
+
90
+ <summary>Enforces a preferred order for manifest keys.</summary>
91
+
92
+ #### Options
93
+
94
+ - `order` (default `[]`) – manifest keys in custom order (e.g. `['name', 'manifest_version']`) to selectively override built-in order with
95
+
96
+ #### <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
97
+
98
+ ```diff
99
+ /* eslint chrome-extensions/manifest-key-order: ['error', { order: ['name', 'manifest_version'] }] */
100
+
101
+ {
102
+ ! "manifest_version": 3,
103
+ "name": "My Extension",
104
+ "version": "1.0.0"
105
+ }
106
+ ```
107
+
108
+ #### <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
109
+
110
+ ```diff
111
+ /* eslint chrome-extensions/manifest-key-order: ['error', { order: ['name', 'manifest_version'] }] */
112
+
113
+ {
114
+ - "manifest_version": 3,
115
+ "name": "My Extension",
116
+ + "manifest_version": 3,
117
+ "version": "1.0.0"
118
+ }
119
+ ```
120
+
121
+ #### When not to use it
122
+
123
+ If you don't care about the order of manifest keys.
124
+
125
+ #### Introduced in
126
+
127
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.0.0">v2.0.0</a> (October 4, 2026)
128
+
129
+ #### See also
130
+
131
+ - [`require-mv3`](#require-mv3)
132
+ - [`valid-mv3-schema`](#valid-mv3-schema)
133
+
134
+ <div align="center">
135
+
136
+ [Back to index ↑][epce-rules]
137
+
138
+ </div>
139
+
140
+ ---
141
+
142
+ </details>
143
+
64
144
  <h3 id="no-content-style-leak">
65
145
  <code>no-content-style-leak</code>
66
146
  <a title="Only included in strict config" href="#usage">
@@ -83,13 +163,16 @@ Available bundles: `recommended`, `strict` (everything), `problem` (deterministi
83
163
  #### <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
164
 
85
165
  ```diff
166
+ + const host = document.createElement('div'),
167
+ + root = host.attachShadow({ mode: 'open' })
168
+ + document.body.append(host)
86
169
  - document.head.append(document.createElement('style'))
87
- + await chrome.scripting.insertCSS({ files: ['content.css'] })
170
+ + root.append(document.createElement('style'))
88
171
  ```
89
172
 
90
173
  #### When not to use it
91
174
 
92
- If you already scope every injected style inside a `shadowRoot` you own.
175
+ If you intentionally interact w/ host page styles.
93
176
 
94
177
  #### Introduced in
95
178
 
@@ -97,13 +180,12 @@ If you already scope every injected style inside a `shadowRoot` you own.
97
180
 
98
181
  #### See also
99
182
 
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>
183
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/content-scripts">Chrome for Developers — Content scripts</a>
184
+ - <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM">MDN — Using shadow DOM</a>
103
185
 
104
186
  <div align="center">
105
187
 
106
- [Back to index ↑][epc-rules]
188
+ [Back to index ↑][epce-rules]
107
189
 
108
190
  </div>
109
191
 
@@ -138,6 +220,7 @@ If you already scope every injected style inside a `shadowRoot` you own.
138
220
 
139
221
  ! chrome.pageAction.show(tabId)
140
222
  ! chrome.extension.getBackgroundPage().doThing()
223
+ ! chrome.tabs.executeScript(tabId, { code: 'doThing()' })
141
224
  ```
142
225
 
143
226
  #### <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
@@ -149,6 +232,8 @@ If you already scope every injected style inside a `shadowRoot` you own.
149
232
  + chrome.action.enable(tabId)
150
233
  - chrome.extension.getBackgroundPage().doThing()
151
234
  + await chrome.runtime.getContexts({ contextTypes: ['BACKGROUND'] })
235
+ - chrome.tabs.executeScript(tabId, { code: 'doThing()' })
236
+ + chrome.scripting.executeScript({ target: { tabId }, func: doThing })
152
237
  ```
153
238
 
154
239
  #### When not to use it
@@ -161,13 +246,127 @@ If you don't want deprecation validation at all. For individual deprecated APIs
161
246
 
162
247
  #### See also
163
248
 
249
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api">Chrome for Developers — API reference</a>
250
+
251
+ <div align="center">
252
+
253
+ [Back to index ↑][epce-rules]
254
+
255
+ </div>
256
+
257
+ ---
258
+
259
+ </details>
260
+
261
+ <h3 id="no-duplicate-manifest-keys">
262
+ <code>no-duplicate-manifest-keys</code>
263
+ <a title="Only works w/ 'json/json' config block" href="#usage">
264
+ <img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-32fcee?style=flat-square" align="middle"></a>
265
+ <a title="Copy rule URL" target="_blank"
266
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-duplicate-manifest-keys">
267
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
268
+ </h3>
269
+
270
+ <details>
271
+
272
+ <summary>Disallows duplicate keys in <code>manifest.json</code>.</summary>
273
+
274
+ #### <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
275
+
276
+ ```diff
277
+ {
278
+ "manifest_version": 3,
279
+ ! "name": "Foo",
280
+ ! "name": "Bar",
281
+ ! "version": "1.0.0",
282
+ ! "version": "2.0.0"
283
+ }
284
+ ```
285
+
286
+ #### <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
287
+
288
+ ```diff
289
+ {
290
+ "manifest_version": 3,
291
+ - "name": "Foo",
292
+ - "name": "Bar",
293
+ "name": "My Extension",
294
+ - "version": "1.0.0",
295
+ - "version": "2.0.0"
296
+ "version": "1.0.0"
297
+ }
298
+ ```
299
+
300
+ #### When not to use it
301
+
302
+ If you rely on a Chromium fork resolving duplicate keys in a specific order.
303
+
304
+ #### Introduced in
305
+
306
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.1.0">v2.1.0</a> (October 5, 2026)
307
+
308
+ #### See also
309
+
310
+ - [`manifest-key-order`](#manifest-key-order)
311
+ - [`no-unused-permissions`](#no-unused-permissions)
312
+
313
+ <div align="center">
314
+
315
+ [Back to index ↑][epce-rules]
316
+
317
+ </div>
318
+
319
+ ---
320
+
321
+ </details>
322
+
323
+ <h3 id="no-insecure-fetch">
324
+ <code>no-insecure-fetch</code>
325
+ <a title="Included in recommended config" href="#usage">
326
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
327
+ <a title="Included in security config" href="#usage">
328
+ <img height=18 alt="[Security]" src="https://img.shields.io/badge/Security-e8357a?style=flat-square" align="middle"></a>
329
+ <a title="Copy rule URL" target="_blank"
330
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-insecure-fetch">
331
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
332
+ </h3>
333
+
334
+ <details>
335
+
336
+ <summary>Flags <code>fetch()</code> and <code>XMLHttpRequest.open()</code> calls that use <code>http://</code>.</summary>
337
+
338
+ #### <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
339
+
340
+ ```diff
341
+ ! fetch('http://api.example.com/data')
342
+ ! xhr.open('GET', 'http://cdn.example.com/lib.js')
343
+ ```
344
+
345
+ #### <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
346
+
347
+ ```diff
348
+ - fetch('http://api.example.com/data')
349
+ + fetch('https://api.example.com/data')
350
+ - xhr.open('GET', 'http://cdn.example.com/lib.js')
351
+ + xhr.open('GET', 'https://cdn.example.com/lib.js')
352
+ ```
353
+
354
+ #### When not to use it
355
+
356
+ If you intentionally target HTTP endpoints.
357
+
358
+ #### Introduced in
359
+
360
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.1.0">v2.1.0</a> (October 5, 2026)
361
+
362
+ #### See also
363
+
364
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/network-requests">Chrome for Developers — Cross-origin network requests</a>
164
365
  - [`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
366
 
168
367
  <div align="center">
169
368
 
170
- [Back to index ↑][epc-rules]
369
+ [Back to index ↑][epce-rules]
171
370
 
172
371
  </div>
173
372
 
@@ -179,6 +378,10 @@ If you don't want deprecation validation at all. For individual deprecated APIs
179
378
  <code>no-insecure-messaging</code>
180
379
  <a title="Included in recommended config" href="#usage">
181
380
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
381
+ <a title="Included in security config" href="#usage">
382
+ <img height=18 alt="[Security]" src="https://img.shields.io/badge/Security-e8357a?style=flat-square" align="middle"></a>
383
+ <a title="Accepts array w/ options" href="#usage">
384
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
182
385
  <a title="Copy rule URL" target="_blank"
183
386
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-insecure-messaging">
184
387
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
@@ -186,36 +389,55 @@ If you don't want deprecation validation at all. For individual deprecated APIs
186
389
 
187
390
  <details>
188
391
 
189
- <summary>Requires external message handlers validate <code>sender</code> + flags unfiltered <code>tabs.query()</code>.</summary>
392
+ <summary>Requires sender/origin validation + filtered <code>tabs.query()</code> + explicit <code>window.postMessage()</code> target.</summary>
393
+
394
+ #### Options
395
+
396
+ - `disallowWildcardTarget` (default `true`) – flag <code>postMessage()</code> calls that use <code>'*'</code> as <code>targetOrigin</code>
190
397
 
191
398
  #### <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
399
 
193
400
  ```diff
194
- ! chrome.runtime.onMessageExternal.addListener((msg, sender, respond) => {
195
- ! handle(msg) })
401
+ chrome.runtime.onMessageExternal.addListener((msg, sender, respond) => {
402
+ ! handle(msg)
403
+ })
404
+
405
+ window.addEventListener('message', event => {
406
+ ! handle(event.data)
407
+ })
408
+
409
+ ! chrome.tabs.query({})
410
+ .then(tabs => tabs.forEach(tab =>
411
+ chrome.tabs.sendMessage(tab.id, { action: 'ping' })))
196
412
 
197
- ! chrome.tabs.query({}).then(tabs => tabs.forEach(tab =>
198
- ! chrome.tabs.sendMessage(tab.id, { action: 'ping' })))
413
+ ! window.postMessage(payload, '*')
199
414
  ```
200
415
 
201
416
  #### <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
417
 
203
418
  ```diff
204
- - chrome.runtime.onMessageExternal.addListener((msg, sender, respond) => {
205
- - handle(msg) })
206
- + chrome.runtime.onMessageExternal.addListener((msg, sender, respond) => {
419
+ chrome.runtime.onMessageExternal.addListener((msg, sender, respond) => {
207
420
  + if (sender.id != 'allowed-extension-id') return
208
- + handle(msg)
209
- + })
421
+ handle(msg)
422
+ })
210
423
 
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' })))
424
+ window.addEventListener('message', event => {
425
+ + if (event.origin != 'https://allowed-origin.com') return
426
+ handle(event.data)
427
+ })
428
+
429
+ - chrome.tabs.query({})
430
+ + chrome.tabs.query({ url: 'https://allowed-origin.com/*' })
431
+ .then(tabs => tabs.forEach(tab =>
432
+ chrome.tabs.sendMessage(tab.id, { action: 'ping' })))
433
+
434
+ - window.postMessage(payload, '*')
435
+ + window.postMessage(payload, 'https://allowed-origin.com')
214
436
  ```
215
437
 
216
438
  #### When not to use it
217
439
 
218
- If you trust every external caller and open tab.
440
+ If you trust every external caller and open tab, or you intentionally broadcast to any origin.
219
441
 
220
442
  #### Introduced in
221
443
 
@@ -223,12 +445,13 @@ If you trust every external caller and open tab.
223
445
 
224
446
  #### See also
225
447
 
226
- - [`no-deprecated-apis`](#no-deprecated-apis)
227
- - [`no-missing-permissions`](#no-missing-permissions)
448
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/runtime#event-onMessageExternal">Chrome for Developers — `runtime.onMessageExternal`</a>
449
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/tabs#method-query">Chrome for Developers — `tabs.query()`</a>
450
+ - <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage#security_concerns">MDN — `window.postMessage()` security concerns</a>
228
451
 
229
452
  <div align="center">
230
453
 
231
- [Back to index ↑][epc-rules]
454
+ [Back to index ↑][epce-rules]
232
455
 
233
456
  </div>
234
457
 
@@ -240,6 +463,8 @@ If you trust every external caller and open tab.
240
463
  <code>no-invalid-permissions</code>
241
464
  <a title="Included in recommended config" href="#usage">
242
465
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
466
+ <a title="Works w/ 'json/json' config block" href="#usage">
467
+ <img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-32fcee?style=flat-square" align="middle"></a>
243
468
  <a title="Accepts array w/ options" href="#usage">
244
469
  <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
245
470
  <a title="Copy rule URL" target="_blank"
@@ -262,8 +487,8 @@ If you trust every external caller and open tab.
262
487
  /* eslint chrome-extensions/no-invalid-permissions: ['error', { target: 'mv3' }] */
263
488
 
264
489
  {
265
- ! "permissions": ["browserAction", "tabs"],
266
- ! "optional_permissions": ["history", "pageAction"]
490
+ ! "permissions": ["browserAction", "tabs"],
491
+ ! "optional_permissions": ["history", "pageAction"]
267
492
  }
268
493
  ```
269
494
 
@@ -273,10 +498,10 @@ If you trust every external caller and open tab.
273
498
  /* eslint chrome-extensions/no-invalid-permissions: ['error', { target: 'mv3' }] */
274
499
 
275
500
  {
276
- - "permissions": ["browserAction", "tabs"],
277
- + "permissions": ["tabs"],
278
- - "optional_permissions": ["history", "pageAction"]
279
- + "optional_permissions": ["history"]
501
+ - "permissions": ["browserAction", "tabs"],
502
+ + "permissions": ["tabs"],
503
+ - "optional_permissions": ["history", "pageAction"]
504
+ + "optional_permissions": ["history"]
280
505
  }
281
506
  ```
282
507
 
@@ -295,7 +520,7 @@ If you don't want permission validation at all. For custom permissions from a Ch
295
520
 
296
521
  <div align="center">
297
522
 
298
- [Back to index ↑][epc-rules]
523
+ [Back to index ↑][epce-rules]
299
524
 
300
525
  </div>
301
526
 
@@ -324,7 +549,7 @@ If you don't want permission validation at all. For custom permissions from a Ch
324
549
  ! "permissions": []
325
550
  }
326
551
 
327
- // background.js
552
+ // service-worker.js
328
553
  ! chrome.cookies.get({ url: 'https://example.com', name: 'x' })
329
554
  ! chrome.history.search({ text: '' })
330
555
  ```
@@ -338,7 +563,7 @@ If you don't want permission validation at all. For custom permissions from a Ch
338
563
  + "permissions": ["cookies", "history"]
339
564
  }
340
565
 
341
- // background.js
566
+ // service-worker.js
342
567
  chrome.cookies.get({ url: 'https://example.com', name: 'x' })
343
568
  chrome.history.search({ text: '' })
344
569
  ```
@@ -358,7 +583,70 @@ If you use APIs through aliases or wrappers that obscure the original call site.
358
583
 
359
584
  <div align="center">
360
585
 
361
- [Back to index ↑][epc-rules]
586
+ [Back to index ↑][epce-rules]
587
+
588
+ </div>
589
+
590
+ ---
591
+
592
+ </details>
593
+
594
+ <h3 id="no-missing-web-accessible-resources">
595
+ <code>no-missing-web-accessible-resources</code>
596
+ <a title="Included in recommended config" href="#usage">
597
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
598
+ <a title="Copy rule URL" target="_blank"
599
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-missing-web-accessible-resources">
600
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
601
+ </h3>
602
+
603
+ <details>
604
+
605
+ <summary>Flags <code>chrome.runtime.getURL()</code> paths missing from <code>web_accessible_resources</code>.</summary>
606
+
607
+ #### <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
608
+
609
+ ```diff
610
+ // manifest.json
611
+ {
612
+ "manifest_version": 3,
613
+ ! "web_accessible_resources": []
614
+ }
615
+
616
+ // content.js
617
+ ! img.src = chrome.runtime.getURL('logo.png')
618
+ ```
619
+
620
+ #### <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
621
+
622
+ ```diff
623
+ // manifest.json
624
+ {
625
+ "manifest_version": 3,
626
+ - "web_accessible_resources": []
627
+ + "web_accessible_resources": [{ "resources": ["logo.png"], "matches": ["<all_urls>"] }]
628
+ }
629
+
630
+ // content.js
631
+ img.src = chrome.runtime.getURL('logo.png')
632
+ ```
633
+
634
+ #### When not to use it
635
+
636
+ If your paths are built at runtime.
637
+
638
+ #### Introduced in
639
+
640
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.1.0">v2.1.0</a> (October 5, 2026)
641
+
642
+ #### See also
643
+
644
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/web-accessible-resources">Chrome for Developers — `web_accessible_resources`</a>
645
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/api/runtime#method-getURL">Chrome for Developers — `runtime.getURL()`</a>
646
+
647
+ <div align="center">
648
+
649
+ [Back to index ↑][epce-rules]
362
650
 
363
651
  </div>
364
652
 
@@ -370,6 +658,8 @@ If you use APIs through aliases or wrappers that obscure the original call site.
370
658
  <code>no-remote-code</code>
371
659
  <a title="Included in recommended config" href="#usage">
372
660
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
661
+ <a title="Included in security config" href="#usage">
662
+ <img height=18 alt="[Security]" src="https://img.shields.io/badge/Security-e8357a?style=flat-square" align="middle"></a>
373
663
  <a title="Accepts array w/ options" href="#usage">
374
664
  <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
375
665
  <a title="Copy rule URL" target="_blank"
@@ -379,7 +669,7 @@ If you use APIs through aliases or wrappers that obscure the original call site.
379
669
 
380
670
  <details>
381
671
 
382
- <summary>Disallows remote or dynamically-evaluated code.</summary>
672
+ <summary>Disallows remote or dynamically-evaluated code (blocked by MV3's CSP at runtime).</summary>
383
673
 
384
674
  #### Options
385
675
 
@@ -389,25 +679,28 @@ If you use APIs through aliases or wrappers that obscure the original call site.
389
679
  #### <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
680
 
391
681
  ```diff
682
+ ! await import('https://cdn.example.com/lib.js')
392
683
  ! const run = eval('1 + 1')
393
684
  ! setTimeout('doThing()', 1000)
394
- ! await import('https://cdn.example.com/lib.js')
685
+ ! chrome.scripting.executeScript({ target: { tabId }, func: () => doThing(value) })
395
686
  ```
396
687
 
397
688
  #### <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
398
689
 
399
690
  ```diff
691
+ - await import('https://cdn.example.com/lib.js')
692
+ + await import('./lib.js')
400
693
  - const run = eval('1 + 1')
401
694
  + const run = 1 + 1
402
695
  - setTimeout('doThing()', 1000)
403
696
  + setTimeout(doThing, 1000)
404
- - await import('https://cdn.example.com/lib.js')
405
- + await import('./lib.js')
697
+ - chrome.scripting.executeScript({ target: { tabId }, func: () => doThing(value) })
698
+ + chrome.scripting.executeScript({ target: { tabId }, func: doThing, args: [value] })
406
699
  ```
407
700
 
408
701
  #### When not to use it
409
702
 
410
- Never (MV3 CSP blocks remote code at runtime).
703
+ If you intentionally target forks w/ relaxed CSPs. For individual patterns you still need, prefer narrowing via `patterns` over disabling the rule.
411
704
 
412
705
  #### Introduced in
413
706
 
@@ -415,12 +708,76 @@ Never (MV3 CSP blocks remote code at runtime).
415
708
 
416
709
  #### See also
417
710
 
418
- - [`no-deprecated-apis`](#no-deprecated-apis)
711
+ - [`require-mv3`](#require-mv3)
419
712
  - [`valid-mv3-schema`](#valid-mv3-schema)
420
713
 
421
714
  <div align="center">
422
715
 
423
- [Back to index ↑][epc-rules]
716
+ [Back to index ↑][epce-rules]
717
+
718
+ </div>
719
+
720
+ ---
721
+
722
+ </details>
723
+
724
+ <h3 id="no-unrestricted-host-permissions">
725
+ <code>no-unrestricted-host-permissions</code>
726
+ <a title="Only works w/ 'json/json' config block" href="#usage">
727
+ <img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-32fcee?style=flat-square" align="middle"></a>
728
+ <a title="Accepts array w/ options" href="#usage">
729
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
730
+ <a title="Copy rule URL" target="_blank"
731
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-unrestricted-host-permissions">
732
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
733
+ </h3>
734
+
735
+ <details>
736
+
737
+ <summary>Disallows broad host permissions that grant access to every site.</summary>
738
+
739
+ #### Options
740
+
741
+ - `allowedPatterns` (default `[]`) – host patterns to explicitly allow (e.g. `['<all_urls>']`)
742
+
743
+ #### <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
744
+
745
+ ```diff
746
+ {
747
+ "manifest_version": 3,
748
+ ! "host_permissions": ["<all_urls>"],
749
+ ! "optional_host_permissions": ["*://*/*"]
750
+ }
751
+ ```
752
+
753
+ #### <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
754
+
755
+ ```diff
756
+ {
757
+ "manifest_version": 3,
758
+ - "host_permissions": ["<all_urls>"],
759
+ + "host_permissions": ["https://example.com/*", "https://*.trusted.com/*"],
760
+ - "optional_host_permissions": ["*://*/*"]
761
+ + "optional_host_permissions": ["https://user-content.example.com/*"]
762
+ }
763
+ ```
764
+
765
+ #### When not to use it
766
+
767
+ If you legitimately need broad access (e.g. ad blockers or dev tools).
768
+
769
+ #### Introduced in
770
+
771
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.1.0">v2.1.0</a> (October 5, 2026)
772
+
773
+ #### See also
774
+
775
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/declare-permissions">Chrome for Developers — Declare permissions</a>
776
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/develop/concepts/match-patterns">Chrome for Developers — Match patterns</a>
777
+
778
+ <div align="center">
779
+
780
+ [Back to index ↑][epce-rules]
424
781
 
425
782
  </div>
426
783
 
@@ -430,8 +787,8 @@ Never (MV3 CSP blocks remote code at runtime).
430
787
 
431
788
  <h3 id="no-unused-permissions">
432
789
  <code>no-unused-permissions</code>
433
- <a title="Only included in recommended + strict configs" href="#usage">
434
- <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
790
+ <a title="Only works w/ 'json/json' config block" href="#usage">
791
+ <img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-32fcee?style=flat-square" align="middle"></a>
435
792
  <a title="Copy rule URL" target="_blank"
436
793
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23no-unused-permissions">
437
794
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
@@ -449,7 +806,7 @@ Never (MV3 CSP blocks remote code at runtime).
449
806
  ! "permissions": ["tabs", "downloads"]
450
807
  }
451
808
 
452
- // background.js
809
+ // service-worker.js
453
810
  chrome.tabs.create({ url: 'https://example.com' })
454
811
  ```
455
812
 
@@ -462,7 +819,7 @@ Never (MV3 CSP blocks remote code at runtime).
462
819
  + "permissions": ["tabs"]
463
820
  }
464
821
 
465
- // background.js
822
+ // service-worker.js
466
823
  chrome.tabs.create({ url: 'https://example.com' })
467
824
  ```
468
825
 
@@ -481,7 +838,7 @@ If you access the granted API through an alias or computed property rather than
481
838
 
482
839
  <div align="center">
483
840
 
484
- [Back to index ↑][epc-rules]
841
+ [Back to index ↑][epce-rules]
485
842
 
486
843
  </div>
487
844
 
@@ -493,6 +850,8 @@ If you access the granted API through an alias or computed property rather than
493
850
  <code>require-mv3</code>
494
851
  <a title="Included in recommended config" href="#usage">
495
852
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
853
+ <a title="Works w/ 'json/json' config block" href="#usage">
854
+ <img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-32fcee?style=flat-square" align="middle"></a>
496
855
  <a title="Copy rule URL" target="_blank"
497
856
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23require-mv3">
498
857
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
@@ -505,7 +864,6 @@ If you access the granted API through an alias or computed property rather than
505
864
  #### <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
506
865
 
507
866
  ```diff
508
- // manifest.json
509
867
  {
510
868
  ! "manifest_version": 2,
511
869
  "name": "My Extension",
@@ -516,7 +874,6 @@ If you access the granted API through an alias or computed property rather than
516
874
  #### <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
517
875
 
518
876
  ```diff
519
- // manifest.json
520
877
  {
521
878
  - "manifest_version": 2,
522
879
  + "manifest_version": 3,
@@ -527,7 +884,7 @@ If you access the granted API through an alias or computed property rather than
527
884
 
528
885
  #### When not to use it
529
886
 
530
- If you intentionally target MV2 for a Chromium fork that still supports it.
887
+ If you intentionally target MV2 for a browser that still supports it (e.g. Firefox).
531
888
 
532
889
  #### Introduced in
533
890
 
@@ -539,7 +896,78 @@ If you intentionally target MV2 for a Chromium fork that still supports it.
539
896
 
540
897
  <div align="center">
541
898
 
542
- [Back to index ↑][epc-rules]
899
+ [Back to index ↑][epce-rules]
900
+
901
+ </div>
902
+
903
+ ---
904
+
905
+ </details>
906
+
907
+ <h3 id="valid-manifest-icons">
908
+ <code>valid-manifest-icons</code>
909
+ <a title="Only works w/ 'json/json' config block" href="#usage">
910
+ <img height=18 alt="[Manifest only]" src="https://img.shields.io/badge/Manifest_only-32fcee?style=flat-square" align="middle"></a>
911
+ <a title="Accepts array w/ options" href="#usage">
912
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
913
+ <a title="Copy rule URL" target="_blank"
914
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23valid-manifest-icons">
915
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
916
+ </h3>
917
+
918
+ <details>
919
+
920
+ <summary>Ensures manifest <code>icons</code> exist and use the recommended sizes.</summary>
921
+
922
+ #### Options
923
+
924
+ - `requireRecommendedSizes` (default `true`) – require all of `16`, `32`, `48`, `128`
925
+ - `checkIconExists` (default `true`) – flag icon paths that don't resolve to an existing file
926
+ - `noSVG` (default `true`) – flag SVG icon paths (unsupported by Chrome)
927
+
928
+ #### <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
929
+
930
+ ```diff
931
+ {
932
+ "manifest_version": 3,
933
+ "icons": {
934
+ ! "48": "icon48.svg",
935
+ "256": "icon256.png"
936
+ }
937
+ }
938
+ ```
939
+
940
+ #### <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
941
+
942
+ ```diff
943
+ {
944
+ "manifest_version": 3,
945
+ "icons": {
946
+ + "16": "icon16.png",
947
+ + "32": "icon32.png",
948
+ - "48": "icon48.svg",
949
+ + "48": "icon48.png",
950
+ + "128": "icon128.png",
951
+ "256": "icon256.png"
952
+ }
953
+ }
954
+ ```
955
+
956
+ #### When not to use it
957
+
958
+ If your icons are injected at build time, or you target browsers that support SVGs (e.g. Firefox).
959
+
960
+ #### Introduced in
961
+
962
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.1.0">v2.1.0</a> (October 5, 2026)
963
+
964
+ #### See also
965
+
966
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/icons">Chrome for Developers — `icons`</a>
967
+
968
+ <div align="center">
969
+
970
+ [Back to index ↑][epce-rules]
543
971
 
544
972
  </div>
545
973
 
@@ -551,6 +979,8 @@ If you intentionally target MV2 for a Chromium fork that still supports it.
551
979
  <code>valid-mv3-schema</code>
552
980
  <a title="Included in recommended config" href="#usage">
553
981
  <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
982
+ <a title="Works w/ 'json/json' config block" href="#usage">
983
+ <img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-32fcee?style=flat-square" align="middle"></a>
554
984
  <a title="Copy rule URL" target="_blank"
555
985
  href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23valid-mv3-schema">
556
986
  <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
@@ -564,11 +994,11 @@ If you intentionally target MV2 for a Chromium fork that still supports it.
564
994
 
565
995
  ```diff
566
996
  {
567
- "manifest_version": 3,
568
- ! "browser_action": {},
569
- ! "page_action": {},
570
- ! "background": { "scripts": ["bg.js"], "persistent": true },
571
- ! "content_security_policy": "script-src 'self' 'unsafe-eval'"
997
+ "manifest_version": 3,
998
+ ! "browser_action": {},
999
+ ! "page_action": {},
1000
+ ! "background": { "scripts": ["background.js"], "persistent": true },
1001
+ ! "content_security_policy": "script-src 'self' 'unsafe-eval'"
572
1002
  }
573
1003
  ```
574
1004
 
@@ -576,20 +1006,20 @@ If you intentionally target MV2 for a Chromium fork that still supports it.
576
1006
 
577
1007
  ```diff
578
1008
  {
579
- "manifest_version": 3,
580
- - "browser_action": {},
581
- - "page_action": {},
582
- + "action": {},
583
- - "background": { "scripts": ["bg.js"], "persistent": true },
584
- + "background": { "service_worker": "sw.js" },
585
- - "content_security_policy": "script-src 'self' 'unsafe-eval'"
586
- + "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'self'" }
1009
+ "manifest_version": 3,
1010
+ - "browser_action": {},
1011
+ - "page_action": {},
1012
+ + "action": {},
1013
+ - "background": { "scripts": ["background.js"], "persistent": true },
1014
+ + "background": { "service_worker": "service-worker.js" },
1015
+ - "content_security_policy": "script-src 'self' 'unsafe-eval'"
1016
+ + "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'self'" }
587
1017
  }
588
1018
  ```
589
1019
 
590
1020
  #### When not to use it
591
1021
 
592
- If you intentionally target MV2 for a Chromium fork that still supports it.
1022
+ If you intentionally target MV2 for a browser that still supports it (e.g. Firefox).
593
1023
 
594
1024
  #### Introduced in
595
1025
 
@@ -602,7 +1032,7 @@ If you intentionally target MV2 for a Chromium fork that still supports it.
602
1032
 
603
1033
  <div align="center">
604
1034
 
605
- [Back to index ↑][epc-rules]
1035
+ [Back to index ↑][epce-rules]
606
1036
 
607
1037
  </div>
608
1038
 
@@ -629,7 +1059,7 @@ If you intentionally target MV2 for a Chromium fork that still supports it.
629
1059
  ! (function init() { chrome.runtime.onMessage.addListener(handler) })()
630
1060
  ! setInterval(() => {}, 5000)
631
1061
  ! const loc = window.location
632
- ! localStorage.setItem('key', 'val')
1062
+ ! localStorage.setItem('key', 'value')
633
1063
  ```
634
1064
 
635
1065
  #### <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
@@ -655,12 +1085,82 @@ If your background context isn't a service worker.
655
1085
 
656
1086
  #### See also
657
1087
 
658
- - [`no-content-style-leak`](#no-content-style-leak)
1088
+ - [`require-mv3`](#require-mv3)
659
1089
  - [`no-remote-code`](#no-remote-code)
660
1090
 
661
1091
  <div align="center">
662
1092
 
663
- [Back to index ↑][epc-rules]
1093
+ [Back to index ↑][epce-rules]
1094
+
1095
+ </div>
1096
+
1097
+ ---
1098
+
1099
+ </details>
1100
+
1101
+ <h3 id="valid-version-field">
1102
+ <code>valid-version-field</code>
1103
+ <a title="Included in recommended config" href="#usage">
1104
+ <img height=18 alt="[Recommended]" src="https://img.shields.io/badge/Recommended-f1f20a?style=flat-square" align="middle"></a>
1105
+ <a title="Works w/ 'json/json' config block" href="#usage">
1106
+ <img height=18 alt="[Manifest]" src="https://img.shields.io/badge/Manifest-32fcee?style=flat-square" align="middle"></a>
1107
+ <a title="Accepts array w/ options" href="#usage">
1108
+ <img height=18 alt="[Configurable]" src="https://img.shields.io/badge/Configurable-black?style=flat-square" align="middle"></a>
1109
+ <a title="Copy rule URL" target="_blank"
1110
+ href="https://copy-to-clipboard.onrender.com/copy/https%3A%2F%2Fcodeberg.org%2Fadamlui%2Feslint-plugin-chrome-extensions%2F%23valid-version-field">
1111
+ <img height=16 width=21 alt="[Copy rule URL]" src="https://api.iconify.design/mdi/content-copy.svg?color=%23cccccc" align="middle"></a>
1112
+ </h3>
1113
+
1114
+ <details>
1115
+
1116
+ <summary>Validates the manifest <code>version</code> field.</summary>
1117
+
1118
+ #### Options
1119
+
1120
+ - `versionType` (default `'chrome'`) – version format to enforce (`'chrome'`, `'semver'`, `'date'`)
1121
+ - `allowFutureDate` (default `false`) – allow `version` dates later than the current date
1122
+ - `timezone` (default `''`) – <a target="_blank" href="https://data.iana.org/time-zones/tzdb/zone1970.tab">IANA timezone</a> used to determine the current date (when omitted, only year/month are enforced)
1123
+
1124
+ #### <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
1125
+
1126
+ ```diff
1127
+ /* eslint chrome-extensions/valid-version-field: ['error', { versionType: 'date' }] */
1128
+
1129
+ {
1130
+ "manifest_version": 3,
1131
+ ! "version": "2026.1.111",
1132
+ "name": "My Extension"
1133
+ }
1134
+ ```
1135
+
1136
+ #### <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
1137
+
1138
+ ```diff
1139
+ /* eslint chrome-extensions/valid-version-field: ['error', { versionType: 'date' }] */
1140
+
1141
+ {
1142
+ "manifest_version": 3,
1143
+ - "version": "2026.1.111",
1144
+ + "version": "2026.10.3",
1145
+ "name": "My Extension"
1146
+ }
1147
+ ```
1148
+
1149
+ #### When not to use it
1150
+
1151
+ If you don't care about version format consistency, or your version is injected at build time.
1152
+
1153
+ #### Introduced in
1154
+
1155
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/releases/tag/v2.0.0">v2.0.0</a> (October 4, 2026)
1156
+
1157
+ #### See also
1158
+
1159
+ - <a target="_blank" href="https://developer.chrome.com/docs/extensions/reference/manifest/version">Chrome for Developers — `version`</a>
1160
+
1161
+ <div align="center">
1162
+
1163
+ [Back to index ↑][epce-rules]
664
1164
 
665
1165
  </div>
666
1166
 
@@ -668,7 +1168,7 @@ If your background context isn't a service worker.
668
1168
 
669
1169
  ---
670
1170
 
671
- ## <img height=30 align="top" src="https://api.iconify.design/boxicons/code-alt-filled.svg?color=%232b2b2a"> Shield embed
1171
+ ## <img width=30 align="top" src="https://api.iconify.design/boxicons/code-alt-filled.svg?color=%232b2b2a"> Shield embed
672
1172
 
673
1173
  <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#readme">
674
1174
  <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>
@@ -676,7 +1176,7 @@ If your background context isn't a service worker.
676
1176
  #### HTML
677
1177
 
678
1178
  ```html
679
- <a href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#readme">
1179
+ <a target="_blank" href="https://codeberg.org/adamlui/eslint-plugin-chrome-extensions/#readme">
680
1180
  <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>
681
1181
  ```
682
1182
 
@@ -751,4 +1251,4 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
751
1251
 
752
1252
  <!-- Links -->
753
1253
 
754
- [epc-rules]: #supported-rules
1254
+ [epce-rules]: #supported-rules