| name | extending-nx-plugins |
| description | Comprehensive guide for creating and managing Nx plugins including generators, inferred tasks, migrations, and best practices for extending Nx workspaces |
Extending Nx with Plugins
Source: Nx Documentation - Extending Nx
Last Updated: December 2024
Nx Version: 22
Overview
Nx plugins extend the core functionality of Nx workspaces by leveraging Nx's task running and project graph capabilities. While Nx core handles task execution and workspace understanding, plugins enforce best practices, integrate tooling, and accelerate development workflows.
Core Nx Functionality:
- Task running and orchestration
- Project and task graph understanding
- Caching and optimization
Plugin Capabilities:
- Enforce organizational best practices
- Provide code generators
- Integrate tools seamlessly
- Configure inferred tasks
- Share solutions across teams
When to Create a Plugin
As your repository grows, you may need custom plugins for:
- Consistency Enforcement: Help coworkers follow best practices with custom code generators
- Configuration Management: Remove duplicate configuration with inferred tasks
- Multi-Repository Standards: Share presets and migrations across multiple monorepos
- Tool Integration: Integrate new tools or frameworks into Nx
- Community Contribution: Share your plugin with the broader Nx ecosystem
Creating a Plugin
New Workspace with Plugin
Create a new workspace with a plugin:
npx create-nx-plugin my-plugin
This scaffolds a complete workspace with:
- Plugin package structure
- Generator templates
- Testing setup
- Build configuration
Add Plugin to Existing Workspace
Add a plugin to an existing Nx workspace:
npx nx add @nx/plugin
npx nx g plugin tools/my-plugin
This creates:
- Plugin package in
tools/my-plugin
- Generator scaffolding
- Plugin registration files
Plugin Components
Code Generators
Generators automate code scaffolding and enforce patterns:
export async function libraryWithReadmeGenerator(
tree: Tree,
options: LibraryWithReadmeGeneratorSchema
) {
}
Key Operations:
- Create new projects and files
- Modify existing code
- Update configuration
- Enforce naming conventions
Inferred Tasks
Inferred tasks automatically configure build, test, and other targets based on project structure:
- Reduce manual configuration
- Ensure accurate caching
- Standardize task execution
Migrations
Migrations help keep projects in sync across updates:
- Update dependencies
- Refactor code patterns
- Modify configuration
- Maintain consistency
Presets
Presets provide starting points for new repositories:
- Organization-specific templates
- Pre-configured tooling
- Standard project structures
Creating Your First Generator
Generate a Generator
Create a new generator with the Nx CLI:
npx nx g generator my-plugin/src/generators/library-with-readme
This creates:
- Generator implementation file
- Schema definition
- Template files folder
- Registration in
generators.json
Generator Structure
Basic Generator Implementation:
import { Tree, addProjectConfiguration, generateFiles, formatFiles } from '@nx/devkit';
import * as path from 'path';
export async function libraryWithReadmeGenerator(
tree: Tree,
options: LibraryWithReadmeGeneratorSchema
) {
const projectRoot = `libs/${options.name}`;
addProjectConfiguration(tree, options.name, {
root: projectRoot,
projectType: 'library',
sourceRoot: `${projectRoot}/src`,
targets: {},
});
generateFiles(
tree,
path.join(__dirname, 'files'),
projectRoot,
options
);
await formatFiles(tree);
}
Key Functions:
addProjectConfiguration - Register new project
generateFiles - Create files from templates
formatFiles - Apply code formatting
Template Files
Templates use EJS syntax for variable injection:
Example README Template:
<!-- files/README.md.template -->
# <%= name %>
This was generated by the `library-with-readme` generator!
Template Variables:
<%= name %> - Inject generator options
<% if (condition) { %> - Conditional logic
<% for (item of items) { %> - Iteration
File Naming:
.template suffix is removed during generation
__name__ is replaced with actual values
Running Generators
Dry Run Mode:
npx nx g my-plugin:library-with-readme mylib --dry-run
Actual Execution:
npx nx g my-plugin:library-with-readme mylib
With Options:
npx nx g my-plugin:library-with-readme mylib --directory=shared --tags=utils
Helper Functions
The Nx Devkit provides utilities for common plugin operations:
File Operations
import {
generateFiles,
readProjectConfiguration,
updateProjectConfiguration,
joinPathFragments
} from '@nx/devkit';
generateFiles(tree, templatePath, targetPath, variables);
const config = readProjectConfiguration(tree, projectName);
updateProjectConfiguration(tree, projectName, updatedConfig);
Project Management
import {
addProjectConfiguration,
removeProjectConfiguration,
getProjects
} from '@nx/devkit';
addProjectConfiguration(tree, name, config);
removeProjectConfiguration(tree, name);
const projects = getProjects(tree);
Dependency Management
import {
addDependenciesToPackageJson,
removeDependenciesFromPackageJson
} from '@nx/devkit';
addDependenciesToPackageJson(
tree,
{ 'lodash': '^4.17.21' },
{ '@types/lodash': '^4.14.0' }
);
String Utilities
import { names } from '@nx/devkit';
const result = names('my-awesome-lib');
Prerequisites for Plugin Development
To effectively develop Nx plugins, you should understand:
- Nx Task Execution: How to run tasks and configure inferred tasks
- Code Generation: How to use Nx generators
- Project Graph: Understanding workspace dependencies
- TypeScript: Writing type-safe code
Plugin Use Cases
Organizational Best Practices
Problem: Teams need consistent project structure and standards.
Solution: Create generators that enforce:
- Naming conventions
- File organization
- Required documentation
- Testing setup
Tool Integration
Problem: New tool needs to work seamlessly with Nx.
Solution: Create plugin that provides:
- Configuration inference
- Task executors
- Code generators
- Project templates
Multi-Repository Consistency
Problem: Multiple monorepos need to stay in sync.
Solution: Provide:
- Shared presets
- Migration generators
- Common configuration
- Standardized workflows
Learning Resources
Official Tutorials
Enforce Best Practices Tutorial:
- Create organization-specific generators
- Enforce coding standards
- Implement custom linting rules
- View Tutorial
Tool Integration Tutorial:
- Integrate new build tools
- Create custom executors
- Configure task inference
- View Tutorial
Reference Documentation
Best Practices
1. Use Nx Devkit Utilities
Leverage built-in functions instead of manual file operations:
import { updateJson } from '@nx/devkit';
updateJson(tree, 'package.json', (json) => {
json.scripts = { ...json.scripts, test: 'jest' };
return json;
});
const content = tree.read('package.json').toString();
const json = JSON.parse(content);
json.scripts.test = 'jest';
tree.write('package.json', JSON.stringify(json));
2. Provide Clear Schema Definitions
Define generator options with clear descriptions:
{
"cli": "nx",
"id": "library-with-readme",
"description": "Generate a library with README",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Library name",
"$default": {
"$source": "argv",
"index": 0
}
},
"directory": {
"type": "string",
"description": "Directory where library is created"
}
},
"required": ["name"]
}
3. Test Your Generators
Write tests to ensure generator reliability:
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { libraryWithReadmeGenerator } from './generator';
describe('library-with-readme', () => {
it('should create README file', async () => {
const tree = createTreeWithEmptyWorkspace();
await libraryWithReadmeGenerator(tree, { name: 'test' });
expect(tree.exists('libs/test/README.md')).toBeTruthy();
});
});
4. Format Generated Code
Always format generated files for consistency:
import { formatFiles } from '@nx/devkit';
export async function myGenerator(tree: Tree, options: Schema) {
await formatFiles(tree);
}
Common Patterns
Composing Generators
Generators can call other generators:
import { libraryGenerator } from '@nx/js';
export async function enhancedLibraryGenerator(
tree: Tree,
options: Schema
) {
await libraryGenerator(tree, {
name: options.name,
directory: 'libs'
});
generateFiles(
tree,
path.join(__dirname, 'files'),
`libs/${options.name}`,
options
);
await formatFiles(tree);
}
Conditional File Generation
Generate files based on options:
export async function myGenerator(tree: Tree, options: Schema) {
generateFiles(tree, baseFilesPath, projectRoot, options);
if (options.includeTests) {
generateFiles(tree, testFilesPath, projectRoot, options);
}
await formatFiles(tree);
}
Updating Existing Projects
Modify existing project configuration:
import { updateProjectConfiguration, readProjectConfiguration } from '@nx/devkit';
export async function myGenerator(tree: Tree, options: Schema) {
const projectConfig = readProjectConfiguration(tree, options.project);
projectConfig.targets.myTarget = {
executor: 'nx:run-commands',
options: {
command: 'echo "Hello"'
}
};
updateProjectConfiguration(tree, options.project, projectConfig);
}
Community and Support
Get Help
Contribute
Next Steps
- Browse Plugin Registry: Find existing plugins that meet your needs
- Follow Tutorials: Complete the step-by-step guides for specific use cases
- Join Community: Collaborate with other plugin authors on Discord
- Consider Enterprise: Get dedicated support for custom plugin development
Summary
Nx plugins extend workspace capabilities by:
- Enforcing Standards: Custom generators ensure consistency
- Reducing Configuration: Inferred tasks eliminate boilerplate
- Integrating Tools: Seamless tool integration
- Enabling Reuse: Share solutions across projects and teams
Start with the built-in generator scaffold, leverage Nx Devkit utilities, and follow the official tutorials to build powerful workspace extensions.