Documentation Index
Fetch the complete documentation index at: https://mintlify.com/nrwl/nx/llms.txt
Use this file to discover all available pages before exploring further.
An Nx plugin is an npm package that extends Nx with generators, executors, and project graph logic. Plugins can be used locally within a single workspace or published to npm and shared across multiple repos.
Scaffolding a plugin
New workspace
Existing workspace
npx create-nx-plugin my-plugin
This creates a new Nx workspace with a plugin already configured.npx nx add @nx/plugin
npx nx g @nx/plugin:plugin tools/my-plugin
Plugin structure
After generation, the plugin folder contains:
tools/my-plugin/
├── src/
│ ├── generators/
│ │ └── my-generator/
│ │ ├── generator.ts
│ │ ├── generator.spec.ts
│ │ ├── schema.d.ts
│ │ └── schema.json
│ ├── executors/
│ │ └── my-executor/
│ │ ├── executor.ts
│ │ ├── executor.spec.ts
│ │ ├── schema.d.ts
│ │ └── schema.json
│ └── index.ts ← plugin entry point
├── generators.json ← registry of all generators
├── executors.json ← registry of all executors
├── package.json
└── tsconfig.json
The index.ts file is the plugin entry point registered in nx.json. Export createNodesV2 and createDependencies from this file to extend the project graph.
Adding generators and executors
# Add a generator
npx nx g @nx/plugin:generator tools/my-plugin/src/generators/application
# Add an executor
npx nx g @nx/plugin:executor tools/my-plugin/src/executors/build
See local generators and local executors for detailed implementation guidance.
Inferring tasks with createNodesV2
The most powerful feature of a plugin is the ability to automatically create tasks for any project that has a particular config file. This removes the need for users to manually define targets in project.json.
Export createNodesV2 from your plugin’s index.ts:
// tools/my-plugin/src/index.ts
import {
CreateNodesContextV2,
CreateNodesV2,
TargetConfiguration,
createNodesFromFiles,
joinPathFragments,
} from '@nx/devkit';
import { readdirSync, readFileSync } from 'fs';
import { dirname, join, resolve } from 'path';
export interface MyPluginOptions {
buildTargetName?: string;
devTargetName?: string;
}
// Glob pattern that identifies which files trigger this plugin
const configGlob = '**/my-tool.config.{js,ts}';
export const createNodesV2: CreateNodesV2<MyPluginOptions> = [
configGlob,
async (configFiles, options, context) => {
return await createNodesFromFiles(
(configFile, options, context) =>
createNodesInternal(configFile, options ?? {}, context),
configFiles,
options,
context
);
},
];
async function createNodesInternal(
configFilePath: string,
options: MyPluginOptions,
context: CreateNodesContextV2
) {
const projectRoot = dirname(configFilePath);
// Only create a project if a package.json or project.json exists alongside the config
const siblingFiles = readdirSync(join(context.workspaceRoot, projectRoot));
if (
!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')
) {
return {};
}
const buildTarget: TargetConfiguration = {
command: 'my-tool build',
options: { cwd: projectRoot },
cache: true,
inputs: [
`{projectRoot}/my-tool.config.js`,
`{projectRoot}/src/**/*`,
{ externalDependencies: ['my-tool'] },
],
outputs: ['{projectRoot}/dist'],
};
return {
projects: {
[projectRoot]: {
targets: {
[options.buildTargetName ?? 'build']: buildTarget,
},
},
},
};
}
Registering your plugin in nx.json
Add the plugin to the plugins array in nx.json. You can pass options that are forwarded to your createNodesV2 function:
// nx.json
{
"plugins": [
{
"plugin": "@myorg/my-plugin",
"options": {
"buildTargetName": "build",
"devTargetName": "dev"
}
}
]
}
For a local plugin (not published to npm), reference the path directly:
{
"plugins": [
{
"plugin": "./tools/my-plugin/src/index.ts"
}
]
}
Creating an init generator
If you create a generator named init, Nx runs it automatically when someone installs your plugin with nx add. Use it to register the plugin in nx.json:
// tools/my-plugin/src/generators/init/generator.ts
import { formatFiles, readNxJson, Tree, updateNxJson } from '@nx/devkit';
export async function initGenerator(tree: Tree, options: {}) {
const nxJson = readNxJson(tree) ?? {};
const hasPlugin = nxJson.plugins?.some((p) =>
typeof p === 'string' ? p === '@myorg/my-plugin' : p.plugin === '@myorg/my-plugin'
);
if (!hasPlugin) {
nxJson.plugins = [
...(nxJson.plugins ?? []),
{
plugin: '@myorg/my-plugin',
options: {
buildTargetName: 'build',
devTargetName: 'dev',
},
},
];
}
updateNxJson(tree, nxJson);
await formatFiles(tree);
}
export default initGenerator;
Publishing a plugin to npm
Update package.json
Make sure your package.json has the correct name, version, main, generators, and executors fields:{
"name": "@myorg/my-plugin",
"version": "1.0.0",
"main": "./src/index.js",
"generators": "./generators.json",
"executors": "./executors.json"
}
Publish to npm
npm publish dist/tools/my-plugin --access public
Install in another workspace
If you have an init generator, it runs automatically.
The generated plugin comes with an e2e test suite that spins up a local Verdaccio registry, publishes your plugin, creates a fresh Nx workspace, and installs your plugin — exactly mirroring the real user experience.
Testing your plugin
The plugin generator creates an e2e project alongside your plugin. Run it with:
For unit testing the inferred task logic, use nx show project to inspect the computed project configuration:
nx show project my-app --json
In e2e tests you can assert on this output:
it('should infer build target', () => {
const projectDetails = JSON.parse(
execSync('nx show project my-app --json', {
cwd: projectDirectory,
}).toString()
);
expect(projectDetails.targets.build).toMatchObject({
cache: true,
executor: 'nx:run-commands',
outputs: ['{projectRoot}/dist'],
});
});