| | |

ESLint 05 🛡️ Rule Severity — off, warn, and error levels

⏱️ Estimated read: 11 min | |

Every ESLint rule has a severity. It is the first value in the rule’s configuration, and it determines two things: whether the rule runs at all, and what happens when it finds a violation. The three severities are "off", "warn", and "error", with numeric equivalents 0, 1, and 2. The difference between them is not cosmetic—it determines whether ESLint exits with code 0 or code 1, which is the entire basis for CI gates, pre-commit hooks, and pull request checks.

This chapter covers the three severity levels and their exit-code consequences, the two places severity can be configured (the flat config file and inline comments), the rules for precedence when both exist, and the practical strategy for using warn as a migration tool rather than a permanent state. It also covers the array syntax for rules that accept options alongside severity, and the --max-warnings interaction that turns warnings into failures without changing the rule configuration.

Key point: "error" makes ESLint exit with code 1 when triggered. "warn" reports the violation but exits with code 0. "off" disables the rule entirely. Only error blocks CI.


Why severity levels exist

The CI gate problem. A linter that reports problems but never fails is a suggestion engine. Severity is how ESLint distinguishes between “this must be fixed before merge” and “this should be looked at eventually.” Setting a rule to "error" causes ESLint to exit with a non-zero code when the rule is violated, which is what CI systems use to block a pull request . Without this distinction, every rule would either be too strict (all errors) or too permissive (all warnings).

The migration problem. Introducing a new rule to an existing codebase often produces dozens or hundreds of violations. Setting it to "error" immediately blocks all development until every violation is fixed. Setting it to "warn" surfaces the violations without breaking the build, allowing the team to fix them incrementally before promoting the rule to "error" . This is the intended use of warn: a transitional state, not a permanent one.

The false-positive problem. Some rules cannot determine with certainty whether a problem exists. A rule that flags a potentially unused variable might be wrong if the variable is used through reflection or in a template string. Setting such rules to "warn" keeps the signal visible without blocking legitimate code . The team reviews the warnings and decides case by case.


a. The three severities and their exit codes

The mapping is fixed and documented:

{
  "rules": {
    "no-unused-vars": "error",
    "no-console": "warn",
    "no-debugger": "off"
  }
}

"error" (or 2): the rule runs, violations are reported as errors, and ESLint exits with code 1 if any error-level violation exists . This is the setting for rules that must block merging.

"warn" (or 1): the rule runs, violations are reported as warnings, and ESLint exits with code 0 even if warnings exist . The warnings appear in the output but do not fail the run. CI passes. This is the setting for rules being introduced gradually or rules with known false positives.

"off" (or 0): the rule does not run. No violations are reported. This is the setting for rules that are irrelevant to the project or that conflict with another tool (such as a formatter) .

// The same configuration with numeric values
{
  "rules": {
    "no-unused-vars": 2,    // error
    "no-console": 1,        // warn
    "no-debugger": 0        // off
  }
}

b. Configuring severity in the flat config file

In eslint.config.js, severity is set inside the rules object. A rule with no options is configured with just the severity string. A rule with options uses an array: the first element is the severity, and the remaining elements are the rule’s options .

// eslint.config.js
export default [
  {
    rules: {
      "no-unused-vars": "error",
      "no-console": "warn",
      "prefer-const": ["error", { ignoreReadBeforeAssign: true }],
    },
  },
];

When multiple configuration objects in the array specify the same rule, the later object takes precedence. This is how file-specific overrides work: a base configuration sets "no-unused-vars": "error", and an object with files: ["**/*.test.js"] sets it to "warn". Test files get the warning; everything else gets the error .

c. Configuring severity inline

Inline comments override the configuration file for the file or line where they appear. The syntax is the same rules object, placed inside a comment .

/* eslint no-console: "off", eqeqeq: "error" */

// Disable a rule for one line
console.log("debug"); // eslint-disable-line no-console

