Modern Linting in 2025: ESLint Flat Config with TypeScript and JavaScript

ESLint Flat Config

Clean, consistent, and secure code is essential—especially in modern web applications where performance, accessibility, and maintainability intersect. With ESLint’s flat config now fully stabilized, it’s time to upgrade how we configure linting across JavaScript and TypeScript projects.

In this post, I’ll walk through how I use ESLint’s flat config with JavaScript, TypeScript, and Prettier. This config powers application codebases, helping our teams ship higher-quality UI with fewer surprises. I have left out specific rules as that will be your teams choice.

Why Use ESLint Flat Config?

The new flat config eliminates legacy concepts like "extends" chains and config merging, replacing them with a single JavaScript file (eslint.config.js) that gives you full control over:

  • What rules apply to which files
  • How parsers and plugins are configured
  • Fine-grained control of language options and environments

The following will demonstrate how to use rulesets for Typescript independently of JavaScript and apply plugins and sharable configs.

Full Example: eslint.config.js

import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import globals from 'globals';
import prettierRecommended from 'eslint-plugin-prettier/recommended';

export default tseslint.config(
  {
    ignores: ['dist/', 'node_modules/', '**/*.d.ts', 'coverage/'],
  },
  prettierRecommended,
  eslint.configs.recommended,
  {
    languageOptions: {
      globals: {
        ...globals.browser,
      },
    },
  },
  {
    files: ['**/*.ts', '**/*.spec.ts'],
    extends: [tseslint.configs.recommended],
    plugins: {
      '@typescript-eslint': tseslint.plugin
    },
    languageOptions: {
      parser: tseslint.parser,
      parserOptions: {},
    },
    rules: {
      '@typescript-eslint/no-explicit-any': 'off',
    },
  },
  {
    files: ['**/*.js', '**/*.mjs'],
    extends: [tseslint.configs.disableTypeChecked],
    languageOptions: {
      globals: {
        SomeGlobalVariable: true,
      },
    },
    rules: {},
  },
);

Section-by-Section Breakdown

1. Ignore Unnecessary Files

{
  ignores: ['dist/', 'node_modules/', '**/*.d.ts', 'coverage/'],
}

This globally prevents ESLint from wasting time on output directories, type declarations, or coverage reports.

2. Core JavaScript Rules

eslint.configs.recommended

This brings in ESLint’s recommended base rules—think things like no-unused-vars or no-undef.

languageOptions: {
  globals: {
    ...globals.browser,
  },
}

This ensures that browser globals like window and document don’t trigger linter warnings.
It has many other properties like jest, qunit, jquery etc

3. TypeScript Support

{
...
  files: ['**/*.ts', '**/*.spec.ts'],
  extends: [tseslint.configs.recommended]
...
}

This pulls in @typescript-eslint‘s best-practice rules, optimized for strictness. These rules will only be run against files of type .ts (not .js files)

languageOptions: {
  parser: tseslint.parser,
  parserOptions: {},
}

Enables full project-aware linting (using tsconfig.json) for improved type resolution.

plugins: {
  '@typescript-eslint': tseslint.plugin
}

@typescript-eslint: Enables type-aware linting

4. Prettier Integration

import prettierRecommended from 'eslint-plugin-prettier/recommended';

{
...
  prettierRecommended
...
}

This makes Prettier the source of truth for formatting by exposing its errors through ESLint.
You will need a prettier.config.js in the root folder configured similar to the following:

export default {
    semi: true,
    trailingComma: 'all',
    singleQuote: true,
    printWidth: 120,
    tabWidth: 4,
    'quote-props': 'consistent',
    endOfLine: 'auto',
};

5. JavaScript-Only Rules

{
  files: ['**/*.js', '**/*.mjs'],
  extends: [tseslint.configs.disableTypeChecked],
}

Here we turn off TypeScript-specific rules when linting .js or .mjs files. This ensures ESLint doesn’t complain about missing types in non-TS files.

Executing eslint via your package.json

To make all this work you need to add a command to the scripts section of the package.json in your project’s root folder. And also install the require packages.

npm install --save-dev eslint-config-prettier eslint-plugin-prettier prettier typescript-eslint
{
...
 "scripts": {
    "lint": "npx eslint . src/ --fix=true"
 }
 "devDependencies":{
    "eslint-config-prettier": "^10.0.2",
    "eslint-plugin-prettier": "^5.2.3",
    "prettier": "^3.4.2",
    "typescript-eslint": "^8.25.0",
 }
...
}

The next command will run eslint rules on the root folder (indicated by the dot) and the src folder (incl subdirectories). The –fix flag will automatically fix prettier and script issues that are fixable.

npm run lint

Conclusion

Setting up a flat ESLint config can be time-consuming and tricky, but I hope this post has helped simplify the process. By walking through this linting setup, my goal was to show how you can align your configuration with your project’s architecture and your team’s development goals. With this approach, you can confidently apply separate rules for JavaScript and TypeScript without conflicts.