ESLint + Prettier Integration
Make ESLint and Prettier work together without conflicts by separating concerns: ESLint for code quality, Prettier for formatting.
Recipe
Quick-reference recipe card — copy-paste ready.
# Install the compatibility package
npm install --save-dev eslint-config-prettier
# That's it — no eslint-plugin-prettier needed// package.json scripts (run them separately)
{
"scripts": {
"lint": "next lint",
"lint:fix": "next lint --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"check-all": "npm run format:check && npm run lint"
}
}When to reach for this: Any project using both ESLint and Prettier (which is most React/Next.js projects).
Working Example
// eslint.config.mjs
import { FlatCompat } from "@eslint/eslintrc";
import { dirname } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({ baseDirectory: __dirname });
const eslintConfig = [
...compat.extends(
"next/core-web-vitals",
"next/typescript",
"prettier", // Must be LAST — disables ESLint formatting rules
),
{
rules: {
// Code quality rules (not formatting) — these won't conflict
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_" },
],
"react-hooks/exhaustive-deps": "warn",
"import/order": [
"warn",
{
groups: ["builtin", "external", "internal"],
"newlines-between": "always",
},
],
},
},
];
export default eslintConfig;// .prettierrc
{
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 80,
"plugins": ["prettier-plugin-tailwindcss"]
}What this demonstrates:
eslint-config-prettier(the"prettier"extend) disables all ESLint rules that conflict with Prettier- ESLint handles code quality only (unused vars, hook deps, import order)
- Prettier handles formatting only (semicolons, quotes, indentation)
- Separate npm scripts for each tool
Deep Dive
How It Works
- ESLint has formatting rules (e.g.,
indent,semi,quotes) and code quality rules (e.g.,no-unused-vars,react-hooks/exhaustive-deps) - Prettier also controls formatting, so the two tools fight over the same files
eslint-config-prettierturns off every ESLint rule that Prettier handles, eliminating conflicts- It must be the last item in
extendsso it overrides all previous configs
Variations
Why NOT to use eslint-plugin-prettier:
// ❌ This approach runs Prettier inside ESLint
// eslint-plugin-prettier — NOT recommended
{
plugins: ["prettier"],
rules: {
"prettier/prettier": "error",
},
}Problems with this approach:
- Every formatting issue shows as an ESLint error, producing noisy output
- Linting becomes significantly slower because Prettier runs inside ESLint
- Red squiggly lines on every formatting issue clutters the editor
- Two tools doing the same job in the same process
Recommended approach: run them separately:
{
"scripts": {
"lint": "next lint",
"lint:fix": "next lint --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"fix-all": "npm run lint:fix && npm run format"
}
}Pre-commit hook approach (best of both worlds):
// package.json
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,json,md}": ["prettier --write"]
}
}TypeScript Notes
// Both tools work on .ts and .tsx files seamlessly.
// ESLint catches type-aware issues:
const unused = "hello"; // @typescript-eslint/no-unused-vars ❌
// Prettier handles the formatting:
const obj = { a: 1, b: 2, c: 3 }; // Prettier controls spacing, trailing commas, etc.
// No overlap — each tool does its job.Gotchas
Things that will bite you. Each gotcha includes what goes wrong, why it happens, and the fix.
-
Config order matters — If
"prettier"is not the last item inextends, ESLint formatting rules from subsequent configs will re-enable and conflict. Fix: Always put"prettier"last in your extends array. -
Checking for conflicts — You are not sure if a rule conflicts. Fix: Run
npx eslint-config-prettier 'src/**/*.tsx'to list rules that conflict with Prettier. The CLI tool reports exactly which rules to turn off. -
Different formatting in CI vs local — If developers do not have format-on-save enabled, CI catches formatting errors that they never saw. Fix: Use Husky + lint-staged to format automatically before each commit.
-
Import order is not formatting —
import/orderis a code quality rule that Prettier does not handle (Prettier does not reorder imports). Fix: Keepimport/orderin ESLint; it does not conflict with Prettier. Alternatively, useprettier-plugin-organize-imports.
Alternatives
Other ways to solve the same problem — and when each is the better choice.
| Alternative | Use When | Don't Use When |
|---|---|---|
| Biome | You want linting + formatting in one tool with zero conflicts | You need the ESLint plugin ecosystem |
| Prettier only (no ESLint) | Very small project with no code quality concerns | You want to catch bugs, unused vars, or hook violations |
| ESLint only (with formatting rules) | You refuse to add another tool | You want consistent, opinionated formatting |
FAQs
What does eslint-config-prettier actually do?
- It turns off every ESLint rule that conflicts with Prettier.
- It does not add any rules of its own.
- It must be the last item in
extendsso it overrides all previous configs.
Why is eslint-plugin-prettier not recommended?
- It runs Prettier inside ESLint, making linting significantly slower.
- Every formatting issue appears as an ESLint error, producing noisy output.
- Red squiggly lines on every formatting difference clutters the editor.
- Running the tools separately is cleaner and faster.
Gotcha: What happens if "prettier" is not the last item in extends?
- ESLint formatting rules from configs listed after
"prettier"will re-enable. - This causes conflicts where ESLint and Prettier fight over the same formatting.
- Always put
"prettier"last in your extends array.
How do I check which ESLint rules conflict with Prettier?
npx eslint-config-prettier 'src/**/*.tsx'This CLI tool reports exactly which active rules conflict with Prettier.
Does import/order conflict with Prettier?
- No. Prettier does not reorder imports.
import/orderis a code quality rule, not a formatting rule.- Keep it in ESLint; it works alongside Prettier without conflicts.
What is the recommended way to run both tools?
{
"scripts": {
"lint": "next lint",
"lint:fix": "next lint --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"fix-all": "npm run lint:fix && npm run format"
}
}Run them as separate commands, not as a single combined tool.
How do I set up pre-commit hooks to run both ESLint and Prettier?
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,json,md}": ["prettier --write"]
}
}ESLint fixes code quality issues first, then Prettier formats.
Gotcha: Why do I see different formatting in CI versus locally?
- Developers without format-on-save enabled see formatting errors only in CI.
- Use Husky + lint-staged to format automatically before each commit.
- This ensures committed code is always formatted, regardless of editor settings.
Does eslint-config-prettier affect TypeScript-specific rules?
- It only disables formatting-related rules (indentation, spacing, quotes).
- TypeScript code quality rules like
@typescript-eslint/no-unused-varsare not affected. - Type-aware rules remain fully active.
Can I use Prettier for formatting and ESLint for TypeScript type-aware linting?
- Yes. This is the recommended setup.
- ESLint handles code quality (unused vars, hook deps, type-aware checks).
- Prettier handles formatting (semicolons, quotes, indentation).
eslint-config-prettierensures no overlap between the two.
What does the check-all script do?
npm run format:check && npm run lintformat:checkverifies files are formatted without modifying them.lintruns ESLint for code quality.- Use this in CI to validate without auto-fixing.
Related
- ESLint Setup for Next.js — base ESLint configuration
- Prettier Setup — Prettier configuration and options
- Husky & lint-staged — auto-format on commit
- Linting in CI/CD — enforce both in CI