ESLint vs Prettier: How to Combine Them for Perfect Code Formatting
If you’ve written JavaScript for more than a week, you’ve likely had a disagreement with your code. Maybe it was a missing semicolon, a tab where a space should be, or a string that should’ve been single-quoted but was double-quoted. These arguments are a waste of time—but they’re also incredibly common. According to the 2023 State of JS survey, over 70% of JavaScript developers use ESLint, and Prettier adoption has grown to roughly 40% of the ecosystem.
The problem? Many developers treat them as interchangeable. They’re not. ESLint and Prettier solve different problems, and when you understand the difference—and how to make them work together—you can eliminate entire categories of code review comments and merge conflicts.
Here’s how to set them up correctly, avoid the infamous “conflict” issues, and get your codebase to a place where formatting is a non-issue.
The Core Difference: Linting vs. Formatting
Let’s start with the fundamental distinction.
ESLint is a linter. It analyzes your code for logic and style issues. It catches things like:
- Undefined variables
- Unused imports
console.logleft in production code- Potential bugs like
==instead of===
ESLint cares about quality and correctness. It asks: “Is this code written well?”
Prettier is a formatter. It doesn’t care about logic at all. It only cares about how the code looks. It enforces consistent rules like:
- Line width (default: 80 characters)
- Indentation style (spaces vs. tabs)
- Semicolon presence
- Quote style
- Trailing commas
Prettier asks: “Does this code look consistent?”
One way to think about it: ESLint checks your grammar, Prettier checks your handwriting.
Why You Need Both
If you only use ESLint, you’ll end up writing style rules in your ESLint config. That’s possible, but ESLint’s formatting rules are slow and limited compared to Prettier. Plus, you’ll spend hours debating whether to use indent: [2, 4] or indent: [2, 2].
If you only use Prettier, your code will look beautiful—but it might still be broken. Prettier won’t catch that you’re using a deprecated API or that you’ve shadowed a variable name.
The industry-standard approach is:
- ESLint for code quality (rules about bugs, best practices, and potential errors)
- Prettier for formatting (rules about spacing, quotes, and line breaks)
This separation is the key. When you mix them, you get conflicts.
The Classic Conflict: Why They Fight
Here’s a scenario that happens to nearly every developer at some point:
- You write code that violates Prettier’s formatting rules.
- You run ESLint with a rule like
quotes: ["error", "single"]. - ESLint flags your double quotes.
- You run Prettier, which changes the quotes to single.
- ESLint runs again and passes.
That works. But what if Prettier’s default settings conflict with your ESLint rules? For example, Prettier defaults to double quotes in some configurations, while ESLint might enforce single quotes. Or Prettier insists on semicolons, but your ESLint rule says semi: ["error", "never"].
This is the “war” you’ve heard about. It’s not that the tools hate each other—it’s that they’re both trying to control the same output, and their default opinions differ.
The Solution: Disable ESLint’s Formatting Rules
The modern, accepted solution is simple: turn off all formatting rules in ESLint and let Prettier handle them.
The eslint-config-prettier package does exactly this. It’s a shareable config that disables all ESLint rules that conflict with Prettier. Here’s how to set it up:
npm install --save-dev eslint-config-prettier
Then, in your .eslintrc.json (or eslint.config.js if you’re using the flat config):
{
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"prettier"
]
}
The "prettier" entry must be last. This ensures it overrides any conflicting rules from the other configs.
With this setup, ESLint stops complaining about formatting. It focuses on logic, bugs, and best practices. Prettier handles the visual consistency.
The Practical Workflow
Once you’ve configured both tools, the workflow becomes:
- Write code (however you want—messy is fine).
- Run Prettier to format it.
- Run ESLint to catch logic issues.
- Fix any ESLint errors (which are now only about code quality).
You can run them in sequence:
npx prettier --write .
npx eslint . --fix
Or, if you want to be more efficient, you can use lint-staged to run both only on files that are staged for commit. This keeps your pre-commit hooks fast.
Here’s a sample .pre-commit config using lint-staged and husky:
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"prettier --write",
"eslint --fix"
]
}
}
This runs Prettier first, then ESLint. The --fix flag on ESLint will auto-fix any remaining issues that Prettier didn’t handle.
Setting Up a New Project: A Complete Guide
If you’re starting from scratch, here’s the complete setup for a modern JavaScript project:
Step 1: Install Both Tools
npm install --save-dev eslint prettier eslint-config-prettier
Step 2: Create ESLint Config
For ESLint 9+ (flat config), create eslint.config.js:
import js from '@eslint/js';
import prettier from 'eslint-config-prettier';
export default [
js.configs.recommended,
prettier,
{
rules: {
// Your custom rules here
'no-console': 'warn',
'no-unused-vars': 'error'
}
}
];
For older versions, use .eslintrc.json:
{
"env": {
"browser": true,
"es2022": true
},
"extends": ["eslint:recommended", "prettier"],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
}
}
Step 3: Create Prettier Config
Create a .prettierrc file:
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80
}
These are sensible defaults. Adjust to your team’s taste—but once you set them, stop debating.
Step 4: Add a .prettierignore File
Just like .gitignore, you want to exclude certain files:
node_modules
dist
build
package-lock.json
Step 5: Add Scripts to package.json
{
"scripts": {
"format": "prettier --write .",
"lint": "eslint .",
"check": "prettier --check . && eslint ."
}
}
The check script is great for CI. It fails if any file isn’t formatted correctly or has lint errors.
Common Pitfalls and How to Avoid Them
Even with the right setup, developers run into issues. Here are the most common ones and how to fix them:
Pitfall 1: ESLint Rules That Override Prettier
If you have eslint-config-prettier in your extends, but you also manually define a formatting rule like "quotes": ["error", "single"], you’ll get conflicts again. The solution: never manually define formatting rules in ESLint. If you find yourself writing rules about indentation, quotes, or semicolons in ESLint, stop. Move those to Prettier.
Pitfall 2: Formatting That Changes Code Behavior
Prettier is generally safe, but there are edge cases. For example:
const a = (1 + 2) * 3;
Prettier might reformat this to:
const a = 1 + 2 * 3;
Which changes the result from 9 to 7. To avoid this, always run your tests after formatting. In practice, Prettier is very conservative about this, but it’s a good habit.
Pitfall 3: Prettier Slowing Down Your Editor
If you’re using VS Code, install the Prettier extension and set it as the default formatter:
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
}
For ESLint, use the ESLint extension. You can also enable “format on save” for ESLint’s --fix:
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
This way, both tools run automatically every time you save. You never have to think about formatting again.
The TypeScript Question
If you’re using TypeScript, the setup is similar but with a few extra packages:
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
Your ESLint flat config becomes:
import tseslint from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import prettier from 'eslint-config-prettier';
export default [
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser: tsParser
},
plugins: {
'@typescript-eslint': tseslint
},
rules: {
...tseslint.configs.recommended.rules,
...prettier.rules
}
}
];
Prettier handles TypeScript formatting natively, so no extra config is needed there.
The Bottom Line
The “ESLint vs Prettier” debate is a false dichotomy. They’re not competitors; they’re complementary tools that serve different purposes. ESLint ensures your code is correct; Prettier ensures it’s consistent.
The winning setup is straightforward:
- Use ESLint for code quality rules.
- Use Prettier for formatting rules.
- Use
eslint-config-prettierto disable any formatting rules in ESLint. - Run Prettier first, then ESLint, in your pre-commit hooks and CI pipeline.
Once you have this configured, you can stop arguing about semicolons and start arguing about what actually matters: architecture, performance, and user experience.
Your future self—and your code reviewers—will thank you.