// Disable a rule for the next line
// eslint-disable-next-line no-unused-vars
const unused = 1;

Inline configuration has the highest priority. It is applied after all configuration files, so it wins even if the config file sets the rule to "error" . This is why inline disables should be used sparingly: they are the final override, and they are invisible to anyone reading the config file. The ESLint documentation recommends documenting the reason for any inline disable after a -- separator, and treating inline disables as temporary measures with follow-up tasks .

d. Warnings and the –max-warnings flag

The default behavior of warn is that it does not affect the exit code. This is the correct behavior for a rule being introduced gradually, but it means CI will pass even if there are hundreds of warnings. The --max-warnings CLI flag changes this: if the number of warnings exceeds the threshold, ESLint exits with code 1 .

# Fail if more than 10 warnings
npx eslint . --max-warnings 10

# Fail on any warning
npx eslint . --max-warnings 0

This allows a project to keep rules at "warn" in the configuration while still enforcing a warning budget in CI. A team can set --max-warnings 0 to require zero warnings for merge, without changing every rule from "warn" to "error". The distinction matters because a rule at "warn" can be temporarily suppressed inline with eslint-disable-next-line, while a rule at "error" creates the same exit code regardless of how it is configured.


Complete Example Session

// ============================================
// PART 1: THE THREE SEVERITY LEVELS
// ============================================
// eslint.config.js
export default [
  {
    rules: {
      "no-unused-vars": "error",   // exit 1 if violated
      "no-console": "warn",        // exit 0 if violated
      "no-debugger": "off",        // never runs
    },
  },
];
// ============================================
// PART 2: NUMERIC EQUIVALENTS
// ============================================
export default [
  {
    rules: {
      "no-unused-vars": 2,         // same as "error"
      "no-console": 1,             // same as "warn"
      "no-debugger": 0,            // same as "off"
    },
  },
];
// ============================================
// PART 3: RULE WITH OPTIONS
// ============================================
export default [
  {
    rules: {
      "prefer-const": ["error", { ignoreReadBeforeAssign: true }],
    },
  },
];
// First array element is severity.
// Remaining elements are rule-specific options.
// ============================================
// PART 4: FILE-SPECIFIC SEVERITY OVERRIDE
// ============================================
export default [
  {
    rules: { "no-unused-vars": "error" },
  },
  {
    files: ["**/*.test.js"],
    rules: { "no-unused-vars": "warn" },
  },
];
// Later objects override earlier ones for matching files.
// Test files get warn; everything else gets error.
// ============================================
// PART 5: INLINE OVERRIDE
// ============================================
// This line disables the rule only for the current line
console.log("debug"); // eslint-disable-line no-console

// This line disables the rule for the next line
// eslint-disable-next-line no-unused-vars
const unused = 1;

// Inline config overrides the config file.
// ============================================
// PART 6: INLINE CONFIGURATION COMMENT
// ============================================
/* eslint eqeqeq: "off", curly: "error" */

// This comment applies to the entire file.
// eqeqeq is disabled; curly is set to error.
// ============================================
// PART 7: INLINE DISABLE WITH REASON
// ============================================
// eslint-disable-next-line no-console -- reason: logging required for debugging
console.log("temporary diagnostic");

// The -- separator documents why the disable exists.
// ============================================
// PART 8: WARN DOES NOT FAIL CI BY DEFAULT
// ============================================
// eslint.config.js:
// "no-console": "warn"

// Running: npx eslint .
// Output: warning  Unexpected console statement  no-console
// Exit code: 0
// CI: passes
// ============================================
// PART 9: --max-warnings TURNS WARNINGS INTO A GATE
// ============================================
// npx eslint . --max-warnings 0
// Any warning causes exit code 1.
// CI fails if any warning exists.

