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:
# Install plugin capability npx nx add @nx/plugin # Generate a new 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:
// Generator function signature
export async function libraryWithReadmeGenerator(
tree: Tree,
options: LibraryWithReadmeGeneratorSchema
) {
// Generator implementation
}
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}`;
// Create project configuration
addProjectConfiguration(tree, options.name, {
root: projectRoot,
projectType: 'library',
sourceRoot: `${projectRoot}/src`,
targets: {},
});
// Generate files from templates
generateFiles(
tree,
path.join(__dirname, 'files'),
projectRoot,
options
);
// Format generated files
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:
- •
.templatesuffix is removed during generation - •
__name__is replaced with actual values
Running Generators
Dry Run Mode:
# Test generator without creating files npx nx g my-plugin:library-with-readme mylib --dry-run
Actual Execution:
# Create files npx nx g my-plugin:library-with-readme mylib
With Options:
# Pass custom 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';
// Generate files from templates
generateFiles(tree, templatePath, targetPath, variables);
// Read project config
const config = readProjectConfiguration(tree, projectName);
// Update project config
updateProjectConfiguration(tree, projectName, updatedConfig);
Project Management
import {
addProjectConfiguration,
removeProjectConfiguration,
getProjects
} from '@nx/devkit';
// Add new project
addProjectConfiguration(tree, name, config);
// Remove project
removeProjectConfiguration(tree, name);
// Get all projects
const projects = getProjects(tree);
Dependency Management
import {
addDependenciesToPackageJson,
removeDependenciesFromPackageJson
} from '@nx/devkit';
// Add dependencies
addDependenciesToPackageJson(
tree,
{ 'lodash': '^4.17.21' }, // dependencies
{ '@types/lodash': '^4.14.0' } // devDependencies
);
String Utilities
import { names } from '@nx/devkit';
const result = names('my-awesome-lib');
// {
// name: 'my-awesome-lib',
// className: 'MyAwesomeLib',
// propertyName: 'myAwesomeLib',
// constantName: 'MY_AWESOME_LIB',
// fileName: '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:
// Good: Use Nx utilities
import { updateJson } from '@nx/devkit';
updateJson(tree, 'package.json', (json) => {
json.scripts = { ...json.scripts, test: 'jest' };
return json;
});
// Bad: Manual file manipulation
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) {
// ... generator logic
// Format all modified files
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
) {
// Generate base library
await libraryGenerator(tree, {
name: options.name,
directory: 'libs'
});
// Add custom files
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) {
// Always generate base files
generateFiles(tree, baseFilesPath, projectRoot, options);
// Conditionally generate test files
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);
// Add new target
projectConfig.targets.myTarget = {
executor: 'nx:run-commands',
options: {
command: 'echo "Hello"'
}
};
updateProjectConfiguration(tree, options.project, projectConfig);
}
Community and Support
Get Help
- •Discord: Join Nx Community
- •Enterprise Support: Nx Enterprise
- •Plugin Registry: Browse Plugins
Contribute
- •Share Your Plugin: Publish to npm and Plugin Registry
- •Open Source: Contribute to Nx
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.