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
Generators automate file and project scaffolding with repeatable conventions. They use the Tree API for filesystem operations and schema-driven options for type-safe inputs.
Basic structure:
import { Tree, formatFiles, generateFiles, joinPathSegments } from '@nx/devkit';
import * as path from 'path';
import { GeneratorSchema } from './schema';
export async function myGenerator(tree: Tree, options: GeneratorSchema) {
const projectRoot = `libs/${options.name}`;
generateFiles(
tree,
path.join(__dirname, 'files'),
projectRoot,
options
);
await formatFiles(tree);
}
export default myGenerator;Write files:
tree.write('path/to/file.ts', 'content');Read files:
const content = tree.read('path/to/file.ts', 'utf-8');Check existence:
if (tree.exists('path/to/file.ts')) {
// file exists
}Delete files:
tree.delete('path/to/file.ts');List files:
tree.children('libs/my-lib/src');Update JSON:
import { updateJson } from '@nx/devkit';
updateJson(tree, 'package.json', (json) => {
json.scripts.build = 'nx build';
return json;
});Read project:
import { readProjectConfiguration } from '@nx/devkit';
const project = readProjectConfiguration(tree, 'my-lib');
console.log(project.root); // libs/my-lib
console.log(project.targets); // { build: {...}, test: {...} }Update project:
import { updateProjectConfiguration } from '@nx/devkit';
const project = readProjectConfiguration(tree, 'my-lib');
project.targets.lint = {
executor: '@nx/linter:eslint',
options: {
lintFilePatterns: [`${project.root}/**/*.ts`]
}
};
updateProjectConfiguration(tree, 'my-lib', project);Add new project:
import { addProjectConfiguration } from '@nx/devkit';
addProjectConfiguration(tree, 'my-lib', {
root: 'libs/my-lib',
projectType: 'library',
sourceRoot: 'libs/my-lib/src',
targets: {
build: {
executor: '@nx/js:tsc',
outputs: ['{options.outputPath}'],
options: {
outputPath: 'dist/libs/my-lib',
main: 'libs/my-lib/src/index.ts',
tsConfig: 'libs/my-lib/tsconfig.lib.json'
}
}
}
});Directory structure:
src/generators/my-generator/
├── generator.ts
├── schema.json
├── schema.d.ts
└── files/
├── src/
│ └── index.ts.template
├── README.md.template
└── __name__/
└── __fileName__.ts.templateTemplate files use EJS syntax:
// files/src/index.ts.template
export * from './<%= name %>';
export function <%= propertyName %>(): string {
return '<%= name %> works!';
}File name substitution:
__name__ → replaced with options.name__fileName__ → replaced with custom option values__directory__ → replaced with options.directoryGenerate from templates:
import { generateFiles } from '@nx/devkit';
import * as path from 'path';
generateFiles(
tree,
path.join(__dirname, 'files'),
`libs/${options.name}`,
{
...options,
template: '', // removes .template extension
propertyName: names(options.name).propertyName
}
);schema.json:
{
"$schema": "http://json-schema.org/schema",
"cli": "nx",
"id": "my-generator",
"title": "My Generator",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Library name",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What name would you like to use?"
},
"directory": {
"type": "string",
"description": "Directory where the library is placed"
},
"tags": {
"type": "string",
"description": "Tags to add to project (comma-delimited)"
},
"skipFormat": {
"type": "boolean",
"description": "Skip formatting files",
"default": false
}
},
"required": ["name"]
}schema.d.ts:
export interface GeneratorSchema {
name: string;
directory?: string;
tags?: string;
skipFormat?: boolean;
}Name transformations:
import { names } from '@nx/devkit';
const result = names('my-lib');
// {
// name: 'my-lib',
// className: 'MyLib',
// propertyName: 'myLib',
// constantName: 'MY_LIB',
// fileName: 'my-lib'
// }Path utilities:
import { joinPathSegments, normalizePath } from '@nx/devkit';
const fullPath = joinPathSegments('libs', options.name, 'src');
const normalized = normalizePath('libs\\my-lib\\src'); // libs/my-lib/srcDependency management:
import { addDependenciesToPackageJson } from '@nx/devkit';
addDependenciesToPackageJson(
tree,
{ '@my-org/shared': '^1.0.0' },
{ '@my-org/build-tools': '^1.0.0' }
);Dry run validation:
npx nx g @my-org/my-plugin:library test --dry-runUnit test example:
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Tree, readProjectConfiguration } from '@nx/devkit';
import { libraryGenerator } from './generator';
describe('library generator', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
});
it('should create library', async () => {
await libraryGenerator(tree, { name: 'test' });
const config = readProjectConfiguration(tree, 'test');
expect(config.root).toBe('libs/test');
expect(tree.exists('libs/test/src/index.ts')).toBeTruthy();
expect(tree.exists('libs/test/README.md')).toBeTruthy();
});
it('should add tags', async () => {
await libraryGenerator(tree, { name: 'test', tags: 'scope:shared,type:util' });
const config = readProjectConfiguration(tree, 'test');
expect(config.tags).toEqual(['scope:shared', 'type:util']);
});
});readProjectConfiguration before updateProjectConfigurationformatFiles(tree) after generation