Skip to content

ESLint vs Prettier: Why You Need Both — And Why Choosing One Is a Mistake

Updated October 16, 2026

Table of Contents

You need both ESLint and Prettier. They solve fundamentally different problems: ESLint finds bugs and enforces code quality rules, while Prettier formats your code for consistent visual presentation. Using only one leaves your codebase exposed to either silent logic errors or formatting chaos. The JavaScript community has spent nearly a decade debating this question, and the answer has been settled — not by opinion, but by architectural reality. These tools occupy separate layers of the code quality stack, and eliminating either one degrades the other’s effectiveness. This article dissects what each tool does under the hood, why they are complementary rather than competitive, how to integrate them without conflicts, and whether modern alternatives like Biome or dprint have made the traditional pairing obsolete.

What ESLint Actually Does: The Static Analysis Engine

ESLint is a configurable static analysis tool for JavaScript and TypeScript. It parses your source code into an Abstract Syntax Tree (AST), then walks that tree comparing node patterns against a set of rules you define. When a rule matches a problematic pattern, ESLint emits a warning or error.

The distinction matters. ESLint does not read your code the way a human does — it reads the structural representation of your code. This is why it can catch bugs that no amount of visual review would reveal:

  • Variables declared but never referenced
  • React hooks called conditionally inside a component
  • Equality comparisons using == instead of ===
  • Unreachable code after a return statement
  • Deprecated API usage flagged via custom rules

ESLint’s rule system is extensible. You can write custom rules, extend shared configurations like eslint-config-airbnb or @typescript-eslint/recommended, and layer severity levels (off, warn, error) per rule. The flat config format introduced in ESLint v8.21.0 and stabilized in v9 replaced the legacy .eslintrc system with a eslint.config.js file that uses ES modules and explicit imports.

Key capabilities of ESLint in modern projects:

  • Type-aware linting via @typescript-eslint/parser and type-checked rules that inspect inferred types
  • React-specific analysis via eslint-plugin-react-hooks and eslint-plugin-react-refresh
  • Accessibility auditing via eslint-plugin-jsx-a11y
  • Import resolution checking via eslint-plugin-import or the successor eslint-plugin-import-x
  • Automatic code fixes for many rules, though not all

ESLint is not a formatter. It can enforce some stylistic preferences — like requiring semicolons or banning trailing commas — but doing so through ESLint is architecturally wrong and historically caused significant friction with formatters. This is the root of the entire “ESLint vs Prettier” confusion.

What Prettier Actually Does: The Opinionated Formatter

Prettier is a code formatter. It takes your source code, parses it into an AST, then reprints that AST from scratch using a consistent set of formatting rules. Unlike ESLint, Prettier has almost no configuration options — you control print width, tab width, semicolons, quotes, trailing commas, and a handful of JSX-specific settings. That is the full extent of it.

This deliberate lack of configuration is the entire point. Prettier enforces a single canonical formatting for any given piece of code. If two developers write the same logic with different indentation, brace placement, or line wrapping, Prettier produces identical output for both. The result:

  • No more pull request diffs dominated by whitespace changes
  • No more time wasted debating whether function arguments should break onto new lines
  • No more merge conflicts caused by inconsistent trailing commas
  • Consistent formatting across every file in the repository regardless of who wrote it

Prettier supports JavaScript, TypeScript, JSX, CSS, SCSS, Less, HTML, JSON, YAML, Markdown, and several other languages. It integrates into editors, pre-commit hooks, and CI pipelines. When you save a file in VS Code with the Prettier extension installed and format-on-save enabled, the tool silently reprints your code in under 50 milliseconds for most files.

The critical architectural point: Prettier does not understand logic. It does not care whether your variable is unused, your effect dependency array is incomplete, or your function has a complexity score of 25. It cares only about whitespace, line length, and syntactic delimiters. This is why comparing it to ESLint is comparing a spell-checker to an editor — they operate at different layers.

Why the “vs” Framing Is Wrong

The phrase “ESLint vs Prettier” implies these tools compete for the same role. They do not. The confusion originated because ESLint included formatting rules in its early life, and many teams used ESLint as both a linter and a formatter. This created real conflicts:

  • ESLint’s indent rule would disagree with Prettier’s indentation logic
  • ESLint’s max-len rule would flag lines that Prettier had intentionally wrapped differently
  • Both tools would emit conflicting auto-fixes, creating infinite fix loops

The solution was never to choose one tool. The solution was to separate concerns: let Prettier own formatting and let ESLint own code quality. This is accomplished through eslint-config-prettier, a shared ESLint configuration that disables every ESLint rule known to conflict with Prettier’s output.

The mental model is a pipeline:

1. Prettier runs first — formatting the code to a canonical representation
2. ESLint runs second — analyzing the formatted code for quality issues
3. No overlap — ESLint never tries to enforce formatting because eslint-config-prettier has disabled those rules

This separation is not a workaround. It is the recommended integration path documented by both the ESLint and Prettier teams.

Setting Up ESLint and Prettier with Flat Config