// npx eslint . --max-warnings 10
// Exit 1 only if more than 10 warnings exist.
// ============================================
// PART 10: MIGRATION STRATEGY
// ============================================
// Step 1: "no-floating-promises": "warn"   (introduce)
// Step 2: Fix all warnings
// Step 3: "no-floating-promises": "error"  (enforce)

// warn is a transition state, not a destination.

The ten parts covered the three severities, numeric equivalents, options syntax, file-specific overrides, inline disables, inline configuration, disable reasons, default warn behavior, --max-warnings, and the warn-to-error migration path.


Quick Reference

Severity levels

StringNumberExit code on violationUse case
"error"21Enforce in CI, block merge
"warn"10Introduce gradually, false positives
"off"0—Disable entirely

Configuration locations

LocationExamplePrecedence
Config file rules"no-console": "warn"Base
Inline comment/* eslint no-console: "off" */Highest
CLI --max-warnings--max-warnings 0Overrides exit code for warnings

Options syntax

RuleSyntax
No options"rule-name": "error"
With options"rule-name": ["error", { option: value }]

Inline disable syntax

ScopeSyntax
Entire file/* eslint-disable rule-name */
One line// eslint-disable-line rule-name
Next line// eslint-disable-next-line rule-name
Re-enable/* eslint-enable rule-name */

Best Practices

✅ Do This:

// Use error for rules that must block CI
"no-unused-vars": "error"                                // ✅
// Use warn for rules being introduced gradually
"no-floating-promises": "warn"                           // ✅
// Document inline disables with a reason
// eslint-disable-next-line no-console -- required for CLI output  // ✅
// Use --max-warnings in CI to enforce a warning budget
npx eslint . --max-warnings 0                            // ✅
// Promote warn to error after violations are fixed
"no-unused-vars": "error"                                // ✅

❌ Don’t Do This:

// Leave a rule at warn indefinitely
"no-console": "warn"  // never promoted to error         // ❌
// Disable a rule inline without a reason
// eslint-disable-next-line no-console                     // ❌
// Use off for a rule that catches real bugs
"no-undef": "off"                                        // ❌
// Assume warn fails CI without --max-warnings
npx eslint .                                             // ❌ (exit 0)

Common Pitfalls

PitfallWhy It HappensFix
CI passes despite warningswarn exits 0 by defaultAdd --max-warnings 0 or promote to error
Inline disable does not workRule name misspelled or wrong plugin prefixCheck the exact rule ID
Severity override ignoredA later config object overrides itCheck array order; later wins
off rule still reportedInline comment enables itInline has highest precedence
Warning count too high to fixRule set to warn without a planCreate a migration task; promote to error after fixing

Real-World Examples

1. Error severity

{ "rules": { "no-unused-vars": "error" } }

2. Warn severity

{ "rules": { "no-console": "warn" } }

3. Off severity

{ "rules": { "no-debugger": "off" } }

4. Numeric equivalents

{ "rules": { "no-unused-vars": 2, "no-console": 1, "no-debugger": 0 } }

5. Rule with options

{ "rules": { "prefer-const": ["error", { "ignoreReadBeforeAssign": true }] } }

6. File-specific override

{ files: ["**/*.test.js"], rules: { "no-unused-vars": "warn" } }

7. Inline disable one line

// eslint-disable-next-line no-console
console.log("debug");

8. Inline configuration comment

/* eslint eqeqeq: "off", curly: "error" */

9. Disable with reason

// eslint-disable-next-line no-console -- logging for diagnostic

10. CI warning gate

npx eslint . --max-warnings 0

Visual

Severity and exit code

┌─────────────────────────────────────────────────────────────┐
│  SEVERITY → EXIT CODE                                        │
│                                                              │
│  "error" or 2                                                │
│    Violation found ──► exit code 1 ──► CI FAILS             │
│                                                              │
│  "warn" or 1                                                 │
│    Violation found ──► exit code 0 ──► CI PASSES            │
│    (unless --max-warnings is exceeded)                       │
│                                                              │
│  "off" or 0                                                  │
│    Rule does not run ──► no violation ──► exit code 0       │
│                                                              │
│  Only "error" blocks the merge by default.                   │
└─────────────────────────────────────────────────────────────┘

