ESLint + Prettier Integration
Make ESLint and Prettier work together without conflicts by separating concerns: ESLint for code quality, Prettier for formatting.
Search across all documentation pages
Make ESLint and Prettier work together without conflicts by separating concerns: ESLint for code quality, Prettier for formatting.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
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).
// 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 Prettierindent, semi, quotes) and code quality rules (e.g., no-unused-vars, react-hooks/exhaustive-deps)eslint-config-prettier turns off every ESLint rule that Prettier handles, eliminating conflictsextends so it overrides all previous configsWhy 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:
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"]
}
}// 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.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 in extends, 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/order is a code quality rule that Prettier does not handle (Prettier does not reorder imports). Fix: Keep import/order in ESLint; it does not conflict with Prettier. Alternatively, use prettier-plugin-organize-imports.
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 |
extends so it overrides all previous configs."prettier" will re-enable."prettier" last in your extends array.npx eslint-config-prettier 'src/**/*.tsx'This CLI tool reports exactly which active rules conflict with Prettier.
import/order is a code quality rule, not a formatting rule.{
"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.
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,json,md}": ["prettier --write"]
}
}ESLint fixes code quality issues first, then Prettier formats.
@typescript-eslint/no-unused-vars are not affected.eslint-config-prettier ensures no overlap between the two.npm run format:check && npm run lintformat:check verifies files are formatted without modifying them.lint runs ESLint for code quality.Reviewed by Chris St. John·Last updated Jul 19, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