The modern setup uses ESLint’s flat config system alongside Prettier’s dedicated config package. Here is the integration pattern for a typical React or TypeScript project.

First, install the dependencies:

npm install --save-dev eslint prettier eslint-config-prettier

If you are using TypeScript:

npm install --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser

Then create eslint.config.js:

import js from "@eslint/js";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import prettierConfig from "eslint-config-prettier";

export default [
  js.configs.recommended,
  {
    files: ["**/.{ts,tsx}"],
    languageOptions: {
      parser: tsParser,
    },
    plugins: {
      "@typescript-eslint": tsPlugin,
    },
    rules: {
      ...tsPlugin.configs.recommended.rules,
    },
  },
  prettierConfig,
];

The prettierConfig import at the end is the critical line. It disables all conflicting formatting rules as an override layer, ensuring ESLint defers to Prettier on all whitespace and stylistic decisions.

For Prettier, create .prettierrc:

{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 80,
  "tabWidth": 2
}

And .prettierignore for files that should not be formatted:

build
dist
node_modules
coverage

This setup is minimal, explicit, and produces zero conflicts.

Common Conflicts and How to Resolve Them

Even with eslint-config-prettier, teams encounter integration issues. The most frequent ones and their resolutions:

Conflict: ESLint’s no-unused-vars flags Prettier-formatted destructuring

When you import a type only for TypeScript’s benefit, the no-unused-vars rule complains. Resolution: use @typescript-eslint/no-unused-vars instead, which understands TypeScript’s type imports.

Conflict: Prettier reformats code in ways that trigger ESLint’s complexity or depth rules

Prettier may wrap deeply nested JSX or long function calls onto multiple lines, pushing indentation deeper. This can trigger ESLint rules like max-depth. Resolution: adjust the rule’s threshold or restructure the code. This is a code quality signal, not a tool conflict.

Conflict: Auto-fix loops between ESLint and Prettier

If both tools are configured to auto-fix on save in an editor, they can race against each other. Resolution: ensure Prettier runs as the final formatting pass. In VS Code, set "editor.defaultFormatter": "esbenp.prettier-vscode" and disable ESLint’s formatting rules entirely.

Conflict: ESLint plugin rules that enforce formatting

Some ESLint plugins (particularly older community plugins) include formatting rules that eslint-config-prettier does not know about. Resolution: manually disable those rules in your config, or extend eslint-config-prettier after the plugin’s shared config.

VS Code Integration

The optimal VS Code configuration for ESLint and Prettier coexistence requires three elements:

Extensions:

  • Prettier – Code Formatter (esbenp.prettier-vscode)
  • ESLint (dbaeumer.vscode-eslint)

Settings in .vscode/settings.json:

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ]
}

The ordering matters. VS Code applies format-on-save first (using Prettier), then applies ESLint code actions. Since ESLint has no formatting rules enabled (thanks to eslint-config-prettier), ESLint only performs code quality fixes. No race condition. No conflict.

If you prefer ESLint to handle formatting through its own stylistic rules instead of Prettier, you would reverse this setup — but that approach sacrifices Prettier’s opinionated consistency and reintroduces the formatting debates the tool was designed to eliminate.

CI Pipeline Integration

In continuous integration, both tools should run as separate, non-overlapping checks:

# In your CI script or package.json
"scripts": {
  "lint": "eslint . --max-warnings=0",
  "format:check": "prettier --check .",
  "format:fix": "prettier --write ."
}

In a GitHub Actions workflow:

name: Code Quality
on: <!--ls-md-69b6ddffa206c-SHORTCODE-0-->

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm run format:check
      - run: npm run lint

Notice that format:check runs before lint. This mirrors the intended execution order: Prettier confirms formatting, ESLint confirms code quality. If Prettier fails, the formatting is wrong. If ESLint fails, the logic or patterns are wrong. The failures are diagnostic and specific.

For pre-commit enforcement, use lint-staged with husky:

{
  "lint-staged": {
    ".{ts,tsx}": [
      "prettier --write",
      "eslint --fix"
    ]
  }
}

This ensures that committed code is both formatted and linted without requiring developers to run the commands manually.

Biome: The Unified Alternative

Biome (formerly Rome) is a Rust-based toolchain that combines linting and formatting in a single binary. It is fast — significantly faster than running ESLint and Prettier separately, because it parses the AST once and applies both lint rules and formatting passes over the same tree.

Biome’s advantages:

  • Speed — 10 to 25 times faster than ESLint plus Prettier on large codebases
  • Single configuration — one biome.json file governs both linting and formatting
  • Built-in rules — includes most common ESLint rules plus formatting defaults modeled on Prettier
  • Zero plugin overhead — no dependency tree of ESLint plugins to maintain

Biome’s limitations:

  • Smaller rule ecosystem — lacks the vast library of community ESLint plugins (React Hooks exhaustive-deps, import resolution, accessibility)
  • No type-aware linting — Biome does not perform type-checked rules like @typescript-eslint can
  • Immature React support — React-specific rules are less comprehensive than the ESLint equivalents
  • Breaking changes — Biome is pre-1.0 stable and has introduced breaking configuration changes between minor versions