Configuration precedence

┌─────────────────────────────────────────────────────────────┐
│  WHICH SEVERITY WINS?                                        │
│                                                              │
│  Lowest:  Config file base rules                             │
│       │                                                      │
│       ▼                                                      │
│  Higher:  Config file later objects (file-specific)          │
│       │                                                      │
│       ▼                                                      │
│  Highest: Inline configuration comments                      │
│                                                              │
│  Inline overrides everything.                                │
│  Later config objects override earlier ones.                 │
└─────────────────────────────────────────────────────────────┘

Warn-to-error migration

┌─────────────────────────────────────────────────────────────┐
│  MIGRATION PATH                                              │
│                                                              │
│  Step 1: Introduce rule at "warn"                            │
│    "new-rule": "warn"                                        │
│    CI passes, violations visible                             │
│                                                              │
│  Step 2: Fix violations incrementally                        │
│    Developer addresses warnings over time                    │
│                                                              │
│  Step 3: Promote to "error"                                  │
│    "new-rule": "error"                                       │
│    CI now blocks new violations                              │
│                                                              │
│  warn is a transition, not a destination.                    │
└─────────────────────────────────────────────────────────────┘

–max-warnings behavior

┌─────────────────────────────────────────────────────────────┐
│  WARNINGS AND CI                                             │
│                                                              │
│  Config: "no-console": "warn"                                │
│                                                              │
│  npx eslint .                                                │
│    10 warnings found                                         │
│    exit code: 0                                              │
│    CI: PASS                                                  │
│                                                              │
│  npx eslint . --max-warnings 0                               │
│    10 warnings found                                         │
│    exit code: 1                                              │
│    CI: FAIL                                                  │
│                                                              │
│  npx eslint . --max-warnings 20                              │
│    10 warnings found                                         │
│    exit code: 0                                              │
│    CI: PASS (under budget)                                   │
└─────────────────────────────────────────────────────────────┘

Summary

ItemValue
"error" / 2Exit code 1 on violation, blocks CI
"warn" / 1Exit code 0 on violation, CI passes
"off" / 0Rule disabled, no report
Config file locationrules object in flat config
Inline location/* eslint rule: severity */
Inline precedenceHighest, overrides config files
Options syntax["severity", { option: value }]
Warning gate--max-warnings N
Migration patternwarn → fix → error

Key takeaways:

  • Severity is an exit-code decision. "error" makes ESLint fail; "warn" reports but does not fail; "off" does nothing. The distinction exists to support CI gating without forcing every rule to be a blocker immediately.
  • "warn" is a transition state. The documentation recommends using it when introducing a new rule or when a rule has false positives. Leaving a rule at "warn" indefinitely means the violations are visible but never enforced.
  • Inline configuration overrides everything. An inline disable or severity change applies after all config files, which makes it powerful and easy to abuse. Document the reason and treat inline disables as temporary.
  • --max-warnings turns warnings into a gate without changing rule configuration. This is useful for enforcing a warning budget in CI while keeping rules at "warn" for local development.
  • Later config objects override earlier ones. In the flat config array, file-specific overrides work by placing a later object with a files pattern. The last matching object wins.

Remember: Rule severity is not a suggestion. It is the mechanism that determines whether ESLint blocks a merge or merely leaves a comment. Use "error" for rules the team agrees must never be violated. Use "warn" for rules being introduced, rules with legitimate false positives, or rules that flag style preferences rather than bugs. Use "off" for rules that conflict with other tooling. And never leave a rule at "warn" without a plan to either fix the violations and promote it, or turn it off entirely. A warning that nobody intends to fix is just noise.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!