---
id: monorepos
slug: /troubleshooting/typed-linting/monorepos
title: Monorepo Configuration
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

:::tip
**The [new "project service" in v8](/blog/announcing-typescript-eslint-v8-beta#project-service) requires no additional configuration for monorepos.**

If you're using `parserOptions.projectService`, you don't need this guide.
:::

If you're using a monorepo with `parserOptions.project`, these docs will help you figure out how to setup typed linting.
If you don't want to use typed linting, then you can stop here - you don't need to do anything special.

`parserOptions.project` configurations will look different based on which monorepo setup you use:

1. [One root `tsconfig.json`](#one-root-tsconfigjson)
2. [One `tsconfig.json` per package (and an optional one in the root)](#one-tsconfigjson-per-package-and-an-optional-one-in-the-root)

## One root `tsconfig.json`

If you only have one `tsconfig.json` file _and_ its `include` paths include all the files you'd like to lint, you can directly use it with typescript-eslint without further configuration.

If its `include` paths cannot include all files to be linted, we suggest creating a new config called `tsconfig.eslint.json`, that looks something like this:

```jsonc title="tsconfig.eslint.json"
{
  // extend your base config to share compilerOptions, etc
  "extends": "./tsconfig.json",
  "compilerOptions": {
    // ensure that nobody can accidentally use this config for a build
    "noEmit": true,
  },
  "include": [
    // whatever paths you intend to lint
    "src",
    "test",
    "tools",
  ],
}
```

Be sure to update your ESLint configuration file to point at this new TSConfig.

## One `tsconfig.json` per package (and an optional one in the root)

The `parserOptions.project` option introduced in [Linting with Type Information](../typed-linting/index.mdx) accepts an array of relative paths.
Paths may be provided as [Node globs](https://github.com/isaacs/node-glob/blob/f5a57d3d6e19b324522a3fa5bdd5075fd1aa79d1/README.md#glob-primer).
For each file being linted, the first matching project path will be used as its backing TSConfig.

<Tabs groupId="eslint-config">
<TabItem value="Flat Config">

```js title="eslint.config.mjs"
// @ts-check

import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';

export default defineConfig(
  js.configs.recommended,
  tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        // Remove this line
        project: true,
        // Add this line
        project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
      },
    },
  },
);
```

</TabItem>
<TabItem value="Legacy Config">

```js title=".eslintrc.js"
/* eslint-env node */
module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended-type-checked',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    // Remove this line
    project: true,
    // Add this line
    project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
  },
  plugins: ['@typescript-eslint'],
  root: true,
};
```

</TabItem>
</Tabs>

### Wide globs in `parserOptions.project`

Using wide globs `**` in your `parserOptions.project` may degrade linting performance.
Instead of globs that use `**` to recursively check all folders, prefer paths that use a single `*` at a time.

<Tabs groupId="eslint-config">
<TabItem value="Flat Config">

```js title="eslint.config.mjs"
// @ts-check

import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';

export default defineConfig(
  js.configs.recommended,
  tseslint.configs.recommendedTypeChecked,
  {
    languageOptions: {
      parserOptions: {
        // Remove this line
        project: ['./tsconfig.eslint.json', './**/tsconfig.json'],
        // Add this line
        project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
      },
    },
  },
);
```

</TabItem>
<TabItem value="Legacy Config">

```js title=".eslintrc.js"
/* eslint-env node */
module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-type-checked',
  ],
  parser: '@typescript-eslint/parser',
  parserOptions: {
    // Remove this line
    project: ['./tsconfig.eslint.json', './**/tsconfig.json'],
    // Add this line
    project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
    tsconfigRootDir: __dirname,
  },
  plugins: ['@typescript-eslint'],
  root: true,
};
```

</TabItem>
</Tabs>

See [Glob pattern in parser's option "project" slows down linting](https://github.com/typescript-eslint/typescript-eslint/issues/2611) for more details.

### Important note regarding large (> 10) multi-package monorepos

We've had reports that for sufficiently large and/or interdependent projects, you may run into OOMs using this approach.
Our advice is to set it up and test first, as there are very few cases that trigger this OOM.

See [#1192](https://github.com/typescript-eslint/typescript-eslint/issues/1192) for more information and discussion.

If you do run into an OOM, please comment on the above issue and let us know about your repo - the more information we have, the better.
As an interim workaround, consider one of the following:

- Switching to one root `tsconfig.eslint.json` (see [One root `tsconfig.json`](#one-root-tsconfigjson))
- Using a shell script to only lint one package at a time, using your existing config above.
