Skip to content

This guide describes advanced scenarios when it comes to updating Nx and the workspaces dependencies. It starts with a summary of the standard update process and continues with those advanced use cases.

The following steps are a summary of the standard update process. For more information on each step, please visit that page.

Step 1: Updating dependencies and generating migrations

Section titled “Step 1: Updating dependencies and generating migrations”

First, run the migrate command:

Terminal window
nx migrate

This performs the following changes:

  • Updates the versions of the relevant packages in the package.json file.
  • Generates a migrations.json if there are pending migrations.

The next step in the process involves using the migrate command to apply the migrations that were generated in the migrations.json file in the previous step. You can do so by running:

Terminal window
nx migrate --run-migrations

Without the agentic flow, Nx leaves all changes to your source code unstaged by default, so you can review and commit them yourself. With it, Nx commits each migration separately by default. For more information, see using an AI agent to apply migrations.

After you run all the migrations, you can remove migrations.json and commit any outstanding changes.

Migrating Jest, Cypress, ESLint, React, Angular, Next, and more is a difficult task. All the tools change at different rates, and they can conflict with each other. In addition, every workspace is different. Even though our goal is for you to update any version of Nx to a newer version of Nx in a single go, sometimes it doesn't work. The recommended process is to update, at most, one major version at a time.

Say you want to migrate from Nx 22.1.0 to Nx 23.0.0. The following steps are more likely to work comparing to nx migrate 23.0.0.

  • Run nx migrate 22.7.5 to update the latest version in the 22.x branch.
  • Run nx migrate --run-migrations.
  • Next, run nx migrate 23.0.0.
  • Run nx migrate --run-migrations.

When the target is more than one major version ahead of your installed version, nx migrate prompts you to choose how far to jump: migrate to the latest in your current major (recommended), step into the next major, or go directly to the target. To skip the prompt, use --multi-major-mode:

  • --multi-major-mode=gradual migrates to the smallest recommended step (typically the latest in your current major), then tells you to re-run to continue.
  • --multi-major-mode=direct migrates straight to the target.

The NX_MULTI_MAJOR_MODE environment variable is equivalent and takes precedence over a multiMajorMode value set in nx.json. In non-interactive environments there is no prompt - Nx warns that updating one major at a time is recommended and proceeds directly to the target.

While in most cases you want to be up to date with Nx and the dependencies it manages, sometimes you might need to stay on an older version of such a dependency. For example, you might want to update Nx to the latest version but keep Angular on v21.x.x and not update it to v22.x.x.

The --include flag controls which packages are updated: required (the target package and the packages it ships with), optional (the dependency updates those packages recommend), or all (the default). The interactive nx migrate flow prompts for this. Pass --include to set it ahead of time.

--include applies only when the target package opts into package selection. Nx and its official plugins do starting in Nx 23, and other plugin authors can opt in as well. For targets that don't, Nx uses all without prompting, and passing --include explicitly errors. Non-interactive runs also default to all.

When unsure, prefer --include=required and follow up with --include=optional. Updating only Nx and its plugins keeps the PR scope small and has less chance of introducing issues, which matters most in large workspaces. Use --include=all when you're okay with doing everything in one PR.

To skip the optional package updates, generate the migration with --include=required:

Terminal window
nx migrate --include=required

Only the target package and the packages it ships with are updated. The package.json and the migrations.json are generated without the optional dependency updates or the migrations that apply them, and you can catch up on those later.

Updating dependencies that are behind the versions Nx manages

Section titled “Updating dependencies that are behind the versions Nx manages”

Once you have skipped some optional updates, there'll come a time when you'll want to update those packages. Run nx migrate --include=optional to collect the optional dependency updates recommended for your installed version. It also collects the plugin migrations that apply an optional update or need one, which --include=required skips. It anchors to your installed version, so the target package must be installed and you can't migrate to a version higher than what's installed.

The catch-up can be scoped to a single plugin by naming it as the target. For example, nx migrate @nx/vite --include=optional collects only the optional updates @nx/vite recommends, such as vite itself.

When you run into problems running the nx migrate --run-migrations command, here are some solutions to break the process down into manageable steps.

Make changes easier to review by committing after each migration runs

Section titled “Make changes easier to review by committing after each migration runs”

Depending on the size of the update (e.g. migrating between major versions is likely to require more significant changes than migrating between feature releases), and the size of the workspace, the overall nx migrate process may generate a lot of changes which then need to be reviewed. Particularly if there are then manual changes which need to be made in addition to those made by nx migrate, it can make the associated PR harder to review because of not being able to distinguish between what was changed automatically and what was changed manually.

If you pass --create-commits to the --run-migrations command, Nx will automatically create a dedicated commit for each successfully completed migration, for example:

Terminal window
nx migrate --run-migrations --create-commits

Your git history will then look something like the following:

nx migrate --run-migrations --create-commits
git log
commit 8c862c780106ab8736985c01de1477309a403548
Author: YOUR_GIT_USERNAME <your_git_email@example.com>
Date: Thu Apr 14 18:35:44 2022 +0400
chore: [nx migration] name-of-the-second-migration-which-ran
commit eb83bca97927af26aae731a2cf51ad62cc75efa3
Author: YOUR_GIT_USERNAME <your_git_email@example.com>
Date: Thu Apr 14 18:35:44 2022 +0400
chore: [nx migration] name-of-the-first-migration-which-ran
etc

