Tutorial 7/8: Reduce configuration with plugins Help me reduce configuration boilerplate in my Nx workspace.
Use my existing workspace and projects for hands-on examples.
First, consolidate shared task config into in . Then show me how to use Nx plugins to automatically infer tasks from my tooling (Vite, Jest, ESLint, etc.) so I don't need to configure each project manually.
Run to see where each task's configuration comes from.
Stay on-topic: only teach what's covered on this page. Do not introduce concepts from later tutorials.
Tutorial: https://canary.nx.dev/docs/getting-started/tutorials/reducing-configuration-boilerplate.md
Help me reduce configuration boilerplate in my Nx workspace.
Use my existing workspace and projects for hands-on examples.
First, consolidate shared task config into in . Then show me how to use Nx plugins to automatically infer tasks from my tooling (Vite, Jest, ESLint, etc.) so I don't need to configure each project manually.
Run to see where each task's configuration comes from.
Stay on-topic: only teach what's covered on this page. Do not introduce concepts from later tutorials.
Tutorial: https://canary.nx.dev/docs/getting-started/tutorials/reducing-configuration-boilerplate.md
Help me reduce configuration boilerplate in my Nx workspace.
Use my existing workspace and projects for hands-on examples.
First, consolidate shared task config into in . Then show me how to use Nx plugins to automatically infer tasks from my tooling (Vite, Jest, ESLint, etc.) so I don't need to configure each project manually.
Run to see where each task's configuration comes from.
Stay on-topic: only teach what's covered on this page. Do not introduce concepts from later tutorials.
Tutorial: https://canary.nx.dev/docs/getting-started/tutorials/reducing-configuration-boilerplate.md
Manually configuring tasks, caching, inputs, and outputs for every project works, but it doesn't scale to dozens or hundreds of projects. Nx provides two mechanisms to reduce this boilerplate: targetDefaults for shared configuration and plugins for automatic task inference.
The examples below use Vite and Vitest, but the concepts apply to any tool. Substitute your own build and test commands as needed.
The scaling problem
Section titled “The scaling problem”Consider a workspace with 20 libraries, each with verbose task configuration:
{ "scripts": { "build": "vite build", "test": "vitest run", }, "nx": { "targets": { "build": { "cache": true, "dependsOn": ["^build"], "inputs": [ "{projectRoot}/src/**/*", "{projectRoot}/vite.config.ts", "{projectRoot}/tsconfig.json", ], "outputs": ["{projectRoot}/dist"], }, "test": { "cache": true, "inputs": ["{projectRoot}/src/**/*", "{projectRoot}/vitest.config.ts"], "outputs": ["{projectRoot}/coverage"], }, }, },}{ "targets": { "build": { "command": "vite build", "cache": true, "dependsOn": ["^build"], "inputs": [ "{projectRoot}/src/**/*", "{projectRoot}/vite.config.ts", "{projectRoot}/tsconfig.json", ], "outputs": ["{projectRoot}/dist"], }, "test": { "command": "vitest run", "cache": true, "inputs": ["{projectRoot}/src/**/*", "{projectRoot}/vitest.config.ts"], "outputs": ["{projectRoot}/coverage"], }, },}That's a lot of repetition across 20 projects. And every time you change the Vite output directory, you'd need to update all of them.
Step 1: Target defaults
Section titled “Step 1: Target defaults”Move shared configuration to nx.json so projects inherit defaults:
{ "targetDefaults": { "build": { "cache": true, "dependsOn": ["^build"], }, "test": { "cache": true, }, },}Now each project only needs to specify what's unique:
{ "scripts": { "build": "vite build", "test": "vitest run", }, "nx": { "targets": { "build": { "inputs": ["{projectRoot}/src/**/*", "{projectRoot}/vite.config.ts"], "outputs": ["{projectRoot}/dist"], }, "test": { "inputs": ["{projectRoot}/src/**/*", "{projectRoot}/vitest.config.ts"], "outputs": ["{projectRoot}/coverage"], }, }, },}{ "targets": { "build": { "command": "vite build", "inputs": ["{projectRoot}/src/**/*", "{projectRoot}/vite.config.ts"], "outputs": ["{projectRoot}/dist"], }, "test": { "command": "vitest run", "inputs": ["{projectRoot}/src/**/*", "{projectRoot}/vitest.config.ts"], "outputs": ["{projectRoot}/coverage"], }, },}Better, but you still repeat inputs and outputs across projects with the same tooling.
Step 2: Nx plugins (inferred tasks)
Section titled “Step 2: Nx plugins (inferred tasks)”Nx plugins can read your existing tooling configuration files, like vite.config.ts, jest.config.ts, or eslint.config.mjs, and automatically create tasks with the correct caching settings. No project.json needed.
Adding a plugin
Section titled “Adding a plugin”Try adding a plugin to your workspace:
nx add @nx/viteThis installs @nx/vite and registers it in nx.json. Some plugins may also need nx sync to update workspace configuration files (e.g., TypeScript project references). See maintain TypeScript monorepos for details.
After installing, check what tasks were inferred for one of your projects:
nx show project my-appYou should see tasks like build, test, and serve that were automatically created from your vite.config.ts file.
The plugin reads your Vite configuration and sets up correct caching, inputs, and outputs without any manual configuration.
The plugin is registered in nx.json:
{ "plugins": [ { "plugin": "@nx/vite/plugin", "options": { "buildTargetName": "build", "testTargetName": "test", "serveTargetName": "serve", }, }, ],}Now, any project with a vite.config.ts automatically gets build, test, and serve tasks, with correct inputs, outputs, and caching, without any project.json configuration.
Seeing what's inferred
Section titled “Seeing what's inferred”Use nx show project to see all tasks for a project, including where they come from:
nx show project my-libmy-lib
Root: packages/my-lib
Type:library
Targets
build
vite build
Cacheablelint
eslint .
Cacheabletest
vitest run
Cacheable
The project details view shows each task's source (plugin, targetDefaults, or project.json) and its computed settings.
How the configuration cascade works
Section titled “How the configuration cascade works”Task configuration can come from three sources, applied in this order:
- Plugin-inferred: automatic from tooling config (lowest priority)
- targetDefaults: shared defaults in
nx.json - Project-level: explicit
project.jsonorpackage.jsonconfig (highest priority)
Each layer can override the previous one. This means you can use plugins for sensible defaults and only add project-level config when a project needs something different.
This is optional
Section titled “This is optional”Plugins are optional. The explicit task configuration from Configuring Tasks works perfectly well. Use plugins when:
- You have many projects with the same tooling (Vite, Jest, ESLint, etc.)
- You want caching configured automatically with correct
inputs/outputs - You prefer minimal configuration files
Stick with explicit configuration when:
- You have unique build setups that plugins don't cover
- You want full control over every task detail
- Your team prefers explicit over implicit
Learn more
Section titled “Learn more”- Inferred tasks: how plugins detect and configure tasks
- Nx plugins: the full plugin system
- Reduce repetitive configuration: step-by-step guide
- Extending Nx: create your own plugins