ESLint Packages

Purpose and Scope

Turborepo publishes two ESLint-facing packages for the same core developer problem: source code can read environment variables that are not represented in the task hash. When that happens, Turborepo may believe a task is cache-safe even though a runtime input changed outside the declared configuration. The ESLint packages bring that problem into the editor and normal lint output by flagging undeclared environment variable usage. This page covers both packages together because teams often decide between a shareable configuration and direct plugin usage when designing a workspace-wide lint setup.

Sources: apps/docs/content/docs/reference/eslint-config-turbo.mdx, apps/docs/content/docs/reference/eslint-plugin-turbo.mdx

The short rule of thumb is simple. Use the configuration package when you want a ready-made Turborepo ESLint configuration that can be included from a shared package. Use the plugin package when you want to register the plugin yourself and control the rule list explicitly. Both references describe the same linting goal and the same rule name, so the operational choice is mostly about how your repository organizes ESLint configuration rather than whether the rule checks a different class of problems.

Sources: packages/eslint-config-turbo/README.md, packages/eslint-plugin-turbo/README.md

Relevant Source Files

  • apps/docs/content/docs/reference/eslint-config-turbo.mdx — First-party reference page for installing and using the shareable configuration, including package-manager-specific workspace commands, flat config import style, legacy extension style, and rule option examples.
  • apps/docs/content/docs/reference/eslint-plugin-turbo.mdx — First-party reference page for installing and using the plugin directly, including recommended flat config, manual plugin registration, legacy plugin configuration, and rule option examples.
  • packages/eslint-config-turbo/README.md — Package README that mirrors the public usage contract for the configuration package and documents the dependency on ESLint plus install and usage snippets.
  • packages/eslint-plugin-turbo/README.md — Package README that mirrors the public usage contract for the plugin package and documents recommended and manual configuration patterns.

Package Roles and Selection

The configuration package is named for the shareable config pattern. In flat config, it is imported from the flat entry point and spread into the exported configuration array. In legacy configuration, it is added through the extends field using the shorthand name. That makes it a good fit for a monorepo package such as a central ESLint configuration workspace, where applications and packages consume a consistent lint policy without repeating Turborepo-specific rule wiring in every project.

Sources: apps/docs/content/docs/reference/eslint-config-turbo.mdx, packages/eslint-config-turbo/README.md

The plugin package is the lower-level integration. It exposes a recommended flat configuration and also supports manual registration under the plugins object. Manual registration is useful when an existing lint stack already has a carefully ordered configuration array, or when a team wants to enable only the Turborepo rule while leaving other ESLint behavior unchanged. The official examples show both the recommended preset and direct rule declaration, which makes the package appropriate for incremental adoption in repositories that are not ready to standardize on a full shared config.

Sources: apps/docs/content/docs/reference/eslint-plugin-turbo.mdx, packages/eslint-plugin-turbo/README.md

Installation Reference

Install the package in the place where your ESLint configuration is held. The documentation examples assume many monorepos keep lint configuration in a dedicated workspace package, so the package-manager commands target that workspace instead of adding the dependency to every application. The package READMEs also call out that ESLint itself is required first. In practice, confirm the workspace that exports your base lint configuration, install the Turborepo ESLint package there, and then update the shared configuration file consumed by apps and packages.

Sources: apps/docs/content/docs/reference/eslint-config-turbo.mdx, apps/docs/content/docs/reference/eslint-plugin-turbo.mdx, packages/eslint-config-turbo/README.md, packages/eslint-plugin-turbo/README.md

pnpm add eslint-config-turbo --filter=@repo/eslint-config
pnpm add eslint-plugin-turbo --filter=@repo/eslint-config
yarn workspace @acme/eslint-config add eslint-config-turbo --dev
yarn workspace @acme/eslint-config add eslint-plugin-turbo --dev
npm install --save-dev eslint-config-turbo -w @acme/eslint-config
npm i --save-dev eslint-plugin-turbo -w @acme/eslint-config
cd packages/eslint-config && bun install eslint-config-turbo --dev
cd packages/eslint-config && bun install eslint-plugin-turbo --dev

For local package README usage outside a workspace-filtered example, install ESLint and then add the selected package as a development dependency. The first-party docs are more specific for monorepos because a shared configuration package is a common Turborepo pattern. Avoid installing both packages everywhere by default. A repository can standardize on the configuration package for most consumers and reserve direct plugin usage for packages with custom lint files, but the source examples do not require both in the same config for the basic rule to work.

Sources: packages/eslint-config-turbo/README.md, packages/eslint-plugin-turbo/README.md

Flat Config Usage

For ESLint v9-style flat config, the configuration package is imported from its flat entry point and spread into the exported array. The spread matters because the entry point provides configuration objects rather than a single legacy extends string. After spreading it, you can append additional configuration objects, including rule overrides. The documented override keeps the Turborepo rule name and adds an allow list option, which is useful when a repository intentionally permits a naming pattern that should not be reported as an undeclared environment variable.

