How to Set Up ESLint and Prettier Together in VS Code Without Conflicts

Almost every JavaScript or TypeScript project ends up with the same two tools: ESLint for code quality and Prettier for formatting. And almost every project ends up with the same headache: you save a file, Prettier reformats it, then ESLint immediately underlines the result in red. Or worse, saving twice produces two different results. Using Prettier and ESLint for JavaScript formatting tackles the same question from another angle.

Most tutorials stop at “install both extensions”. This guide goes further: it shows you the exact eslint prettier vscode setup that removes the conflict at the source, with the real config files (flat config and legacy .eslintrc), the exact settings.json, and a troubleshooting table for when things still misbehave.

TL;DR: the 60-second version

  1. Install eslint, prettier and eslint-config-prettier as dev dependencies.
  2. Add eslint-config-prettier as the last item in your ESLint config so it turns off every formatting rule ESLint owns.
  3. Install the two official VS Code extensions: dbaeumer.vscode-eslint and esbenp.prettier-vscode.
  4. Set Prettier as the default formatter, enable editor.formatOnSave, and add "source.fixAll.eslint": "explicit" to editor.codeActionsOnSave.
  5. Commit .vscode/settings.json so the whole team gets identical behaviour.

Now here is the same thing, explained properly.

code editor screen

Why ESLint and Prettier fight in the first place

The two tools overlap on a narrow but very visible surface: stylistic rules.

Tool Job Typical output
ESLint Finds bugs and enforces code quality rules (unused vars, unsafe types, bad hooks usage, forbidden imports) “‘user’ is assigned a value but never used”
Prettier Reprints your code with a consistent style, no opinions about logic Line width, quotes, semicolons, indentation, trailing commas

The conflict appears when ESLint also carries formatting rules such as quotes, semi, indent, comma-dangle or max-len. Prettier writes the file one way, ESLint demands another, and your editor becomes a tug of war.

The fix is not clever settings. The fix is deleting the overlap. That is exactly what eslint-config-prettier does: it is a config that switches off every ESLint rule that could ever disagree with Prettier. After that, ESLint handles logic, Prettier handles looks, and neither has an opinion on the other’s territory.

Ignore the outdated advice you will find elsewhere

  • prettier.eslintIntegration in settings.json: removed years ago, it does nothing today.
  • prettier-eslint based extensions: still exist, but they add a slow extra layer that most projects do not need.
  • Third-party formatters like “Format Code Action”: unnecessary since VS Code got native codeActionsOnSave support.

Step 1: Install the packages locally

Always install locally in the project, never globally. The VS Code extensions read the versions from your node_modules, which is what keeps your machine and your CI identical.

JavaScript project

npm install --save-dev eslint @eslint/js prettier eslint-config-prettier

TypeScript project

npm install --save-dev eslint @eslint/js typescript typescript-eslint prettier eslint-config-prettier

Prefer a guided start? npm init @eslint/config@latest scaffolds an ESLint config for you, then you add Prettier on top with the steps below.

code editor screen

Step 2: Create the Prettier config

Create .prettierrc.json at the root of the project. Even if you keep the defaults, having the file matters: it is the signal that tells the VS Code extension (and your teammates) that this project is Prettier-managed.

{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 100,
  "tabWidth": 2,
  "arrowParens": "always",
  "endOfLine": "lf"
}

Then add .prettierignore:

node_modules
dist
build
coverage
.next
pnpm-lock.yaml
package-lock.json

Tip: "endOfLine": "lf" saves mixed Windows/macOS teams from a whole category of phantom diffs.

Step 3: Wire ESLint to Prettier (the part everyone skips)

This is the actual conflict fix. The golden rule: eslint-config-prettier must always be last, so it overrides any formatting rule enabled before it.

Modern flat config: eslint.config.js (ESLint 9 and later)

Flat config is the default in current ESLint versions. Here is a complete TypeScript-ready file:

// eslint.config.js
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import prettier from 'eslint-config-prettier/flat';

export default tseslint.config(
  {
    ignores: ['dist/**', 'build/**', 'coverage/**', 'node_modules/**'],
  },
  js.configs.recommended,
  ...tseslint.configs.recommended,
  {
    rules: {
      'no-unused-vars': 'off',
      '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
      'no-console': ['warn', { allow: ['warn', 'error'] }],
      eqeqeq: ['error', 'smart'],
    },
  },
  // MUST stay last: disables every ESLint rule that conflicts with Prettier
  prettier,
);

For a plain JavaScript project, drop the typescript-eslint lines:

// eslint.config.js
import js from '@eslint/js';
import prettier from 'eslint-config-prettier/flat';

