If you have ever saved a JavaScript file and watched VS Code add semicolons, then remove them, then underline your quotes in red, you already know the problem: ESLint and Prettier are stepping on each other. This guide walks through a clean ESLint Prettier VS Code setup from an empty project to format-on-save that just works, including the exact error messages you are likely to hit and the full config files you can copy. There’s a good explainer over at visualstudio.com.
Everything below targets ESLint 9 and newer (flat config), Prettier 3, and eslint-config-prettier 10+, which is the standard stack in 2026.
What you will have at the end
- ESLint catching real bugs (unused variables, undefined globals, bad comparisons)
- Prettier owning all formatting decisions (quotes, semicolons, line width, indentation)
- Zero overlap between the two, thanks to
eslint-config-prettier - Format on save with Prettier, plus auto-fixable ESLint problems fixed on save
- Committed workspace settings so every teammate gets the same behaviour

Why ESLint and Prettier conflict in the first place
ESLint was originally both a linter and a code style enforcer. Prettier is only a formatter, and it reprints your file from the AST with no negotiation. When both are active, they can have opinions about the same character.
| ESLint rule | Prettier option | Typical symptom |
|---|---|---|
quotes |
singleQuote |
Red squiggles on every string after saving |
semi |
semi |
Semicolons added then removed on each save |
indent |
tabWidth / useTabs |
Endless indentation errors ESLint cannot fix |
max-len |
printWidth |
Lines flagged even after Prettier formatted them |
comma-dangle |
trailingComma |
Trailing commas ping-ponging in diffs |
The fix is not to disable one tool. It is a simple rule of thumb: Prettier decides how code looks, ESLint decides whether code is correct. eslint-config-prettier is the package that switches off every ESLint rule that touches looks.
Step 1: Check your prerequisites
- Node.js 20 or newer (Node 22 LTS or Node 24 LTS recommended). Verify with
node -v. - A project with a
package.json. If you do not have one, runnpm init -y. - VS Code opened at the project root, not at a parent folder. This single detail causes more “ESLint not working” tickets than anything else, because the extension resolves configs relative to the workspace folder.

Step 2: Install the packages locally
Install everything as dev dependencies in the project. Do not install ESLint or Prettier globally: the VS Code extensions look for a local install first, and global versions drift from what CI uses.
npm install --save-dev eslint @eslint/js globals prettier eslint-config-prettier
Yarn and pnpm equivalents:
pnpm add -D eslint @eslint/js globals prettier eslint-config-prettier
yarn add -D eslint @eslint/js globals prettier eslint-config-prettier
| Package | What it does |
|---|---|
eslint |
The linter engine and CLI |
@eslint/js |
Official recommended JavaScript rule sets for flat config |
globals |
Predefined global variables (browser, node, jest) so you stop seeing no-undef |
prettier |
The formatter itself |
eslint-config-prettier |
The peace treaty: disables all ESLint rules that conflict with Prettier |
You can also scaffold a base config interactively with npm init @eslint/config@latest, then paste the Prettier part from Step 5.
What about eslint-plugin-prettier?
eslint-plugin-prettier runs Prettier as an ESLint rule and reports formatting as lint errors. It is not needed for the setup in this article, it is slower, and it floods your Problems panel with formatting noise like Insert "·". Skip it unless you have a hard requirement that formatting must fail your lint job. We cover that case at the end.
Step 3: Create the ESLint flat config
ESLint 9 removed .eslintrc.json as the default. Create a file named eslint.config.mjs in the project root:
// eslint.config.mjs
import js from "@eslint/js";
import globals from "globals";
import prettier from "eslint-config-prettier/flat";
export default [
// 1. Files ESLint should never look at
{
ignores: ["node_modules/**", "dist/**", "build/**", "coverage/**"],
},
// 2. Official recommended rules
js.configs.recommended,
// 3. Your project rules and language settings
{
files: ["**/*.{js,mjs,cjs,jsx}"],
languageOptions: {
ecmaVersion: 2024,
sourceType: "module",
globals: {
...globals.browser,
...globals.node,
},
parserOptions: {
ecmaFeatures: { jsx: true },
},
},
rules: {
"no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
"no-console": "warn",
eqeqeq: ["error", "always"],
"prefer-const": "error",
},
},
// 4. MUST STAY LAST: disables every rule that fights Prettier
prettier,
];
Important version note: the eslint-config-prettier/flat entry point exists in version 10 and later. On version 9 or older, import it as import prettier from "eslint-config-prettier"; instead. Check with npm list eslint-config-prettier.
Step 4: Create the Prettier config
Prettier only auto-activates in VS Code when it finds a config file (if you enable prettier.requireConfig, which we do in Step 7). Add .prettierrc.json at the root:
{
"semi": true,
"singleQuote": true,
"jsxSingleQuote": false,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf"
}
Then .prettierignore, so Prettier does not rewrite generated files:
node_modules
dist
build
coverage
.next
package-lock.json
pnpm-lock.yaml
yarn.lock
*.min.js
*.min.css
Optional but recommended, an .editorconfig so non VS Code editors behave the same. Prettier reads it as a fallback:
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

