Node.js ESLint & Prettier Setup Guide for 2026
Published September 21, 2026 · Editorial policy

Node.js tooling has evolved significantly. If you are still cobbling together .eslintrc.*, .prettierrc.*, .eslintignore, and .prettierignore files while fighting plugin conflicts, you are carrying technical debt from the previous decade.
In 2026, the ecosystem has fully standardized around ESLint's Flat Config system (introduced in v9) and streamlined formatting practices. The days of @typescript-eslint/parser and eslint-config-prettier hacks are behind us. Flat config natively cascades, merges, and simplifies everything.
This guide walks you through setting up a modern, zero-friction Node.js project using ESLint v9+ and Prettier, leveraging native ESM, TypeScript (optional, but assumed as standard for Tier-1 devs), and strict code quality controls.
Prerequisites and Environment
Before running a single command, ensure your local environment matches the baseline expectations for a 2026 production Node.js environment:
- Node.js: v22.x LTS or v24.x Current (utilizing native ESM support by default via
"type": "module"inpackage.json). - Package Manager: pnpm (recommended for speed and strict dependency hoisting) or npm v10+.
Initialize your project workspace if you haven't already:
mkdir node-modern-setup
cd node-modern-setup
pnpm init
Open your newly created package.json and ensure you are opting into native ECMAScript Modules:
{
"name": "node-modern-setup",
"version": "1.0.0",
"type": "module",
"private": true,
"engines": {
"node": ">=22.0.0"
}
}
Installing ESLint (Flat Config) and Prettier
We will install ESLint and Prettier as development dependencies. Notice that we are skipping legacy shared config packages that require complex inheritance chains.
Run the following command to install the core tools:
pnpm add -D eslint prettier
If your project uses TypeScript (which is standard for Tier-1 backend development in 2026), install the official TypeScript ESLint packages alongside them:
pnpm add -D typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin
Understanding the Flat Config Paradigm
In ESLint 9 and later, configuration shifts from hierarchical cascading JSON/JS files (.eslintrc.js) to a single, explicit JavaScript file named eslint.config.js at the root of your project.
Each object in the exported array is a configuration configuration object that can target specific files using files and ignores patterns. This removes ambiguity about which configuration applies to which file.
Configuring Prettier
Prettier handles code formatting (whitespace, line length, semicolons), while ESLint handles code quality (unused variables, logical bugs, security anti-patterns).
Create a .prettierrc file in your root directory. We prefer a clean, modern configuration that aligns with standard enterprise practices:
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"endOfLine": "lf"
}
To prevent Prettier from formatting files you don't care about (like build outputs, coverage reports, or lockfiles), create a .prettierignore file:
node_modules
dist
build
coverage
pnpm-lock.yaml
package-lock.json
Configuring ESLint (Flat Config)
Now, let's set up eslint.config.js in the root of your project.
Because we want to avoid formatting rules conflicting between ESLint and Prettier, we use eslint-config-prettier (or simply let Prettier run independently via lint-staged). In 2026, the cleanest architecture is letting ESLint focus purely on AST analysis and letting Prettier handle raw text layout.
Create eslint.config.js:
import js from '@eslint/js';
import tseslint from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import globals from 'globals';
export default [
// Global ignores to prevent scanning build directories and lockfiles
{
ignores: ['dist/**', 'build/**', 'node_modules/**', 'coverage/**'],
},
// Base ESLint recommended rules for JavaScript
js.configs.recommended,
// Custom configuration for all TypeScript and JavaScript source files
{
files: ['**/*.{js,mjs,cjs,ts}'],
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
globals: {
...globals.node,
...globals.es2024,
},
},
plugins: {
'@typescript-eslint': tseslint,
},
rules: {
// TypeScript specific overrides
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'warn',
// General Node.js best practices
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-unused-vars': 'off', // Handled by @typescript-eslint/no-unused-vars
'prefer-const': 'error',
'eqeqeq': ['error', 'always'],
'curly': ['error', 'all'],
},
},
];
Breaking Down the Flat Config File
ignoresblock: Replaces.eslintignore. Any files matched here are entirely skipped by the linter engine.js.configs.recommended: Pulls in ESLint's default sensible rules for modern JavaScript.languageOptions&globals: Instead of guessing environment variables, we explicitly inject Node.js and ES2024 globals using the standardglobalsnpm package (pnpm add -D globalsif not installed).rules: Enforces strict coding standards, such as throwing errors on mutable variables (prefer-const), disallowing loose equality (eqeqeq), and flagging strayconsole.logstatements in production codebases while keepingconsole.errorandconsole.warnaccessible.
Integrating Prettier into ESLint
Historically, developers installed eslint-plugin-prettier to run Prettier as an ESLint rule. Performance benchmarks and maintainer recommendations have shifted away from this approach. Running Prettier as an ESLint rule introduces unnecessary overhead during lint operations.
Instead, the modern standard is a two-step verification process:
- ESLint checks code correctness.
- Prettier checks and fixes code formatting.
To make this seamless, add formatting check scripts to your package.json:
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --check .",
"format:write": "prettier --write ."
}
}
Automating with Git Hooks (Husky & Lint-Staged)
No Tier-1 development workflow is complete without automated guardrails. You want to ensure that developers cannot commit code that violates formatting or linting rules.
We will use Husky (for managing Git hooks) and lint-staged (to run linters only on staged files, keeping commit speeds blazing fast).
Install the packages:
pnpm add -D husky lint-staged
Initialize Husky in your project:
pnpm exec husky init
This command creates a .husky directory with a default pre-commit hook script. Open .husky/pre-commit and replace its contents with:
npx lint-staged
Next, configure lint-staged inside your package.json. This tells the hook to run ESLint and Prettier checks specifically on files staged for commit:
{
"lint-staged": {
"*.{js,mjs,cjs,ts}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yaml,yml}": [
"prettier --write"
]
}
}
Now, whenever a developer runs git commit, Husky intercepts the commit, lint-staged formats and lints only the modified files, fixes auto-fixable issues, and halts the commit if unresolvable lint errors exist.
IDE Integration (VS Code)
Even with robust CLI tooling and Git hooks, real-time feedback inside your editor is essential for a smooth developer experience. If you are using VS Code, configure your workspace settings to format and lint on save without conflicts.
Create a .vscode/settings.json file in your project root:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
Ensure your team has the following extensions installed:
- ESLint (
dbaeumer.vscode-eslint) - Prettier - Code formatter (
esbenp.prettier-vscode)
Because ESLint v9 uses Flat Config, ensure your VS Code ESLint extension is up to date (v3.0+). It automatically detects eslint.config.js without requiring legacy workspace flags.
Testing Your Setup
Let's verify that everything is working harmoniously. Create a sample TypeScript file at src/index.ts with intentional formatting flaws and an unused variable to test both Prettier and ESLint:
// src/index.ts
const greeting: string = "Hello, TypeScript 2026";
function calculateTax(amount:number):number{
const unusedVar = 42;
return amount * 0.2;
}
console.log(greeting, calculateTax(100));
1. Test Prettier Formatting
Run the format check command:
pnpm format
You should see output indicating that src/index.ts contains formatting issues (e.g., spacing around colons). Fix them automatically by running:
pnpm format:write
2. Test ESLint Quality Checks
Run the lint command:
pnpm lint
ESLint will flag the unusedVar variable because our configuration specifies '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }].
Output:
/path/to/node-modern-setup/src/index.ts
5:9 error 'unusedVar' is assigned a value but never used @typescript-eslint/no-unused-vars
✖ 1 problem (1 error, 0 warnings)
Refactor src/index.ts to clear the error:
// src/index.ts
const greeting: string = 'Hello, TypeScript 2026';
function calculateTax(amount: number): number {
return amount * 0.2;
}
// eslint-disable-next-line no-console
console.log(greeting, calculateTax(100));
Run pnpm lint again. The output should be clean with a exit code of 0.
Troubleshooting Common 2026 Gotchas
When migrating legacy codebases or setting up Greenfield projects with modern tooling, you may occasionally run into specific edge cases.
Flat Config Not Detecting Files
If ESLint says it processed 0 files, verify that your eslint.config.js file uses explicit glob patterns inside the files array and that you are running ESLint from the directory containing eslint.config.js.
Rule Conflicts between Prettier and ESLint
If you notice ESLint throwing errors about code style (such as max-len, indentation, or quotes), ensure you haven't accidentally imported conflicting stylistic rules into your eslint.config.js. In modern ESLint setups, stylistic rules should be left entirely to Prettier, leaving ESLint to handle logical code correctness.
Monorepo Support
If your Node.js project is part of a Turborepo or pnpm workspace, place a base eslint.config.js at the root and extend or reference it within package-specific flat configs, utilizing workspaces flags where appropriate. The flat config design natively supports cascading arrays across workspace packages without complex path mapping hacks.


