Complete Nx plugin development toolkit: create custom generators, executors, and extend Nx workspaces with reusable automation
93
94%
Does it follow best practices?
Impact
92%
1.00xAverage score across 5 eval scenarios
Passed
No known issues
Start from scratch:
npx create-nx-plugin my-plugin
cd my-pluginThis creates:
Install plugin package:
npx nx add @nx/pluginGenerate plugin:
npx nx g @nx/plugin:plugin tools/my-pluginGenerated structure:
tools/my-plugin/
├── src/
│ ├── generators/
│ │ └── my-plugin/
│ │ ├── generator.ts
│ │ ├── schema.json
│ │ └── schema.d.ts
│ ├── executors/
│ └── index.ts
├── package.json
├── project.json
├── tsconfig.json
└── tsconfig.lib.jsonMinimal plugin:
my-plugin/
├── src/
│ ├── generators/
│ │ ├── generator-name/
│ │ │ ├── generator.ts
│ │ │ ├── schema.json
│ │ │ ├── schema.d.ts
│ │ │ └── files/ # Optional templates
│ │ │ └── __name__.txt.template
│ │ └── generators.json
│ ├── executors/
│ │ ├── executor-name/
│ │ │ ├── executor.ts
│ │ │ ├── schema.json
│ │ │ └── schema.d.ts
│ │ └── executors.json
│ └── index.ts
├── package.json
└── project.jsontools/my-plugin/package.json:
{
"name": "@my-org/my-plugin",
"version": "0.0.1",
"main": "./src/index.ts",
"generators": "./generators.json",
"executors": "./executors.json",
"dependencies": {
"@nx/devkit": "^22.0.0",
"tslib": "^2.3.0"
}
}generators.json:
{
"generators": {
"my-generator": {
"factory": "./src/generators/my-generator/generator",
"schema": "./src/generators/my-generator/schema.json",
"description": "Generate something useful"
}
}
}executors.json:
{
"executors": {
"my-executor": {
"implementation": "./src/executors/my-executor/executor",
"schema": "./src/executors/my-executor/schema.json",
"description": "Execute something useful"
}
}
}Enable package references:
package.json:{
"workspaces": [
"packages/*",
"tools/*"
]
}tsconfig.base.json:{
"compilerOptions": {
"paths": {
"@my-org/my-plugin": ["tools/my-plugin/src/index.ts"]
}
}
}tools/my-plugin/src/index.ts:export * from './generators/my-generator/generator';
export * from './executors/my-executor/executor';Unit tests:
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Tree, readProjectConfiguration } from '@nx/devkit';
import { myGenerator } from './generator';
describe('my-generator', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
});
it('should generate files', async () => {
await myGenerator(tree, { name: 'test' });
expect(tree.exists('libs/test/README.md')).toBeTruthy();
});
});E2E tests:
import { execSync } from 'child_process';
import { rmSync } from 'fs';
describe('my-plugin e2e', () => {
beforeAll(() => {
execSync('npx create-nx-workspace test-workspace --preset=empty --no-interactive');
});
afterAll(() => {
rmSync('test-workspace', { recursive: true, force: true });
});
it('should generate library', () => {
execSync('npx nx g @my-org/my-plugin:library test', {
cwd: 'test-workspace',
stdio: 'inherit'
});
});
});Local testing:
npm link
cd ../test-workspace
npm link @my-org/my-pluginRegistry publishing:
npm publish --access publicNx plugin registry: Submit to nx.dev/plugin-registry after publishing to npm.