By default, nx will apply the prefix of chore: [nx migration] to each commit in order to clearly identify it, but you can also customize this prefix by passing --commit-prefix to the command:

Terminal window
nx migrate --run-migrations --create-commits --commit-prefix="chore(core): AUTOMATED - "
git log
commit 8c862c780106ab8736985c01de1477309a403548
Author: YOUR_GIT_USERNAME <your_git_email@example.com>
Date: Thu Apr 14 18:35:44 2022 +0400
chore(core): AUTOMATED - name-of-the-second-migration-which-ran
commit eb83bca97927af26aae731a2cf51ad62cc75efa3
Author: YOUR_GIT_USERNAME <your_git_email@example.com>
Date: Thu Apr 14 18:35:44 2022 +0400
chore(core): AUTOMATED - name-of-the-first-migration-which-ran
etc

Customizing which migrations run by altering migrations.json

Section titled “Customizing which migrations run by altering migrations.json”

For small projects, running all the migrations at once often succeeds without any issues. For large projects, more flexibility is sometimes needed, and this is where having the separation between generating the migrations to be run, and actually running them, really shines.

All you need to do is amend the JSON file in whatever way makes sense based on your circumstances, for example:

  • You may have to skip a migration.
  • You may want to run one migration at a time to address minor issues.
  • You may want to reorder migrations.
  • You may want to run the same migration multiple time if the process takes a long time and you had to rebase.

Because you can run nx migrate --run-migrations as many times as you want, you can achieve all of that by commenting out and reordering items in migrations.json. The migration process can take a long time, depending on the number of migrations, so it is useful to commit the migrations file with the partially-updated repo alongside any changes which were created by previously completed migrations.

You can even provide a custom location for the migrations file if you wish, you simply pass it to the --run-migrations option:

Terminal window
nx migrate --run-migrations=migrations.json

To run one migration from migrations.json on its own, pass its id to --run-migration:

Terminal window
nx migrate --run-migration=<package>:<migration-name>

A bare <migration-name> also works when it matches exactly one migration. Running the command again runs the migration again, which is also how you restart it after an interruption.

With the agentic flow enabled, Nx runs the migration's generator itself, if it has one, and starts an agent session only when the migration leaves a prompt to apply or changes to review. The session ends once that migration is done, without the final check of the projects a run touched.

If a --run-migrations run is active, Nx warns that the run doesn't record this migration, so continuing it later may apply the migration again. Nx refuses to start while another nx migrate process is working on a run.

Inside an AI agent's terminal, Nx prints the migration's prompt, or instructions to review its generator's changes, for that agent to follow. Pass --no-validate or --no-agentic to skip the review instructions.

Some migrations ship an AI prompt that an installed agent applies for you. The agentic flow is interactive by default, but you can control it with flags:

  • --agentic enables it and resolves the installed agent. Use --agentic=claude-code (or codex, opencode) to pin one, or --no-agentic to disable it.
  • --validate / --no-validate toggles the agent's review of generator-only migrations. It's on by default.
  • --final-validation / --no-final-validation toggles the agent's final check of the projects the run touched. It's on by default.

A non-interactive --agentic run warns and continues without the agent, since the agent session needs an interactive terminal. When the flow is enabled, Nx commits each migration separately by default so the agent can review an isolated diff.

To always use the agentic flow (or a specific agent) without passing the flag, set the agentic, validate, and finalValidation options in the migrate section of nx.json.

The command Nx prints to continue a stopped run looks like this:

Terminal window
nx migrate --run-migrations --agentic=claude-code --run-id=<run-id> --create-commits

It repeats the run's commit and install flags, since Nx doesn't continue a run with different ones. The run also keeps the validation settings it started with.

When you run nx migrate --run-migrations without --run-id while a run is active, Nx asks what to do with it in a terminal. With the agentic flow you can continue the run, and either way you can start fresh or leave it as it is. Without a terminal, Nx prints the run's state and the commands to continue it or start fresh, then exits with an error.

Starting fresh deletes the run's record and runs the whole plan again. Migrations the deleted run already applied stay applied, so running the plan applies them a second time. To start fresh without the prompt, pass the run's id:

Terminal window
nx migrate --run-migrations --start-fresh --run-id=<run-id>

To avoid passing the same flags on every run, set defaults in the migrate section of nx.json - createCommits, commitPrefix, include, multiMajorMode, agentic, validate, and finalValidation. A command-line flag always overrides the nx.json value, which in turn overrides the built-in Nx defaults.

Sometimes, you may want to use a different version of a package than what Nx recommends. To do that, specify the package and version:

Terminal window
nx migrate --to="jest@30.0.0,cypress@15.0.0"

By default, Nx uses currently installed packages to calculate what migrations need to run. To override them, override the version:

Terminal window
nx migrate --to="@nx/jest@12.0.0"

Updates are best done on a clean git history so that it can be easily reversed if something fails. We try our best to make sure migrations do not fail but if one does, please report it on GitHub.

If an update fails for any reason, you can revert it as you do any other set of changes:

Terminal window
git reset --hard # Reset any changes
git clean -fd # Delete newly added files and directories