Step-by-step
-
1
Install Prettier and ESLint
Install all three tools as development dependencies.
@eslint/jsprovides the modern recommended rule set.eslint-config-prettieris the bridge that prevents formatting conflicts — it turns off all ESLint rules that Prettier handles.bashnpm install --save-dev prettier eslint @eslint/js eslint-config-prettier # Confirm installations npx prettier --version npx eslint --version -
2
Create a .prettierrc Config
Create
.prettierrcin the project root. Prettier has sensible defaults — you only need to override the options you disagree with. The three most commonly customized aresemi,singleQuote, andprintWidth.Pick a config and commit it. The goal is consistency, not perfection. The team's second-choice format, enforced automatically, is better than the best format enforced by social pressure alone.
json// .prettierrc { "semi": true, "singleQuote": true, "printWidth": 100, "tabWidth": 2, "trailingComma": "all", "arrowParens": "always" } -
3
Create the ESLint Flat Config
ESLint v9+ uses a flat config file named
eslint.config.js(the old.eslintrcformat is deprecated). The key line iseslintConfigPrettierat the end of the array — it disables all ESLint formatting rules so they never conflict with Prettier.javascript// eslint.config.js import js from '@eslint/js'; import eslintConfigPrettier from 'eslint-config-prettier'; export default [ js.configs.recommended, { rules: { // Your project-specific rules 'no-console': 'warn', 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], 'no-var': 'error', 'prefer-const': 'error', }, }, // MUST be last — disables ESLint formatting rules // so Prettier can own them without conflicts eslintConfigPrettier, ]; // Note: if your package.json doesn't have "type": "module", // use .mjs extension: eslint.config.mjs -
4
Add Ignore Files
Tell both tools to skip generated files and dependencies. Without these, Prettier and ESLint waste time processing hundreds of files they should never touch.
bash# .prettierignore node_modules/ dist/ build/ coverage/ *.min.js package-lock.json pnpm-lock.yaml # Also add ignores to eslint.config.js: # Add this object BEFORE the rules objects: { ignores: [ 'node_modules/', 'dist/', 'build/', 'coverage/', '**/*.min.js', ], } -
5
Add Format and Lint Scripts
Add scripts to
package.jsonso the whole team uses the same commands.format:checkis used in CI to fail the build if unformatted code is committed;formatis the local fix command.javascript// package.json { "scripts": { "lint": "eslint .", "lint:fix": "eslint . --fix", "format": "prettier --write \"**/*.{js,ts,jsx,tsx,json,css,md}\"", "format:check": "prettier --check \"**/*.{js,ts,jsx,tsx,json,css,md}\"" } } // Usage: npm run format # fix all files in place npm run format:check # just check, no writes (good for CI) npm run lint # report ESLint violations npm run lint:fix # auto-fix fixable violations -
6
Enable Format on Save in VS Code
Install the Prettier - Code formatter extension (ID:
esbenp.prettier-vscode). Then configure VS Code to format on every save. Commit the settings file so the whole team gets it automatically.json// .vscode/settings.json { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" }, // Per-language overrides (if needed) "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" } } -
7
Add a Pre-commit Hook with Husky and lint-staged
Format on Save is a courtesy to the developer. The pre-commit hook is the enforcement. It runs Prettier and ESLint only on the files staged for the commit — fast, targeted, and automatic. Code that fails the hook cannot be committed.
bash# Install husky and lint-staged npm install --save-dev husky lint-staged # Initialize husky (creates .husky/ directory) npx husky init # The init command creates .husky/pre-commit — replace its content: echo 'npx lint-staged' > .husky/pre-commit -
8
Configure lint-staged
Add a
lint-stagedconfig topackage.json. This tells lint-staged exactly which tool to run on which file types. The--fixflag on ESLint means it auto-corrects what it can before the commit; Prettier rewrites the file to match the format config.javascript// package.json — add this at the top level { "lint-staged": { "*.{js,ts,jsx,tsx}": [ "prettier --write", "eslint --fix" ], "*.{json,css,md}": [ "prettier --write" ] } } // Test it: stage a file with bad formatting, then commit git add src/index.js git commit -m "test" // Prettier runs on src/index.js, ESLint runs on src/index.js // If either fails (unfixable ESLint error), the commit is blocked
Tips & gotchas
- Run <code>npm run format</code> once on the entire codebase before setting up the pre-commit hook — otherwise the first commit after setup rewrites hundreds of files and pollutes the git blame.
- ESLint and Prettier serve different purposes and should not overlap. ESLint catches <code>no-unused-vars</code>, <code>no-undef</code>, and <code>prefer-const</code>. Prettier handles indentation, quotes, and line length. Never try to enforce formatting through ESLint rules when Prettier is installed.
- If you are using TypeScript, also install <code>@typescript-eslint/eslint-plugin</code> and <code>@typescript-eslint/parser</code> for type-aware linting rules.
- Add a CI check (<code>npm run format:check && npm run lint</code>) to your GitHub Actions workflow. This catches anything that slipped past the pre-commit hook (skipped with <code>--no-verify</code>) or was edited directly on the remote.
- The <code>prettier --write</code> command modifies files in place. If you want to preview changes without writing, use <code>prettier --check</code> — it exits with a non-zero code if any file needs formatting, which is what CI uses.
Wrapping up
Prettier and ESLint together solve two completely different problems. Prettier removes the entire class of formatting debates from your team — no one argues about semicolons when a machine decides. ESLint catches the code patterns that actually matter: undefined variables, unused imports, inconsistent const/let usage. The pre-commit hook is the enforcement layer that means neither tool depends on developer discipline to work.