Step 5: Verify that the conflicts are actually gone
Do not trust the setup blindly. eslint-config-prettier ships a CLI helper that inspects your resolved config and reports any remaining rule that can conflict with Prettier:
npx eslint-config-prettier
On older versions the helper takes a file path, for example npx eslint-config-prettier src/index.js. A clean run prints that no conflicting rules were found. If it lists something like quotes or indent, you have one of these three problems:
- The Prettier config object is not last in the exported array. Later entries win in flat config, so order is everything.
- You re-enabled a stylistic rule after it in your own
rulesblock. - You are extending a shared config (Airbnb, a company preset) that is loaded after it.
Then run the two tools manually once:
npx eslint .
npx prettier . --check
Add these scripts to package.json so CI and your teammates use the exact same commands:
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier . --write",
"format:check": "prettier . --check"
}
}
Step 6: Install the two VS Code extensions (and only those)
- ESLint by Microsoft, extension ID
dbaeumer.vscode-eslint - Prettier – Code formatter by Prettier, extension ID
esbenp.prettier-vscode
Uninstall or disable any other formatter for JavaScript (Beautify, JS-CSS-HTML Formatter, older “Prettier ESLint” hybrids). Two formatters competing for the same file is the second most common cause of the save loop.
Commit an .vscode/extensions.json so new contributors get prompted to install the right pair:
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
],
"unwantedRecommendations": [
"hookyqr.beautify"
]
}

Step 7: The workspace settings file that ties it together
Put this in .vscode/settings.json and commit it. Workspace settings beat personal user settings, which is exactly what you want for a shared repository.
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSaveMode": "file",
"editor.formatOnPaste": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[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" },
"prettier.requireConfig": true,
"eslint.validate": ["javascript", "javascriptreact"],
"eslint.run": "onType",
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true
}
What each key is doing
| Setting | Why it is there |
|---|---|
editor.formatOnSave |
Runs Prettier on every save |
editor.defaultFormatter |
Removes the “multiple formatters” prompt by naming a winner |
source.fixAll.eslint: "explicit" |
Applies ESLint auto-fixes on save. Use the string "explicit"; the old boolean true is deprecated |
prettier.requireConfig |
Prettier stays silent in projects that have no Prettier config, so it never reformats unrelated repos |
files.eol |
Forces LF, which kills the Delete ␍ class of errors on Windows |
eslint.run |
onType for instant feedback, switch to onSave on very large files |
Step 8: Test it in 30 seconds
Create test.js with deliberately ugly code:
const name = "pixelfabs"
let unused = 42
if (name == 'pixelfabs') { console.log( "hello" ) }
Save the file. Expected result:
- Prettier reformats spacing, converts the double quotes to single quotes and adds semicolons
- ESLint reports
unusedas a warning and==as an error fromeqeqeq console.logis flagged as a warning- Saving a second time changes nothing. If the file keeps changing on every save, your configs still conflict
Troubleshooting: the exact errors developers hit
| Message or symptom | Cause and fix |
|---|---|
ESLint couldn't find the config "prettier" to extend from |
The package is missing or installed in the wrong folder. Run npm i -D eslint-config-prettier in the same directory as the config file. |
Cannot find package 'eslint-config-prettier/flat' |
You are on version 9 or older. Upgrade, or import eslint-config-prettier without the /flat suffix. |
Delete ␍ eslint(prettier/prettier) |
CRLF line endings. Set "endOfLine": "lf" in Prettier, files.eol in VS Code, and * text=auto eol=lf in .gitattributes. |
| “There are multiple formatters for JavaScript files” | Pick one via the language specific editor.defaultFormatter blocks shown above. |
| “ESLint is disabled since its execution has not been approved” | Open the Command Palette, run ESLint: Manage Library Execution and allow it for this workspace. |
| Format on save does nothing | Check Output > Prettier. Usual causes: file listed in .prettierignore, no Prettier config while requireConfig is on, a user setting overriding formatOnSave, or the file was saved with “Save without Formatting”. |
'process' is not defined (no-undef) |
Missing environment globals. Add ...globals.node (or globals.browser) to languageOptions.globals. |
Parsing error: Unexpected token on JSX or import |
Set sourceType: "module", a modern ecmaVersion, and ecmaFeatures: { jsx: true }. |
Invalid option '--ext' |
Flat config dropped --ext. Use the files array inside eslint.config.mjs and run plain eslint .. |
| Code flips back and forth on each save | A stylistic ESLint rule survived. Move the Prettier entry to the very end of the array and re-run the conflict checker. |
| ESLint works in the terminal but not in the editor | The workspace root does not match the package root. Open the folder containing package.json, or set eslint.workingDirectories in a monorepo. |