export default [
  { ignores: ['dist/**', 'node_modules/**'] },
  js.configs.recommended,
  prettier,
];

Note: if you are on an older eslint-config-prettier release that does not expose the /flat entry point, import it as import prettier from 'eslint-config-prettier'; and keep it in the same last position.

Legacy config: .eslintrc.json (older projects)

Plenty of production codebases still run the legacy format. Same principle, "prettier" last in extends:

{
  "root": true,
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier"
  ],
  "env": { "browser": true, "node": true, "es2022": true },
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "rules": {
    "no-unused-vars": "off",
    "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }]
  }
}

What about eslint-plugin-prettier?

There is a second approach: run Prettier through ESLint with eslint-plugin-prettier, so formatting issues appear as ESLint errors. It works, but it has costs.

Approach Pros Cons Best for
Separate tools
(eslint-config-prettier only)
Fastest, clean separation, fewer moving parts, easiest to debug Two commands in CI instead of one Recommended for 95% of projects
Prettier as an ESLint rule
(eslint-plugin-prettier)
One command, formatting errors visible in the lint report Slower on large repos, noisier red squiggles, harder to isolate failures Teams that want a single gate in CI

This guide sticks with the separate-tools approach, which is what the Prettier documentation itself recommends.

code editor screen

Step 4: Install the right VS Code extensions

Only two are needed. Install them from the Extensions panel or from the command line:

code --install-extension dbaeumer.vscode-eslint
code --install-extension esbenp.prettier-vscode
  • ESLint (dbaeumer.vscode-eslint): shows lint problems inline and applies auto-fixes on save.
  • Prettier – Code formatter (esbenp.prettier-vscode): the formatter itself.

Then help your team by committing .vscode/extensions.json:

{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode"
  ],
  "unwantedRecommendations": [
    "hookyqr.beautify",
    "rvest.vs-code-prettier-eslint"
  ]
}

Step 5: The exact settings.json

Create .vscode/settings.json in the repository (not only in your user settings) so every contributor gets the same behaviour. This is the file that makes format-on-save and lint-fix-on-save cooperate:

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "editor.formatOnSaveMode": "file",

  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },

  "eslint.useFlatConfig": true,
  "eslint.run": "onType",
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ],

  "prettier.requireConfig": true,

  "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "[javascriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "[css]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "[scss]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
  "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }
}

What each key actually does

Setting Effect
editor.defaultFormatter Makes Prettier the single formatter, so VS Code never asks “which formatter?”
editor.formatOnSave Runs Prettier every time you hit save
source.fixAll.eslint Applies ESLint auto-fixes (unused imports, prefer-const, hooks fixes) on save
"explicit" instead of true Current syntax: fixes run on manual saves only, not on auto-save. Boolean values are deprecated
eslint.useFlatConfig Tells the extension you use eslint.config.js. Set to false for legacy .eslintrc
prettier.requireConfig Prettier only formats projects that actually contain a Prettier config file. Prevents mangling unrelated repos

Order of operations: VS Code runs the code actions first, then the formatter. So on each save ESLint applies its fixes, and Prettier gets the final word on layout. That is precisely the order you want, and it is why the file never “bounces” between two styles. A step-by-step version exists if you prefer that format.

code editor screen

Step 6: Add npm scripts and verify

Your editor is only half of the story. Add scripts so CI enforces the same rules:

{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "verify": "npm run lint && npm run format:check"
  }
}

Now run the sanity check:

  1. Run npx prettier --check .. Everything should pass after one npx prettier --write ..
  2. Run npx eslint .. You should see zero formatting complaints, only real code quality issues.
  3. Open a messy file in VS Code, hit Ctrl+S (or Cmd+S), and check it stabilises after a single save.
  4. Open the Output panel and select ESLint, then Prettier, to confirm neither is reporting a config error.

If you still suspect an overlapping rule, temporarily comment out the Prettier config line in your ESLint config and compare the lint output. Any rule that disappears was a conflicting formatting rule, which proves the wiring works.

Lock it in with a pre-commit hook

Editors drift. Git hooks do not:

npm install --save-dev husky lint-staged
npx husky init
// package.json
{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.{json,css,scss,md,yml,yaml}": ["prettier --write"]
  }
}

Put npx lint-staged inside .husky/pre-commit and unformatted code stops reaching your main branch.

Troubleshooting: when it still does not work

