How to Set Up ESLint and Prettier in 2026 (The Right Way)
On this page
How to Set Up ESLint and Prettier in 2026 (The Right Way)
If you've ever spent an afternoon wrestling with conflicting linting rules, cryptic config files, or teammates pushing code with inconsistent formatting, you know the pain. The JavaScript tooling ecosystem has evolved dramatically, and in 2026, setting up ESLint and Prettier is cleaner, faster, and more straightforward than ever — if you know the right approach.
This guide walks you through a modern, no-nonsense setup that works for real-world projects. No legacy baggage, no unnecessary plugins, no cargo-culted configurations from 2019 blog posts.
Why You Still Need Both ESLint and Prettier
A common misconception is that ESLint and Prettier do the same thing. They don't.
ESLint is a static analysis tool. It catches bugs, enforces coding patterns, and flags potential issues like unused variables, unreachable code, or unsafe type coercion. It answers the question: Is this code correct and well-structured?
Prettier is an opinionated code formatter. It handles whitespace, line breaks, semicolons, quote styles, and indentation. It answers the question: Does this code look consistent?
You need both because formatting and linting are fundamentally different concerns. Prettier ensures every file looks the same regardless of who wrote it. ESLint ensures the code actually makes sense. Trying to use one without the other leaves a gap — either your code looks good but has bugs, or it's bug-free but visually inconsistent across your team.
Prerequisites
Before you start, make sure you have:
- Node.js 20 or later (LTS recommended)
- npm 10+, pnpm 9+, or yarn 4+
- A project initialized with
package.json
This guide uses npm in the examples, but all commands have direct equivalents in pnpm and yarn.
Step 1: Install ESLint with the Flat Config
ESLint has fully committed to the flat config system (eslint.config.js). The legacy .eslintrc.* format is no longer supported as of ESLint v9+. If you're starting a new project, this is great news — flat config is simpler, more explicit, and easier to reason about.
Install ESLint:
npm install --save-dev eslint @eslint/js
Create an eslint.config.js file in your project root:
import js from "@eslint/js";
export default [
js.configs.recommended,
{
rules: {
"no-unused-vars": "warn",
"no-console": "warn",
},
},
];
That's it. No .eslintrc.json, no .eslintignore (use the ignores property instead), no extends arrays with cryptic plugin resolution. The flat config is just an array of configuration objects, merged in order. What you see is what you get.
Adding TypeScript Support
If you're using TypeScript (and in 2026, most projects are), install typescript-eslint:
npm install --save-dev typescript-eslint
Update your config:
import js from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
"@typescript-eslint/no-unused-vars": "warn",
},
}
);
The tseslint.config() helper is a thin wrapper that provides type checking for your configuration. It returns the same flat array format.
Step 2: Install and Configure Prettier
Install Prettier:
npm install --save-dev prettier
Create a prettier.config.js file (or .prettierrc.json — both work fine):
export default {
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: "all",
printWidth: 80,
bracketSpacing: true,
};
A practical tip: don't overthink Prettier options. The whole point of Prettier is to stop debating style. Pick a config once, commit it, and move on. The defaults are sensible for most teams.
Create a .prettierignore file to skip files that shouldn't be formatted:
dist
build
coverage
node_modules
pnpm-lock.yaml
package-lock.json
Step 3: Make ESLint and Prettier Work Together
This is where most setups go wrong. The key principle is simple: ESLint should not enforce formatting rules. Formatting is Prettier's job. If both tools have opinions about formatting, they will conflict, and you will waste hours debugging why your CI pipeline fails on a semicolon.
The modern solution is eslint-config-prettier. This package disables all ESLint rules that would conflict with Prettier.
npm install --save-dev eslint-config-prettier
Add it as the last item in your ESLint config array:
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import prettierConfig from "eslint-config-prettier";
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommended,
prettierConfig,
{
rules: {
"@typescript-eslint/no-unused-vars": "warn",
},
}
);
Order matters here. eslint-config-prettier must come after other configs so it can override their formatting rules. Your own custom rules object can come after it since you shouldn't be adding formatting rules there anyway.
A note on eslint-plugin-prettier: You may see older guides recommending this plugin, which runs Prettier as an ESLint rule. Avoid this approach in 2026. It's slower (Prettier runs on every lint pass), produces noisier output, and conflates two separate concerns. Run Prettier and ESLint as separate tools.
Step 4: Add Scripts to package.json
Define clear, separate scripts for linting and formatting:
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}
Use lint and format:check in CI to verify code quality without modifying files. Use lint:fix and format locally to auto-fix issues before committing.
Step 5: Set Up Editor Integration
Tooling only works if it runs automatically. Configure your editor to format on save and show lint errors inline.
VS Code
Install the ESLint and Prettier extensions, then add to your .vscode/settings.json:
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
This setup formats the file with Prettier on every save and applies ESLint auto-fixes at the same time. Commit this file to your repo so every team member gets the same behavior.
Other Editors
Most modern editors (Zed, Neovim, WebStorm, Helix) have built-in or plugin-based support for ESLint and Prettier. The same principle applies everywhere: Prettier handles formatting, ESLint handles linting, and both run on save.
Step 6: Add Pre-Commit Hooks with lint-staged
Editor integration catches most issues, but it doesn't prevent someone from committing unformatted code via the command line or a different editor. Use lint-staged and husky to enforce standards at commit time.
npm install --save-dev husky lint-staged
npx husky init
Add to your package.json:
{
"lint-staged": {
"*.{js,ts,jsx,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,css,yaml,yml}": ["prettier --write"]
}
}
Update .husky/pre-commit:
npx lint-staged
Now every commit automatically lints and formats only the staged files. This is fast, unobtrusive, and catches issues before they reach your CI pipeline.
Step 7: Add CI Checks
Your CI pipeline should verify that code passes both checks. Add this to your GitHub Actions workflow (or equivalent):
- name: Lint
run: npm run lint
- name: Check formatting
run: npm run format:check
Use format:check (not format) in CI. You don't want your CI pipeline modifying code — you want it to fail loudly when someone bypasses the pre-commit hooks.
Common Pitfalls to Avoid
Don't use legacy ESLint config formats. If you see .eslintrc, .eslintrc.json, or .eslintrc.yml in guides, those are outdated. Flat config is the standard.
Don't add formatting rules to ESLint. Rules like indent, quotes, semi, comma-dangle — these belong to Prettier. If you add them to ESLint, they will conflict.
Don't skip eslint-config-prettier. Even if you think your ESLint config has no formatting rules, shared configs and plugins often sneak them in. eslint-config-prettier is cheap insurance.
Don't run Prettier through ESLint. Keep them as separate tools with separate commands. It's faster, cleaner, and easier to debug.
Don't ignore your editor setup. The best linting configuration in the world is useless if developers don't see feedback until CI fails ten minutes after pushing.
Practical Tips for Teams
- Commit your editor settings. A
.vscode/settings.jsonor.editorconfigensures consistency across the team without relying on each developer's personal setup. - Start strict, loosen later. It's easier to disable a rule that's too noisy than to enable one after thousands of violations already exist in the codebase.
- Use
--cachefor speed. Both ESLint (eslint --cache) and Prettier (prettier --cache) support caching to skip unchanged files. This makes a significant difference in large projects. - Pin your versions. Use exact versions in
devDependenciesor a lockfile to prevent surprise breakage when a minor update changes rule behavior.
FAQ
Can I use Biome instead of ESLint and Prettier?
Yes. Biome is a viable alternative that combines linting and formatting into a single, fast Rust-based tool. However, ESLint's ecosystem of plugins and community rules is still significantly larger. If you need framework-specific rules (React, Vue, Angular, Svelte), ESLint typically has better coverage. For greenfield projects that don't need specialized plugins, Biome is worth evaluating.
Do I need @eslint/compat for older plugins?
Some older ESLint plugins haven't migrated to flat config yet. The @eslint/compat package provides a fixupPluginRules() utility to bridge the gap. Use it as a temporary measure — most popular plugins have released flat config support by now.
Should I use eslint-plugin-prettier?
No. As discussed above, running Prettier as an ESLint rule is slower, noisier, and architecturally wrong. Run them as separate tools.
How do I handle files that shouldn't be linted or formatted?
Use the ignores property in your ESLint flat config and a .prettierignore file. Common entries include dist/, build/, coverage/, and generated files. Don't ignore node_modules — both tools skip it by default.
What about monorepos?
Place your eslint.config.js and prettier.config.js at the repository root. ESLint's flat config naturally cascades through subdirectories. If individual packages need different rules, use the files property in your config objects to scope rules to specific paths rather than creating separate config files per package.
Does this setup work with frameworks like Next.js or Nuxt?
Yes, but you'll want to add the framework's ESLint plugin. For example, Next.js provides @next/eslint-plugin-next, and Nuxt has @nuxt/eslint-config. Add these to your flat config array before eslint-config-prettier so their formatting rules get properly disabled.
How do I migrate from an existing .eslintrc setup?
ESLint provides a migration tool: npx @eslint/migrate-config .eslintrc.json. It generates a flat config equivalent. Review the output — automated migrations sometimes introduce redundancy — but it's a solid starting point.
Wrapping Up
The modern ESLint and Prettier setup is remarkably simple: flat config, separate tools, no formatting rules in ESLint, and automation at every step — editor, pre-commit, and CI. The tools have matured to the point where the initial setup takes minutes, not hours, and once configured, your team never has to think about code style again. That's the whole point.
The article is ready above (~1,800 words). I wasn't able to write it to a file due to permissions, but the full markdown content is displayed. You can copy it directly.