Prettier and ESLint Indent Rules Conflicting: the Fix
Stop fighting indent: Expected indentation of 2 spaces but found 4 by turning off the rule that conflicts with Prettier, not by retuning it.
A .eslintrc.js sets indent: ['error', 2]. Prettier reformats the file with tabWidth: 2.
ESLint still fails:
Expected indentation of 2 spaces but found 4. (indent)Both tools are configured for 2 spaces. Neither is wrong by its own rules, and the error does not go away by nudging the number.
TL;DR#
ESLint's indent rule and Prettier's formatter are two independent implementations of
"how should this be indented", built by different projects with different edge-case
decisions (switch/case, JSX attributes, chained calls, ternaries). They will diverge on
some real files even when both are set to the same number of spaces. The fix is not to
retune indent — it is to turn it off and let Prettier own indentation entirely, with
eslint-config-prettier doing that in
one line instead of a manual rule-by-rule disable.
Why it happens#
This exact conflict was raised against ESLint itself. In eslint/eslint#10930, ESLint maintainer Kai Cataldo confirmed the mechanism in a comment the original Stack Overflow answer quotes: ESLint's indent rule and Prettier are "completely separate implementations" of the same formatting goal — two different approaches to the same problem — and, in Cataldo's words, "it's guaranteed that they will clash."
There is a second, more current reason the conflict is getting less visible by the day: ESLint's core indent rule is now deprecated. ESLint's own rule metadata lists it among the rules replaced by the separate @stylistic/eslint-plugin package, which now owns all of ESLint's old formatting rules (indent, quotes, semi, and the rest). The core rule still ships and still runs if a config turns it on — ESLint has not removed it — but no current recommended config enables it by default, and a project that still has indent: ['error', 2] sitting in an old .eslintrc.js inherited it from a 2019-era config, not from anything ESLint ships today.
So the realistic 2026 version of this bug is one of two configs:
- An old
.eslintrc.js(or a flateslint.config.jsthat still imports@stylistic/eslint-pluginforindent) enabling a formatting rule that duplicates Prettier. - A shared config (
airbnb,standard, a team preset) that enablesindentor a@stylistic/@typescript-eslintindentation rule internally — handled by placing"prettier"after it in"extends", step 2 below.
Fix#
1. Install eslint-config-prettier#
npm install --save-dev eslint-config-prettierThis package does not format anything. It only turns off the ESLint rules that fight
Prettier — including the core indent, the @stylistic/eslint-plugin equivalents, and
the @typescript-eslint indentation rules, per its own plugin list. A current eslint-config-prettier covers all of those plugins from the single "prettier" entry — its own README notes that older guides recommending a separate "prettier/react" or "prettier/@typescript-eslint" config are no longer necessary. If ESLint instead stops on a TypeScript file with Parsing error: The keyword 'enum' is reserved, Prettier is not the cause: set @typescript-eslint/parser as the parser.
2. Put it last, in both config formats#
With the legacy .eslintrc.* format (still supported, no longer the default since ESLint 9):
{
"extends": [
"plugin:@typescript-eslint/recommended",
"prettier"
]
}"prettier" must be the last entry so it overrides the rules the earlier configs turned
on — ESLint applies "extends" entries in order, and only a later one can disable a rule an
earlier one enabled.
With a flat eslint.config.js (the ESLint 9+ default, confirmed by the current configuration files doc):
import tseslint from 'typescript-eslint';
import eslintConfigPrettier from 'eslint-config-prettier/flat';
export default [
...tseslint.configs.recommended,
eslintConfigPrettier,
];Flat config has no implicit override order from "extends" — array position is the order,
so eslintConfigPrettier must be the last array entry for the same reason.
3. Delete the manual indent rule entirely#
Do not leave indent: ['error', 2] in the rules block and expect "prettier" to beat it.
eslint-config-prettier's own docs are explicit about this: for the legacy format, a rule set
directly in "rules" always overrides one disabled by an extended config, because that is how
"extends" is designed to work. The config package cannot touch your own rules block — only
the CLI helper tool below can flag it.
rules: {
- indent: ['error', 2],
quotes: ['error', 'single'],
}4. Catch what you missed with the CLI helper#
npx eslint-config-prettier path/to/a/real/file.jsThis reports any rule in your own rules block (not an extended config) that conflicts with
Prettier — exactly the case step 3 fixes. It is shipped inside eslint-config-prettier
itself, so no extra install is needed.
5. Only add eslint-plugin-prettier if you want Prettier diffs as lint errors#
eslint-config-prettier disables conflicting rules; it does not run Prettier. If the goal is
also to fail lint on formatting drift (instead of running prettier --check separately), add
eslint-plugin-prettier and its bundled
recommended config, which wires up both packages in one step:
// eslint.config.js
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
export default [
// other configs
eslintPluginPrettierRecommended,
];or, legacy format: "extends": ["plugin:prettier/recommended"], last in the array.
Verify the fix#
- Run
npx eslint .on a file Prettier just reformatted. Theindenterror (or its@stylistic/@typescript-eslintequivalent) must be gone — if it still fires, runnpx eslint-config-prettier <file>to find the rule enabled directly inrules(step 3). - Run
npx eslint --print-config path/to/file.js | grep '"indent"'(or the equivalent on Windows) and confirm the rule's severity is"off"(0), not just absent — an unset rule can still be turned on by a shared config loaded afterwards. - Format a file with a construct the two tools are known to disagree on — a
switch/caseblock or a chained method call spanning several lines — and confirm ESLint reports nothing on it after a Prettier pass.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
What a shared config changes#
A shared config such as airbnb-base or standard can still enable indent or a
@typescript-eslint indentation rule inside itself. Because it is extended before
"prettier" (or placed before it in a flat config array), "prettier" still disables it —
the override order from step 2 is exactly what handles this case, with no extra step. The
one thing that order cannot fix is a rule set directly in the project's own rules block
(step 3), because "extends" is designed to let a project's own rules win.
If ESLint next reports a rule definition that cannot be found — the same "not found" family
as Definition for rule 'import/extensions' was not found, which happens when the plugin
that owns a rule is not registered in the config — see
the import/extensions fix
for how to register a missing plugin.
Next.js projects hit the flat-config version of this fix directly: Next.js 16 removed
next lint and the eslint next.config option entirely, so linting runs as a plain
eslint.config.mjs with no Next.js wrapper. See the
ESLint 9 flat-config migration guide
for the codemod and the eslint-config-next flat-config subpaths.
And if a rule-config comment such as // eslint @typescript-eslint/no-explicit-any: "off" is
silently ignored instead of a conflicting-formatting error, that is a different ESLint
footgun with its own fix in
Unexpected any. Specify a different type: fix or disable.
Indentation conflicts are the most visible symptom of running two formatting engines on the
same files, but they are not the only one — quotes, semicolons and trailing commas can clash
the same way. eslint-config-prettier disables all of them from the same plugin list, which
is why disabling the conflicting rules, rather than tuning them to match, is the fix that
holds regardless of which construct triggers the next disagreement.
Further reading: Fix 'originalKeywordKind' DeprecationError in TypeScript. Also see Fix ESLint: Module.createRequire is not a function. Also see Fix Cannot resolve symbol 'Routes': stale JetBrains typings. Also see Fix 'ESLint was configured to run on' in Cypress tests.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
Fix ESLint: Module.createRequire is not a function
Reproduced on Node 10 vs Node 18 with the same node_modules: the crash is the Node runtime executing ESLint, not Parcel or a broken module resolution.
Fix ESLint 'import/extensions' Definition Not Found
The import/extensions error appears when the eslint-plugin-import package isn't loaded. Register the plugin and extend airbnb-base to resolve it.
Fix 'originalKeywordKind' DeprecationError in TypeScript
The 'originalKeywordKind' DeprecationError fires when @typescript-eslint packages are stuck on v5 while TypeScript moved on. Upgrade to v6.x and restart ESLint to clear it.