For greenfield projects that do not depend heavily on specialized ESLint plugins, Biome is a legitimate replacement for the ESLint-plus-Prettier pairing. For mature React or TypeScript codebases with custom ESLint configurations, migration requires careful auditing of which rules have Biome equivalents.

dprint: The Pluggable Formatter

dprint is a Rust-based code formatter with a plugin architecture. Unlike Prettier’s monolithic design, dprint uses language-specific plugins for TypeScript, Markdown, JSON, and others. It supports Prettier-compatible configuration options and can produce output nearly identical to Prettier’s.

dprint’s advantages:

  • Plugin architecture — add only the language support you need
  • Speed — comparable to Biome, substantially faster than Prettier
  • Configuration flexibility — more options than Prettier while maintaining opinionated defaults

dprint’s limitations:

  • No linting — it is purely a formatter, so you still need ESLint or Biome for code quality
  • Smaller community — fewer editor integrations and fewer teams using it in production
  • Prettier compatibility — formatting output differs in edge cases, creating migration friction

dprint is the right choice when you want a faster Prettier replacement but intend to keep ESLint for linting. It is not a unified tool like Biome.

The Practical Verdict

The ESLint-plus-Prettier combination remains the most widely adopted, best-documented, and most plugin-rich setup for JavaScript and TypeScript projects. It is not the fastest option, and the two-tool configuration has a learning curve. But it offers unmatched depth in code quality analysis through ESLint’s plugin ecosystem and unmatched formatting consistency through Prettier’s opinionated engine.

For teams considering alternatives, the decision framework is straightforward:

  • Need the richest lint rules and maximum compatibility? Use ESLint plus Prettier with eslint-config-prettier.
  • Building a new project and willing to trade plugin depth for speed? Evaluate Biome.
  • Want Prettier-level formatting but faster? Use dprint and keep ESLint.
  • Working in a monorepo with mixed languages? Prettier’s broad language support or dprint’s plugin architecture may be preferable.

The one configuration to avoid is running both ESLint formatting rules and Prettier simultaneously without eslint-config-prettier. That combination produces conflicts, redundant auto-fixes, and team frustration. Separate the concerns, use the bridge package, and the integration becomes invisible.

Recommendation

Use ESLint for code quality and Prettier for formatting. Configure eslint-config-prettier as the final layer in your ESLint flat config. Set up VS Code with Prettier as the default formatter and ESLint as the code action provider. Add both tools to your CI pipeline as separate checks. This setup is battle-tested, well-documented, and scales from solo projects to enterprise monorepos.

If raw performance becomes a bottleneck — which it typically does not until you reach thousands of files — evaluate Biome as a unified replacement. Until then, the two-tool approach gives you the deepest analysis and the most consistent output with the least configuration complexity.

FAQ

Do you need both ESLint and Prettier in 2026?

Yes. ESLint handles code quality — detecting bugs, enforcing best practices, and checking React or TypeScript-specific patterns. Prettier handles formatting — consistent indentation, line wrapping, and bracket placement. They operate on different concerns and do not overlap when properly configured with eslint-config-prettier. Using only one tool leaves a gap in your code quality pipeline.

Can ESLint replace Prettier?

Technically, ESLint has formatting rules that can replicate some of Prettier’s behavior. Practically, this is a poor approach. ESLint’s formatting rules are less opinionated, produce less consistent output across team members, and lack Prettier’s support for non-JavaScript languages like CSS, HTML, and Markdown. The ESLint team itself recommends delegating formatting to a dedicated tool.

What does eslint-config-prettier do?

eslint-config-prettier is an ESLint shared configuration that disables all ESLint rules known to conflict with Prettier’s formatting output. When you extend this config in your ESLint configuration, ESLint stops enforcing stylistic rules like indentation, quotes, semicolons, and line length — deferring those decisions entirely to Prettier. This eliminates all conflicts between the two tools.

Is Biome better than ESLint plus Prettier?

Biome is faster and simpler, but it is not universally better. It lacks the extensive plugin ecosystem of ESLint, does not support type-aware linting, and has fewer React-specific rules. For small to medium projects without complex linting requirements, Biome is a strong choice. For large codebases with custom ESLint configurations or heavy TypeScript usage, the ESLint-plus-Prettier combination remains more capable.

How do you set up ESLint and Prettier together in a React project?

Install eslint, prettier, eslint-config-prettier, and your preferred ESLint plugins. Create an eslint.config.js using the flat config format, extending the recommended rules and importing eslint-config-prettier as the final configuration object. Create a .prettierrc with your formatting preferences. In VS Code, set Prettier as the default formatter and ESLint as the code action source. Both tools will operate without conflicts on every save.

Should ESLint or Prettier run first?

Prettier should run first — formatting the code into a canonical shape. ESLint should run second — analyzing the formatted code for quality issues. In CI pipelines, check formatting before linting. In editors, apply format-on-save before ESLint code actions. This ordering ensures that ESLint analyzes the final formatted output rather than fighting Prettier over whitespace decisions.