Symptom Likely cause Fix
File keeps changing on every save A formatting rule is still active in ESLint Move eslint-config-prettier to the very end of the config array or extends
Nothing formats at all No default formatter or another extension took over Right-click the file, Format Document With…, then Configure Default Formatter and pick Prettier
ESLint extension shows no problems Wrong config mode or wrong working directory Check eslint.useFlatConfig, and set eslint.workingDirectories in a monorepo
Fixes only apply on a second save Auto-save is on with "explicit" Use manual saves, or switch to "always" if you accept fixes on auto-save
Works locally, fails in CI Globally installed binaries or mismatched versions Install everything as dev dependencies and commit the lockfile
Endless line-ending diffs CRLF vs LF across operating systems Set "endOfLine": "lf" and add * text=auto eol=lf to .gitattributes
Prettier ignores the project config Config file outside the opened workspace folder Open the folder containing package.json, or set prettier.configPath

Monorepo note

In a workspace with several packages, point the ESLint extension at each package root so it resolves the right config and dependencies:

{
  "eslint.workingDirectories": [{ "mode": "auto" }]
}
code editor screen

Framework specifics worth knowing

  • React: add eslint-plugin-react-hooks and eslint-plugin-jsx-a11y. They are logic rules, so they never clash with Prettier.
  • Next.js: eslint-config-next already handles the framework rules. Just keep eslint-config-prettier after it.
  • Vue and Svelte: install the matching Prettier plugin and add the language to eslint.validate.
  • Tailwind CSS: prettier-plugin-tailwindcss sorts your class names automatically and belongs to Prettier, not ESLint.
  • Import order: use a Prettier plugin, not an ESLint stylistic rule, to avoid reopening the conflict you just closed.

Frequently asked questions

How do I configure ESLint with Prettier?

Install eslint-config-prettier, then add it as the last entry of your ESLint config (last item of the exported array in eslint.config.js, or last string in extends in .eslintrc). It disables every ESLint rule that overlaps with Prettier, so ESLint stops policing formatting.

Is ESLint obsolete?

No. ESLint is still the standard linter for JavaScript and TypeScript and remains actively developed. What changed is its scope: stylistic rules were deprecated and moved out of the core, so ESLint now focuses on code quality and bug detection while Prettier handles formatting. Faster alternatives such as Biome and Oxlint exist, but ESLint keeps the largest plugin ecosystem by a wide margin.

What is the difference between Prettier and ESLint?

Prettier is a formatter: it reprints your code with consistent spacing, quotes and line breaks, and has no opinion on logic. ESLint is a linter: it analyses your code for problems such as unused variables, unreachable code, unsafe types or misused React hooks. They are complementary tools, not competitors.

How do I run Prettier in VS Code?

Install the Prettier – Code formatter extension, then either press Shift+Alt+F (Shift+Option+F on macOS), or open the Command Palette and run Format Document. Set "editor.formatOnSave": true to run it automatically on every save. This write-up is worth a look.

Do I need eslint-plugin-prettier as well?

Not for a standard setup. eslint-config-prettier alone resolves the conflict. Add eslint-plugin-prettier only if you specifically want formatting problems reported as ESLint errors in a single CI command, and accept the performance cost.

Should .vscode/settings.json be committed to Git?

Yes for project-level settings such as the default formatter, format-on-save and ESLint options. Keep personal preferences (theme, font size, keybindings) in your user settings so you do not impose them on the team.

Can I use format-on-save without ESLint auto-fix?

Absolutely. Remove the editor.codeActionsOnSave block and Prettier still formats on save, while ESLint keeps reporting problems inline without touching your file. Many teams prefer this to keep saves fully predictable.

Final checklist

  • ✓ eslint, prettier and eslint-config-prettier installed as dev dependencies
  • ✓ eslint-config-prettier is the last item in the ESLint config
  • ✓ .prettierrc.json and .prettierignore exist at the project root
  • ✓ Only the two official extensions are installed and enabled
  • ✓ .vscode/settings.json and .vscode/extensions.json are committed
  • ✓ npm run lint and npm run format:check both pass in CI
  • ✓ A pre-commit hook enforces the same rules for everyone

Get these seven right and the ESLint versus Prettier war is over for good: ESLint catches your bugs, Prettier keeps your diffs clean, and your save key does exactly one predictable thing.

Need help standardising tooling across a whole engineering team or a large legacy codebase? The team at GeminiWeb builds and audits front-end setups like this every week.

Search

Recent Blog

  • All Post
  • Email Marketing
  • Responsive Website
  • SEO
  • Social Media Marketing
  • Web Design
  • Web Development

Subscribe

You have been successfully Subscribed! Ops! Something went wrong, please try again.

Company Name

Gemini Web

Company Address

3444 Hall Valley Drive, Davy, WV 24828 USA

Company Email

[email protected]

Copyright © 2022 Gemini Web. All Rights Reserved.