Extras worth adding for a team
Enforce it before commits
Editor settings are advisory. A pre-commit hook is not. With husky and lint-staged:
npm install --save-dev husky lint-staged
npx husky init
Then in package.json:
{
"lint-staged": {
"*.{js,mjs,cjs,jsx}": ["eslint --fix", "prettier --write"],
"*.{json,css,scss,md,html}": ["prettier --write"]
}
}
And put npx lint-staged inside .husky/pre-commit.
If you really want formatting to fail lint
Only then install eslint-plugin-prettier and add its recommended config last, instead of eslint-config-prettier alone (the plugin’s recommended config already includes the conflict-disabling rules). Expect slower lint runs and formatting reported as errors. This article covers the same ground in more depth.
TypeScript projects
Add typescript-eslint, spread tseslint.configs.recommended before the Prettier entry, and extend the files pattern to **/*.{ts,tsx}. The conflict logic is identical: Prettier last, always.
Stylistic rules after ESLint 10
Core formatting rules in ESLint have been deprecated and progressively removed in favour of the @stylistic plugin. With the setup in this article you do not need them at all, since Prettier handles formatting. If a legacy config in your repo still uses them, delete them rather than trying to align them with Prettier.
Recap checklist
- Install
eslint,@eslint/js,globals,prettier,eslint-config-prettierlocally - Create
eslint.config.mjswith the Prettier config last - Create
.prettierrc.jsonand.prettierignore - Install only the ESLint and Prettier VS Code extensions
- Commit
.vscode/settings.jsonwith format on save plussource.fixAll.eslint - Run
npx eslint-config-prettierto prove there are no conflicts left - Add npm scripts and a pre-commit hook so CI agrees with your editor
FAQ
How do I set up Prettier with ESLint?
Install both as dev dependencies, keep their responsibilities separate, then add eslint-config-prettier as the final entry of your ESLint flat config. That single package disables the ESLint rules that duplicate Prettier options, which is what stops the two tools from fighting.
How do I set up ESLint in VS Code?
Install ESLint in the project with npm i -D eslint, create an eslint.config.mjs, install the dbaeumer.vscode-eslint extension, then open VS Code at the project root. If nothing is underlined, approve the local library through ESLint: Manage Library Execution and check the ESLint output channel.
How do I configure Prettier in VS Code?
Install esbenp.prettier-vscode, set "editor.defaultFormatter": "esbenp.prettier-vscode" and "editor.formatOnSave": true in your workspace settings, and put your options in a .prettierrc.json file rather than in VS Code settings so the CLI and CI behave identically.
Should ESLint or Prettier run first on save?
With this setup the order barely matters because there is no overlap. VS Code runs the formatter and then the save code actions. If you prefer Prettier to run through code actions before ESLint fixes, you can drive it with editor.codeActionsOnSave instead of formatOnSave, but the simple configuration above is enough for most projects.
Is ESLint obsolete?
No. ESLint is still the reference linter for JavaScript and TypeScript in 2026. What changed is its scope: formatting rules moved out (to Prettier or @stylistic) and the flat config replaced .eslintrc. Newer Rust based tools such as Biome or Oxlint are faster alternatives that bundle linting and formatting, but ESLint keeps the largest plugin ecosystem, which is why the ESLint plus Prettier pairing remains the safe default.
Do I need both a Prettier config file and VS Code settings?
Yes, and they serve different purposes. The .prettierrc.json defines the formatting rules for everyone, including CI and the CLI. The .vscode/settings.json only tells the editor when and with which extension to apply them.
Need help standardising linting, formatting and CI checks across a whole codebase or a team? The engineering team at pixelfabs.com does exactly this kind of developer experience clean-up.