JavaScript Linting & Formatting in 2026: ESLint and Prettier Complete Guide
Code quality isn’t just about whether your program works — it’s about whether your entire team can read, maintain, and extend it without introducing bugs. JavaScript linting with ESLint catches errors before they reach production. Prettier enforces consistent formatting so code reviews focus on logic, not semicolons. Together, they’re the foundation of professional JavaScript development in 2026.
This guide walks you through setting up ESLint and Prettier from scratch, configuring rules for your team, integrating with your editor, and enforcing standards in CI/CD pipelines.
What Is JavaScript Linting and Why It Matters
Linting is static analysis — examining your code without running it to find potential bugs, style violations, and problematic patterns. A JavaScript linter reads your source files, compares them against a set of rules, and reports violations. Unlike testing, which verifies behavior, linting catches structural issues: unused variables, missing return statements, implicit type coercion, and hundreds of other patterns that lead to bugs.
Consider this code that’s valid JavaScript but almost certainly a bug:
// ESLint catches all of these
if (user.name = 'admin') { // Assignment instead of comparison
deleteEverything();
}
const data = fetchData(); // Missing await
console.log(data); // data is a Promise, not the result
function calculate(x, y) {
if (x > 0) {
return x + y;
}
// No return for x <= 0 — returns undefined
}
const config = require('./config');
const config = require('./other'); // Duplicate variable declaration
Every one of these bugs would pass through a JavaScript interpreter without errors. ESLint catches them instantly. Teams that adopt linting report fewer bugs in production, faster code reviews, and smoother onboarding for new developers.
ESLint Setup: Installing and Configuring JavaScript Linting
ESLint is the standard JavaScript linter. It's highly configurable, supports plugins for React, TypeScript, Vue, and more, and integrates with every major editor.
# Install ESLint
npm install --save-dev eslint
# Interactive setup (recommended for new projects)
npx eslint --init
# Or install with specific config
npm install --save-dev eslint @eslint/js
ESLint 9+ uses a flat config file (eslint.config.js) instead of the old .eslintrc format. Here's a modern configuration:
// eslint.config.js (flat config - ESLint 9+)
import js from '@eslint/js';
import globals from 'globals';
export default [
// Apply recommended rules to all JS files
js.configs.recommended,
{
files: ['**/*.{js,jsx,mjs}'],
languageOptions: {
ecmaVersion: 2025,
sourceType: 'module',
globals: {
...globals.browser,
...globals.node
}
},
rules: {
// Errors - these catch real bugs
'no-unused-vars': 'warn',
'no-undef': 'error',
'no-console': 'warn',
'eqeqeq': 'error', // Require === instead of ==
'no-var': 'error', // Use let/const
'prefer-const': 'warn', // Use const when possible
// Style
'curly': 'error', // Always use braces
'no-multi-spaces': 'warn',
'no-trailing-spaces': 'warn'
}
},
// Ignore patterns
{
ignores: ['dist/', 'node_modules/', '*.min.js']
}
];
# Run ESLint
npx eslint src/
# Auto-fix what's possible
npx eslint src/ --fix
# Check a specific file
npx eslint src/app.js
# Output as JSON (for CI tools)
npx eslint src/ --format json
ESLint Rules: Understanding Error Levels for JavaScript Linting
Every ESLint rule has three severity levels:
"off" (or 0) — disables the rule completely. "warn" (or 1) — shows a warning but doesn't fail the build. Useful during migration. "error" (or 2) — fails the lint check. Use for rules your team agrees must never be violated.
// Rule configuration examples
rules: {
// Simple on/off
'no-debugger': 'error',
// Rules with options
'no-unused-vars': ['warn', {
argsIgnorePattern: '^_', // Allow _unused parameters
varsIgnorePattern: '^_'
}],
'max-lines-per-function': ['warn', {
max: 50,
skipBlankLines: true,
skipComments: true
}],
'complexity': ['warn', 10], // Max cyclomatic complexity
'no-restricted-imports': ['error', {
patterns: ['../../../*'] // Prevent deeply nested imports
}]
}
Start with js.configs.recommended and customize from there. Don't enable every possible rule — overly strict linting leads to developers adding // eslint-disable comments everywhere, defeating the purpose.
ESLint Plugins: React, TypeScript, and More
ESLint's plugin system extends its capabilities to frameworks and languages. Here are the most important plugins for JavaScript linting in 2026:
# React
npm install --save-dev eslint-plugin-react eslint-plugin-react-hooks
# TypeScript
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
# Import sorting and validation
npm install --save-dev eslint-plugin-import
# Accessibility (a11y)
npm install --save-dev eslint-plugin-jsx-a11y
// eslint.config.js with React
import js from '@eslint/js';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
export default [
js.configs.recommended,
{
files: ['**/*.{jsx,tsx}'],
plugins: {
react,
'react-hooks': reactHooks
},
rules: {
'react/jsx-uses-react': 'off', // Not needed with new JSX transform
'react/react-in-jsx-scope': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn'
},
settings: {
react: { version: 'detect' }
}
}
];
The react-hooks/rules-of-hooks rule is particularly valuable — it prevents calling hooks conditionally or in loops, which would cause subtle and hard-to-debug state management bugs in your React applications.
Prettier: Automatic Code Formatting for JavaScript
While ESLint catches bugs and enforces coding patterns, Prettier handles formatting — indentation, line length, quotes, semicolons, trailing commas, and bracket spacing. The key philosophy: Prettier is opinionated. It has very few configuration options by design, ending formatting debates once and for all.
# Install Prettier
npm install --save-dev prettier
# Format all files
npx prettier --write src/
# Check formatting without modifying files
npx prettier --check src/
# Format a specific file
npx prettier --write src/app.js
// .prettierrc (minimal config — the whole point is few options)
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80,
"bracketSpacing": true,
"arrowParens": "avoid"
}
Create a .prettierignore file to skip generated files:
# .prettierignore
dist/
build/
coverage/
node_modules/
package-lock.json
*.min.js
Making ESLint and Prettier Work Together
ESLint and Prettier can conflict — ESLint has formatting rules that may disagree with Prettier's output. The solution is to disable ESLint's formatting rules and let Prettier handle all formatting. This keeps JavaScript linting focused on code quality while Prettier manages style.
# Install the integration packages
npm install --save-dev eslint-config-prettier eslint-plugin-prettier
// eslint.config.js with Prettier integration
import js from '@eslint/js';
import prettier from 'eslint-config-prettier';
export default [
js.configs.recommended,
prettier, // Must be last — disables conflicting ESLint rules
{
rules: {
// Your custom rules here
'no-unused-vars': 'warn',
'eqeqeq': 'error'
}
}
];
The eslint-config-prettier package disables all ESLint rules that conflict with Prettier. By placing it last in the config array, it overrides any formatting rules from other configs. This is the standard approach used by most teams in 2026.
Editor Integration: Real-Time JavaScript Linting
The real power of linting comes from editor integration. Instead of running npx eslint manually, your editor highlights problems as you type and auto-fixes on save.
VS Code Setup
// .vscode/settings.json
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
Install the ESLint extension (by Microsoft) and the Prettier extension (by Prettier). With the settings above, every time you save a file, Prettier formats it and ESLint auto-fixes what it can. You'll see red underlines for errors and yellow for warnings in real time.
Git Hooks: Enforcing Linting Before Commits
Editor integration is great, but not everyone on your team uses the same editor or has the same settings. Git hooks ensure that no unlinted code enters your repository.
# Install Husky and lint-staged
npm install --save-dev husky lint-staged
# Initialize Husky
npx husky init
// package.json
{
"scripts": {
"lint": "eslint src/",
"format": "prettier --write src/",
"prepare": "husky"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,css}": [
"prettier --write"
]
}
}
# .husky/pre-commit
npx lint-staged
Now when a developer runs git commit, lint-staged automatically runs ESLint and Prettier on only the staged files. If ESLint finds an unfixable error, the commit is blocked. This catches issues at the earliest possible point — before they enter the codebase.
JavaScript Linting in CI/CD Pipelines
Git hooks can be bypassed with --no-verify. Your CI/CD pipeline is the final enforcement layer for JavaScript linting.
# .github/workflows/lint.yml (GitHub Actions)
name: Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- run: npm ci
- run: npx eslint src/ --max-warnings 0
- run: npx prettier --check src/
The --max-warnings 0 flag is important — it fails the CI check even on warnings. This prevents warnings from accumulating over time. In mature codebases, every JavaScript linting violation should be either fixed or explicitly disabled with a comment explaining why.
Custom ESLint Rules for Your Team
Beyond built-in rules, you can create team-specific conventions:
// eslint.config.js — team conventions
{
rules: {
// Prevent accidental console.log in production code
'no-console': ['error', { allow: ['warn', 'error'] }],
// Enforce consistent naming
'camelcase': ['error', { properties: 'never' }],
// Limit file complexity
'max-lines': ['warn', { max: 300, skipComments: true }],
'max-depth': ['warn', 4],
// Enforce import organization
'sort-imports': ['warn', {
ignoreCase: true,
ignoreDeclarationSort: true
}],
// Prevent common async mistakes
'no-await-in-loop': 'warn',
'require-await': 'warn',
'no-return-await': 'error'
}
}
Document your rule choices in a team wiki or README. When a developer encounters a rule they don't understand, they should know why it exists and what problem it prevents.
Migrating an Existing Codebase to JavaScript Linting
Adding JavaScript linting to a large existing codebase can surface thousands of violations. Don't try to fix everything at once. Here's the practical migration strategy:
Step 1: Install ESLint with recommended rules and run it. Count the violations. Step 2: Set all rules to "warn" instead of "error." This lets the team see issues without blocking their work. Step 3: Fix the easy wins — unused variables, missing semicolons, == vs ===. Many of these are auto-fixable with --fix. Step 4: Gradually promote rules from "warn" to "error" as the codebase is cleaned up. Step 5: Enable the pre-commit hook once the team is comfortable with the rules.
# See how many violations exist
npx eslint src/ --format compact | wc -l
# Auto-fix everything possible
npx eslint src/ --fix
# Fix only specific rules
npx eslint src/ --fix --rule 'prefer-const: warn' --rule 'no-var: error'
What's Next
Linting catches bugs in your JavaScript. But what if your language itself could prevent entire categories of bugs? In the next lesson on TypeScript Basics, we'll explore how adding types to JavaScript eliminates null reference errors, catches wrong function arguments, and makes large codebases dramatically easier to refactor.
For more on the build tools that run ESLint and Prettier in your pipeline, see our guide to JavaScript Bundlers. And if you're setting up these tools in a new project, check out our npm & package.json guide first.