@nx/eslint - Migrations
For an overview of the plugin and setup instructions, see the @nx/eslint introduction.
The @nx/eslint plugin provides various migrations to help you migrate to newer versions of eslint projects within your Nx workspace. Below is a complete reference for all available migrations.
23.1.x
Section titled “23.1.x”update-23-1-0-convert-to-flat-config
Section titled “update-23-1-0-convert-to-flat-config”Version: 23.1.0-beta.0
Convert remaining ESLint configs to flat config for ESLint v9 and keep the workspace lint-passing, disabling rules whose preset defaults changed.
Requires
Section titled “Requires”| Name | Version |
|---|---|
eslint | >=9.0.0 |
ESLint v9 Flat Config Migration Instructions for LLM
Section titled “ESLint v9 Flat Config Migration Instructions for LLM”Overview
Section titled “Overview”These instructions guide you through finishing the migration of an Nx workspace to ESLint v9.
ESLint v9 makes flat config (eslint.config.{mjs,cjs,js}) the default config format. The legacy eslintrc format (.eslintrc.*) still works at runtime, but only when ESLINT_USE_FLAT_CONFIG=false is set, so Nx converts workspaces to flat config instead of relying on that escape hatch.
The migration runs in two halves:
- A deterministic pre-pass (the
@nx/eslint:convert-to-flat-configgenerator) that already converted the JSON and YAML eslintrc configs. - This prompt: finish the parts that need judgment and leave the workspace lint-passing.
Work systematically through each section below.
<pre_pass_summary note=“a deterministic pre-pass already applied these edits; verify the new shape is in place rather than redoing them”>
The pre-pass handled, mechanically:
- Converted the root and per-project JSON/YAML eslintrc files to
eslint.config.mjs:eslint:recommendedtojs.configs.recommended@nx/*presets to their flat-config equivalentsenvtolanguageOptions.globalsparser/parserOptionstolanguageOptionspluginsto the flatpluginsobjectignorePatternsand.eslintignoretoignores- stale
.eslintrc/.eslintignorereferences innx.jsonandproject.jsoninputs
- Added
@eslint/jsand@eslint/eslintrctopackage.jsonwhen the converted config needs them.
The pre-pass does NOT:
- Convert JavaScript-based eslintrc files (
.eslintrc.js,.eslintrc.cjs). It cannot evaluate them safely. - Change the output formatter a lint target uses.
- Decide whether a generated
FlatCompatshim should become flat-native config. - Make the workspace pass lint after ESLint v9 changed which rules its preset defaults enable.
Everything the pre-pass could not finish is forwarded to you in <advisory_context>.
How to read the wrapper sections above this file:
<files_changed>lists files the pre-pass wrote. Verify the new shape is in place; do not re-apply the same edit. It is absent when the pre-pass made no changes (for example a workspace that was already on flat config).<advisory_context>lists detections the pre-pass forwarded because it could not safely complete them. Every entry is pending work. Address each one in the relevant section below.
</pre_pass_summary>
<handoff_guidance>
In your handoff summary (1 to 3 sentences per the system prompt), name the sections you applied and explicitly call out any you skipped because they did not apply (for example “no JavaScript-based configs and no removed formatters in this workspace”).
</handoff_guidance>
Pre-Migration Checklist
Section titled “Pre-Migration Checklist”-
Confirm the ESLint version is v9:
Terminal window npx eslint --version -
Locate all ESLint config files:
- Flat configs:
eslint.config.{mjs,cjs,js}at the root and in each project. - Any remaining eslintrc files:
.eslintrc,.eslintrc.json,.eslintrc.yaml,.eslintrc.yml,.eslintrc.js,.eslintrc.cjs. - Ignore files:
.eslintignore.
- Flat configs:
-
Identify all lint targets:
Terminal window nx show projects --with-target lintCheck
project.jsonfiles for the@nx/eslint:lintexecutor oreslintrun-commands. Workspaces using the inferred plugin (@nx/eslint/plugin) get lint targets from the presence ofeslint.config.*; inspect them withnx show project <name> --json. -
Identify local ESLint rules or plugins authored inside the workspace. These use the rule API that changed in v9 (see section 6).
Nx-Specific Notes (read first)
Section titled “Nx-Specific Notes (read first)”- Flat config is the default in v9. eslintrc only resolves when
ESLINT_USE_FLAT_CONFIG=falseis set. Nx converts the workspace to flat config so that no environment variable is required. - Shared base config pattern: many Nx workspaces have a root
eslint.config.mjsthat each project imports, for exampleimport baseConfig from '../../eslint.config.mjs'. Convert and verify the base config first, then the per-project configs. - Inferred plugin targets:
@nx/eslint/plugininfers the lint target from the presence ofeslint.config.*. Renaming or moving the config invalidates inference. After config edits, runnx reset && nx show project <name>on a sample project to confirm the target is still present. - FlatCompat shim: when the pre-pass could not translate a third-party
extendsor a complex override natively, it emitted aFlatCompatshim (from the@eslint/eslintrcpackage). That config works as-is, but section 3 covers replacing it with flat-native config where low-risk.
1. Already on flat config? Verify only
Section titled “1. Already on flat config? Verify only”If the workspace already uses eslint.config.* at the root and in every project, with no remaining .eslintrc.* files, do NOT restructure it. The only required work is the passing-state check in section 4: a workspace on ESLint v9 can newly fail because v9 and typescript-eslint v8 changed which rules their recommended sets enable, even when the config was already flat.
2. Convert JavaScript-based ESLint configs the pre-pass skipped
Section titled “2. Convert JavaScript-based ESLint configs the pre-pass skipped”Search pattern: .eslintrc.js and .eslintrc.cjs files (forwarded in <advisory_context>).
What changed: the pre-pass only converts JSON and YAML eslintrc files. JavaScript-based configs run arbitrary code, so they need manual conversion.
// BEFORE (.eslintrc.js)module.exports = { extends: ['../../.eslintrc.json'], overrides: [ { files: ['*.ts'], rules: { '@typescript-eslint/no-explicit-any': 'error' }, }, ],};// AFTER (eslint.config.mjs)import baseConfig from '../../eslint.config.mjs';
export default [ ...baseConfig, { files: ['**/*.ts'], rules: { '@typescript-eslint/no-explicit-any': 'error' }, },];Action items:
- Convert each JavaScript-based config to
eslint.config.mjs, mirroring the structure the pre-pass produced for the JSON/YAML configs. - Preserve the existing rules, plugins, parser options, and overrides.
- Delete the original
.eslintrc.js/.eslintrc.cjsonce the flat config replaces it. - Update any
project.json/nx.jsoninputs that referenced the old file name.
3. Convert FlatCompat shims to flat-native config where low-risk
Section titled “3. Convert FlatCompat shims to flat-native config where low-risk”Search pattern: FlatCompat, @eslint/eslintrc, compat.extends(, compat.config( in the generated eslint.config.* files (listed in <files_changed>).
What changed: FlatCompat is a runtime shim that adapts eslintrc-style extends into flat config. Many plugins now ship native flat presets, which are clearer and avoid the shim.
Decision rule: convert a FlatCompat usage to flat-native config when it is low-risk, otherwise keep the shim.
- Low-risk (prefer flat-native): typescript-eslint configs, and plugins that document a flat preset (for example
eslint-plugin-react,eslint-plugin-import). - Keep the shim: third-party shared configs that do not document a flat-config entry point.
// BEFORE (FlatCompat shim, eslint.config.mjs)import js from '@eslint/js';import { fileURLToPath } from 'url';import { dirname } from 'path';import { FlatCompat } from '@eslint/eslintrc';
const compat = new FlatCompat({ baseDirectory: dirname(fileURLToPath(import.meta.url)), recommendedConfig: js.configs.recommended,});
export default [...compat.extends('plugin:@typescript-eslint/recommended')];// AFTER (flat-native, eslint.config.mjs)import tseslint from 'typescript-eslint';
export default [...tseslint.configs.recommended];Action items:
- For each
FlatCompatusage, decide flat-native vs keep-the-shim using the rule above. - When converting, drop the now-unused
@eslint/eslintrcimport if no shim remains in that file. - Re-run lint after each change to confirm the rule set did not silently shift.
4. Restore the passing baseline (required)
Section titled “4. Restore the passing baseline (required)”This is the core requirement of the migration: the workspace must lint cleanly when you are done.
ESLint v9 and typescript-eslint v8 changed which rules their recommended sets enable. A rule the user never configured may now report errors. Disable those rules; do not edit source files to satisfy them.
The set of rules the user explicitly configured before the migration is in <advisory_context> (the entry that starts with “Passing-state requirement”).
Procedure:
-
Run lint across the workspace:
Terminal window nx run-many -t lint -
Tell a rule violation apart from a plugin crash. If a project fails with a thrown error instead of rule findings - a
TypeErrorsuch ascontext.getAncestors is not a function, aCould not find "<rule>" in plugin "<name>"/couldn't find the config "<name>" to extend from, or a plugin that fails to load - the plugin predates ESLint v9; this is not a changed preset default. Do NOT disable the rule (that silently drops its coverage); update the plugin instead (section 6), then re-run lint and continue. -
For each rule that now reports errors (findings, not a thrown error):
- If the rule ID is NOT in the user’s explicit list, it came from a changed preset default. Disable it in the relevant flat config with a short comment explaining why.
- If the rule ID IS in the user’s explicit list, the user chose it. Leave it as-is and report it in your handoff summary.
// Disable a rule that a changed preset default newly enabled (eslint.config.mjs).export default [ ...baseConfig, { files: ['**/*.ts'], rules: { // Newly enabled by the ESLint v9 recommended set; was not enforced before the upgrade. 'no-unused-expressions': 'off', }, },];Action items:
- Run lint and collect every newly reported rule.
- Treat a plugin crash (a thrown error, not rule findings) as a version incompatibility: update the plugin (section 6), never disable its rules to silence it.
- Disable preset-originated rules that the user did not configure.
- Never disable or weaken a rule the user explicitly configured.
- Never edit source files to satisfy a newly enabled rule.
<fail_if note=“if you cannot reach a passing state without editing source or disabling a user-configured rule, stop and report”> You cannot make lint pass without either editing source files or disabling a rule the user explicitly configured. Write status: failed and explain which rule and project in your summary. Do not guess. </fail_if>
5. Fix removed output formatters
Section titled “5. Fix removed output formatters”Search pattern: the format option on lint targets (forwarded in <advisory_context>).
What changed: ESLint v9 removed several built-in output formatters. The built-ins that remain are stylish, html, json, and json-with-metadata. Removed: compact, codeframe, unix, visualstudio, table, checkstyle, jslint-xml, junit, tap.
Fix: switch the target to a built-in that remains, or install the matching community package and reference it by its package name.
# Example: keep junit output by installing the community formatter package.npm install --save-dev eslint-formatter-junit// project.json (reference the community formatter by package name)"lint": { "executor": "@nx/eslint:lint", "options": { "format": "eslint-formatter-junit" }}Action items:
- For each flagged target, switch to a remaining built-in formatter or a community package.
- When using a community package, add it to
devDependencies.
6. Other ESLint v9 runtime breaking changes
Section titled “6. Other ESLint v9 runtime breaking changes”Search pattern: lint executor options, run-commands invoking eslint, and local rule/plugin source.
- Removed CLI flags and executor options:
--rulesdir,--ext, and--resolve-plugins-relative-towere removed. The matching@nx/eslint:lintoptions (rulesdir,resolvePluginsRelativeTo,ignorePath) are not supported for flat config. Move file targeting into the config via thefilesandignoreskeys. - No eslintrc auto-merge: flat config does not merge
.eslintrc.*files found up the tree. Every setting must live ineslint.config.*. - Third-party plugins that predate ESLint v9: an installed ESLint plugin that was not updated for v9 breaks at lint time - its rules call the removed
contextAPIs below, or it only ships an eslintrc config that no longer loads. This surfaces as a thrown error (see section 4), not a new rule violation. List the installed plugins frompackage.json(dependencies/devDependenciesmatchingeslint-plugin-*or@<scope>/eslint-plugin-*), and for each confirm its version supports ESLint v9 (its changelog, or thatpeerDependencies.eslintallows>=9). Update any that do not, and prefer the plugin’s flat entry point where it ships one (for exampleeslint-plugin-cypress/flat). Update the plugin rather than disabling its rules. - Local rule API moved to
SourceCode(only relevant if the workspace authors its own rules):context.getScope()tosourceCode.getScope(node)context.getAncestors()tosourceCode.getAncestors(node)context.getDeclaredVariables()tosourceCode.getDeclaredVariables(node)context.markVariableAsUsed(name)tosourceCode.markVariableAsUsed(name, node)context.getSource()tosourceCode.getText()context.parserServicestosourceCode.parserServices
- Stricter rule schema: a custom rule that accepts options must declare
meta.schemain v9.
Action items:
- Remove unsupported CLI flags and executor options; move targeting into
files/ignores. - Update any installed third-party ESLint plugin that does not yet support ESLint v9, preferring its flat entry point; do not disable its rules to work around a load error.
- Update local rules to the
SourceCodeAPI and addmeta.schemawhere required.
Post-Migration Verification
Section titled “Post-Migration Verification”-
Clear the inference cache so renamed configs are re-detected:
Terminal window nx reset -
Confirm lint passes across the workspace:
Terminal window nx run-many -t lint -
Spot-check that a converted project resolves its config:
Terminal window npx eslint --print-config <a-file-in-the-project> -
Confirm no
.eslintrc.*files remain unless one was intentionally kept.
References
Section titled “References”- ESLint configuration files (flat config): https://eslint.org/docs/latest/use/configure/configuration-files
- Migrate to ESLint v9.0.0: https://eslint.org/docs/latest/use/migrate-to-9.0.0
- typescript-eslint configs: https://typescript-eslint.io/users/configs
- Nx ESLint plugin: https://nx.dev/nx-api/eslint
update-23-1-0-remove-removed-typescript-eslint-extension-rules
Section titled “update-23-1-0-remove-removed-typescript-eslint-extension-rules”Version: 23.1.0-beta.5
Handle typescript-eslint rules removed in v8 in ESLint flat configs, since referencing a removed rule stops the config from loading. Deletes the removed formatting/extension rules (e.g. @typescript-eslint/no-extra-semi) and rewrites the safe 1:1 renames (no-throw-literal -> only-throw-error, no-useless-template-literals -> no-unnecessary-template-expression) to preserve enforcement.
update-23-1-0-migrate-ban-types-rule
Section titled “update-23-1-0-migrate-ban-types-rule”Version: 23.1.0-beta.5
Migrate the removed @typescript-eslint/ban-types rule to its v8 successors (no-empty-object-type, no-unsafe-function-type, no-wrapper-object-types), whose options do not map 1:1 - so it is driven by an AI prompt rather than a deterministic codemod.
23.1.0-package-updates
Section titled “23.1.0-package-updates”Version: 23.1.0-beta.0
Packages
Section titled “Packages”The following packages will be updated:
| Name | Version | Always add to package.json |
|---|---|---|
eslint | ^9.8.0 | Updated only |
typescript-eslint | ^8.40.0 | Updated only |
@typescript-eslint/eslint-plugin | ^8.40.0 | Updated only |
@typescript-eslint/parser | ^8.40.0 | Updated only |
@typescript-eslint/utils | ^8.40.0 | Updated only |
@typescript-eslint/type-utils | ^8.40.0 | Updated only |
@typescript-eslint/rule-tester | ^8.40.0 | Updated only |
@typescript-eslint/scope-manager | ^8.40.0 | Updated only |
@typescript-eslint/typescript-estree | ^8.40.0 | Updated only |
eslint-config-prettier | ^10.0.0 | Updated only |
23.1.0-typescript-eslint-package-updates
Section titled “23.1.0-typescript-eslint-package-updates”Version: 23.1.0-beta.0
Packages
Section titled “Packages”The following packages will be updated:
| Name | Version | Always add to package.json |
|---|---|---|
typescript-eslint | ^8.58.0 | Updated only |
@typescript-eslint/eslint-plugin | ^8.58.0 | Updated only |
@typescript-eslint/parser | ^8.58.0 | Updated only |
@typescript-eslint/utils | ^8.58.0 | Updated only |
@typescript-eslint/rule-tester | ^8.58.0 | Updated only |
@typescript-eslint/scope-manager | ^8.58.0 | Updated only |
@typescript-eslint/typescript-estree | ^8.58.0 | Updated only |
23.1.0-@typescript-eslint-package-updates
Section titled “23.1.0-@typescript-eslint-package-updates”Version: 23.1.0-beta.0
Packages
Section titled “Packages”The following packages will be updated:
| Name | Version | Always add to package.json |
|---|---|---|
typescript-eslint | ^8.58.0 | Updated only |
@typescript-eslint/eslint-plugin | ^8.58.0 | Updated only |
@typescript-eslint/parser | ^8.58.0 | Updated only |
@typescript-eslint/utils | ^8.58.0 | Updated only |
@typescript-eslint/rule-tester | ^8.58.0 | Updated only |
@typescript-eslint/scope-manager | ^8.58.0 | Updated only |
@typescript-eslint/typescript-estree | ^8.58.0 | Updated only |
23.0.x
Section titled “23.0.x”rewrite-eslint-internal-subpath-imports
Section titled “rewrite-eslint-internal-subpath-imports”Version: 23.0.0-beta.17
Rewrites @nx/eslint/src/* subpath imports now that the ./src/* subpath is no longer exposed by @nx/eslint’s exports map. Named imports/exports of public symbols are routed to @nx/eslint and the rest to the new @nx/eslint/internal entry; require, dynamic import and jest.mock calls reference the whole module and are routed to @nx/eslint/internal.
update-23-0-0-migrate-create-nodes-v2-import
Section titled “update-23-0-0-migrate-create-nodes-v2-import”Version: 23.0.0-beta.24
Rename imports of createNodesV2 from @nx/eslint/plugin to the canonical createNodes export.
Rename createNodesV2 imports to createNodes
Section titled “Rename createNodesV2 imports to createNodes”@nx/eslint renamed its primary inferred-plugin export from createNodesV2 to createNodes. The createNodesV2 name is preserved as a deprecated alias for now, but new code should use createNodes.
This migration scans every .ts, .tsx, .cts, and .mts file in your workspace and rewrites named imports and re-exports of createNodesV2 from @nx/eslint/plugin to createNodes.
Sample Code Changes
Section titled “Sample Code Changes”Before
Section titled “Before”import { createNodesV2 } from '@nx/eslint/plugin';import { createNodes } from '@nx/eslint/plugin';Aliases are preserved (createNodesV2 as cn becomes createNodes as cn), and if a file already imports both names ({ createNodes, createNodesV2 }) the redundant binding is dropped.
What is not rewritten
Section titled “What is not rewritten”Only static import/export named bindings from @nx/eslint/plugin are rewritten. Namespace imports, dynamic import(...), require(...) destructuring, and property access such as plugin.createNodesV2 are left untouched — they keep working through the createNodesV2 runtime alias. Update those by hand if you want to drop the deprecated name everywhere.
22.7.x
Section titled “22.7.x”update-executor-lint-inputs
Section titled “update-executor-lint-inputs”Version: 22.7.0-beta.12
Add missing inputs to @nx/eslint:lint executor target defaults
21.5.x
Section titled “21.5.x”21.5.0-typescript-eslint-package-updates
Section titled “21.5.0-typescript-eslint-package-updates”Version: 21.5.0-beta.2
Packages
Section titled “Packages”The following packages will be updated:
| Name | Version | Always add to package.json |
|---|---|---|
typescript-eslint | ^8.40.0 | Updated only |
@typescript-eslint/eslint-plugin | ^8.40.0 | Updated only |
@typescript-eslint/parser | ^8.40.0 | Updated only |
@typescript-eslint/utils | ^8.40.0 | Updated only |
@typescript-eslint/rule-tester | ^8.40.0 | Updated only |
@typescript-eslint/scope-manager | ^8.40.0 | Updated only |
@typescript-eslint/typescript-estree | ^8.40.0 | Updated only |
21.5.0-@typescript-eslint-package-updates
Section titled “21.5.0-@typescript-eslint-package-updates”Version: 21.5.0-beta.2
Packages
Section titled “Packages”The following packages will be updated:
| Name | Version | Always add to package.json |
|---|---|---|
typescript-eslint | ^8.40.0 | Updated only |
@typescript-eslint/eslint-plugin | ^8.40.0 | Updated only |
@typescript-eslint/parser | ^8.40.0 | Updated only |
@typescript-eslint/utils | ^8.40.0 | Updated only |
@typescript-eslint/rule-tester | ^8.40.0 | Updated only |
@typescript-eslint/scope-manager | ^8.40.0 | Updated only |
@typescript-eslint/typescript-estree | ^8.40.0 | Updated only |
21.2.x
Section titled “21.2.x”21.2.0-typescript-eslint-package-updates
Section titled “21.2.0-typescript-eslint-package-updates”Version: 21.2.0-beta.0
Packages
Section titled “Packages”The following packages will be updated:
| Name | Version | Always add to package.json |
|---|---|---|
typescript-eslint | ^8.29.0 | Updated only |
@typescript-eslint/eslint-plugin | ^8.29.0 | Updated only |
@typescript-eslint/parser | ^8.29.0 | Updated only |
@typescript-eslint/utils | ^8.29.0 | Updated only |
@typescript-eslint/rule-tester | ^8.29.0 | Updated only |
@typescript-eslint/scope-manager | ^8.29.0 | Updated only |
@typescript-eslint/typescript-estree | ^8.29.0 | Updated only |
21.2.0-@typescript-eslint-package-updates
Section titled “21.2.0-@typescript-eslint-package-updates”Version: 21.2.0-beta.0
Packages
Section titled “Packages”The following packages will be updated:
| Name | Version | Always add to package.json |
|---|---|---|
typescript-eslint | ^8.29.0 | Updated only |
@typescript-eslint/eslint-plugin | ^8.29.0 | Updated only |
@typescript-eslint/parser | ^8.29.0 | Updated only |
@typescript-eslint/utils | ^8.29.0 | Updated only |
@typescript-eslint/rule-tester | ^8.29.0 | Updated only |
@typescript-eslint/scope-manager | ^8.29.0 | Updated only |
@typescript-eslint/typescript-estree | ^8.29.0 | Updated only |