Sources: apps/docs/content/docs/reference/eslint-config-turbo.mdx, packages/eslint-config-turbo/README.md

import turboConfig from "eslint-config-turbo/flat";
 
export default [
  ...turboConfig,
  {
    rules: {
      "turbo/no-undeclared-env-vars": [
        "error",
        {
          allowList: ["^ENV_[A-Z]+$"],
        },
      ],
    },
  },
];

For direct plugin usage, the simplest flat config path is the recommended preset exposed from the plugin. When more control is required, register the plugin under the turbo key and declare the rule in the rules section. The examples show the same rule in a string severity form and in an array form with options. That means teams can begin with the preset, then move to manual registration later if they need to tune severity, add an allow list, or compose the plugin inside a larger custom configuration array.

Sources: apps/docs/content/docs/reference/eslint-plugin-turbo.mdx, packages/eslint-plugin-turbo/README.md

import turbo from "eslint-plugin-turbo";
 
export default [turbo.configs["flat/recommended"]];
import turbo from "eslint-plugin-turbo";
 
export default [
  {
    plugins: {
      turbo,
    },
    rules: {
      "turbo/no-undeclared-env-vars": "error",
    },
  },
];

Legacy eslintrc Usage

Legacy configuration is still documented for both packages. With the configuration package, add turbo to the extends list and rely on ESLint’s shareable config shorthand, which omits the package prefix. With the plugin package, add turbo to the plugins list and then configure the rule yourself. The difference mirrors ESLint’s traditional model: extends imports a prepared configuration, while plugins only register rule implementations and require explicit rule choices unless another configuration enables them.

Sources: apps/docs/content/docs/reference/eslint-config-turbo.mdx, apps/docs/content/docs/reference/eslint-plugin-turbo.mdx

{
  "extends": ["turbo"]
}
{
  "plugins": ["turbo"],
  "rules": {
    "turbo/no-undeclared-env-vars": [
      "error",
      {
        "allowList": ["^ENV_[A-Z]+$"]
      }
    ]
  }
}

Use legacy examples when a repository still uses eslintrc files, but prefer matching the configuration format already used by the rest of the workspace. Mixing flat and legacy examples in the same package can create confusion for maintainers because the import, plugin registration, and export shapes differ. The source material presents both modes as supported usage paths, not as migration steps, so the safest implementation plan is to select one format per shared lint package and keep app-level consumers aligned with it.

Sources: packages/eslint-config-turbo/README.md, packages/eslint-plugin-turbo/README.md

Rule and Option Reference

The documented rule is turbo/no-undeclared-env-vars. Its purpose is to detect environment variables that appear in source code but are not part of Turborepo’s hashing configuration. The references describe the user-visible result as editor highlighting and ESLint output. The shown option is allowList, an array of string patterns such as ^ENV_[A-Z]+$. Use that option for deliberate exceptions, but treat it as a narrow escape hatch: the preferred fix for real runtime inputs is usually to declare the environment variable in the relevant Turborepo configuration so task hashing reflects it.

Sources: apps/docs/content/docs/reference/eslint-config-turbo.mdx, apps/docs/content/docs/reference/eslint-plugin-turbo.mdx

ComponentPublic entry pointMain configuration shapeRule shown in docsOption shown in docs
eslint-config-turboeslint-config-turbo/flat or extends turboSpread flat config or extend shareable configturbo/no-undeclared-env-varsallowList
eslint-plugin-turboeslint-plugin-turboRecommended preset or plugins plus rulesturbo/no-undeclared-env-varsallowList

Practical Adoption Flow

Start by identifying the workspace package that owns shared lint configuration. Install either the configuration package or the plugin package there, then update the exported base configuration in that same package. Run ESLint locally and confirm that undeclared environment variable reads surface as lint errors. When a lint error is valid, update the Turborepo configuration so the environment input participates in hashing. When an exception is intentional, add a tightly scoped allow list pattern and document why the variable should not be represented as a hashed input.

Sources: apps/docs/content/docs/reference/eslint-config-turbo.mdx, apps/docs/content/docs/reference/eslint-plugin-turbo.mdx

After enabling the rule, review related configuration documentation for environment handling and task hashing. These ESLint packages are guardrails; they do not replace the need to model runtime inputs in Turborepo configuration. The next useful pages are the configuration reference for declaring environment-related fields and the environment variables concept page for understanding strictness, hashing, and runtime behavior. If your repository also maintains a general ESLint guide, connect it to this page so developers know why Turborepo-specific lint failures are cache correctness issues, not only style feedback.

Sources: apps/docs/content/docs/reference/eslint-config-turbo.mdx, apps/docs/content/docs/reference/eslint-plugin-turbo.mdx