ESLint & Prettier — Configuration, Integration & CI Setup
ESLint catches bugs and enforces code quality rules. Prettier formats your code consistently. Together, they automate the two most tedious parts of development: finding errors and fixing style.
What You’ll Learn
In this tutorial, you’ll learn how to configure ESLint with the new flat config system, set up Prettier with .prettierrc, combine them without conflicts, integrate with VS Code for fix-on-save, add pre-commit hooks with husky and lint-staged, configure monorepo setups, and run both tools in CI pipelines.
Why It Matters
Inconsistent code style causes pointless code review debates. Linting errors caught in CI waste developer time. A well-configured ESLint + Prettier setup catches bugs before they reach production and eliminates style discussions from code reviews.
Real-World Use
When you save a file in VS Code and it auto-formats — that’s Prettier running. When you see red squiggles under an unused variable — that’s ESLint. Durga Antivirus Pro uses ESLint to enforce secure coding patterns in its JavaScript components, preventing common vulnerabilities before they ship.
flowchart LR
A[Developer Writes Code] --> B{ESLint}
B -->|Errors| C[Fix Issues]
B -->|Warning| D[Review]
B -->|Pass| E{Prettier}
C --> E
D --> E
E -->|Format| F[Formatted Code]
F --> G[Pre-commit Hook]
G -->|lint-staged| H[Stage & Commit]
H --> I[CI Pipeline]
I --> J{Build Passes?}
J -->|Yes| K[Deploy]
J -->|No| L[Fix & Re-commit]
ESLint Flat Config
ESLint 9+ uses the flat config system (eslint.config.js) instead of the legacy .eslintrc format.
// eslint.config.js — Flat config (ESLint 9+)
import js from '@eslint/js';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import reactPlugin from 'eslint-plugin-react';
import globals from 'globals';
export default [
// 1. Global ignores
{
ignores: ['dist/', 'node_modules/', '*.min.js'],
},
// 2. JavaScript base config
js.configs.recommended,
// 3. TypeScript config
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser: tsParser,
parserOptions: {
project: './tsconfig.json',
},
},
plugins: {
'@typescript-eslint': tsPlugin,
},
rules: {
...tsPlugin.configs.recommended.rules,
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
}],
'@typescript-eslint/explicit-function-return-type': 'warn',
},
},
// 4. React config
{
files: ['**/*.jsx', '**/*.tsx'],
plugins: {
react: reactPlugin,
},
rules: {
...reactPlugin.configs.recommended.rules,
'react/react-in-jsx-scope': 'off', // Not needed in React 18+
},
settings: {
react: { version: 'detect' },
},
},
// 5. Custom rules for all files
{
rules: {
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-debugger': 'error',
'eqeqeq': ['error', 'always'],
'curly': ['error', 'all'],
},
},
];Key Concepts
files: pattern to filter which files the config applies toignores: global file patterns to skiplanguageOptions: parser (espree, @typescript-eslint/parser) and ecmaVersionplugins: objects with rule definitionsrules: rule configurations ('off','warn','error')
Running ESLint
# Check for issues
npx eslint src/
# Auto-fix fixable issues
npx eslint src/ --fix
# Output results as JSON
npx eslint src/ --format json
# Exit with non-zero on warnings too
npx eslint src/ --max-warnings 0Prettier Configuration
Prettier is an opinionated formatter. Create a .prettierrc file in your project root:
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"tabWidth": 2,
"printWidth": 100,
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf"
}Running Prettier
# Check formatting
npx prettier --check src/
# Format all files
npx prettier --write src/
# Check specific file types
npx prettier --check "src/**/*.{js,ts,tsx,json,css,md}"Prettier vs ESLint Overlap
Prettier and ESLint’s formatting rules overlap. Before Prettier, ESLint rules like indent, max-len, and comma-dangle handled formatting. Prettier now handles all formatting; ESLint should focus on code-quality rules.
Prettier replaces these ESLint rules (among others):
indent,max-len,comma-dangle,quotes,semi,space-before-function-parenobject-curly-spacing,array-bracket-spacing,computed-property-spacingkeyword-spacing,key-spacing,comma-spacing
Avoiding Conflicts
Use eslint-config-prettier to turn off all ESLint rules that conflict with Prettier:
// eslint.config.js with Prettier integration
import js from '@eslint/js';
import prettierConfig from 'eslint-config-prettier';
export default [
js.configs.recommended,
// Custom rules
{
rules: {
'no-unused-vars': 'warn',
'no-console': 'warn',
},
},
// MUST be last — disables conflicting rules
prettierConfig,
];Install:
npm install --save-dev eslint-config-prettierVS Code Integration (Fix-on-Save)
Create .vscode/settings.json in your project:
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}Pre-commit Hooks with Husky and lint-staged
# Install
npm install --save-dev husky lint-staged
# Initialize husky
npx husky init
# Add lint-staged config to package.jsonIn package.json:
{
"lint-staged": {
"*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,css,md}": ["prettier --write"]
}
}Modify .husky/pre-commit:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-stagedNow every commit auto-formats and lints only staged files — keeping the entire codebase clean without slowing down commits.
Monorepo Configuration
For monorepos (Nx, Turborepo, Lerna) with multiple packages:
// Root eslint.config.js
import tsPlugin from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
export default [
{
ignores: ['**/dist/', '**/node_modules/'],
},
// Base config for all packages
{
rules: {
'no-console': 'warn',
},
},
// Package-specific overrides
{
files: ['packages/frontend/**'],
rules: {
'no-console': 'error',
},
},
{
files: ['packages/backend/**'],
rules: {
'no-console': 'off',
},
},
];Each package can also have its own .prettierrc if they need different formatting (rare — consistency is better).
CI Integration
GitHub Actions example:
# .github/workflows/lint.yml
name: Lint and Format Check
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npx eslint src/ --max-warnings 0
- run: npx prettier --check src/For GitLab CI:
lint:
stage: test
script:
- npm ci
- npx eslint src/ --max-warnings 0
- npx prettier --check src/Common Mistakes
1. Running ESLint and Prettier without conflict resolution
Without eslint-config-prettier, they fight over formatting. ESLint formats one way, Prettier formats another. Always add prettier config as the last entry.
2. Using extends in flat config
Flat config doesn’t support extends. Use array spread (...) or import config objects. Legacy .eslintrc files won’t work with ESLint 9+.
3. Not ignoring build output
Running ESLint on dist/ or node_modules/ wastes time and produces noise. Always add ignores for generated directories.
4. Overriding Prettier’s defaults
Prettier works best with its defaults. Changing printWidth to 120 or tabWidth to 4 is common but reduces consistency with the ecosystem.
5. Running lint-staged without husky
Lint-staged alone doesn’t run automatically. Install husky to create the git hook that triggers lint-staged before each commit.
6. Not handling monorepo overrides
A strict ESLint rule in a library package might break a frontend package. Use per-package overrides in the flat config.
Practice Questions
What does
eslint-config-prettierdo? It disables all ESLint rules that conflict with Prettier’s formatting, allowing ESLint to focus on code quality while Prettier handles formatting.How does flat config differ from
.eslintrc? Flat config uses an array of config objects ineslint.config.js. There’s noextends— you import and spread configs. It’s cleaner and tree-shakeable.What is lint-staged and why use it? lint-staged runs linters on only the files staged for commit. It prevents committing dirty code without linting the entire codebase on every commit.
How do you fix formatting on save in VS Code? Set
editor.formatOnSave: trueandeditor.defaultFormatter: "esbenp.prettier-vscode". For ESLint fixes, addsource.fixAll.eslinttocodeActionsOnSave.Why should ESLint not handle formatting rules? Prettier is a specialised formatter that handles formatting better. ESLint should focus on code quality (unused vars, security, type checking). Separating concerns reduces config complexity.
Challenge
Set up a new project with ESLint flat config, Prettier, husky, and lint-staged from scratch. Add rules for React and TypeScript. Verify that:
- ESLint catches
no-unused-vars - Prettier formats on save
git commitruns lint-staged and rejects on ESLint errors
Real-World Task
In an existing project, run npx eslint --format json src/ > eslint-report.json and examine the output. Identify the top 3 most common rule violations. Add corresponding rules to prevent them going forward.
FAQ
Mini Project: Full Toolchain Setup Script
Create a bash script that bootstraps a new project with:
- ESLint + flat config + Prettier + husky + lint-staged
- TypeScript and React support
- VS Code settings for fix-on-save
- CI workflow for GitHub Actions
The script should prompt for project type (React, Node, Library) and set up the appropriate plugins.
What’s Next
Before moving on, you should understand:
- ESLint flat config structure and how plugins work
- Prettier configuration and how it differs from ESLint
- How husky + lint-staged prevent unformatted code from being committed
- CI integration for linting and formatting
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro