0
# Plugin System
1
2
Comprehensive APIs for creating custom plugins that extend Nx's project detection, dependency analysis, and task configuration capabilities.
3
4
## Capabilities
5
6
### Plugin Interface
7
8
Core plugin interface for extending Nx functionality.
9
10
```typescript { .api }
11
/**
12
* Main plugin interface for extending Nx
13
*/
14
interface NxPlugin {
15
/** Plugin name */
16
name: string;
17
/** Create project configurations from config files */
18
createNodes?: CreateNodes;
19
/** Create additional project dependencies */
20
createDependencies?: CreateDependencies;
21
/** Process and modify the project graph */
22
processProjectGraph?: ProcessProjectGraph;
23
/** Generate workspace files and configurations */
24
createNodesV2?: CreateNodesV2;
25
}
26
27
/**
28
* Function to create project nodes from configuration files
29
*/
30
type CreateNodes = (
31
configFilePath: string,
32
options: any,
33
context: CreateNodesContext
34
) => CreateNodesResult | Promise<CreateNodesResult>;
35
36
/**
37
* Enhanced version of CreateNodes with batching support
38
*/
39
type CreateNodesV2 = (
40
configFiles: string[],
41
options: any,
42
context: CreateNodesContext
43
) => Promise<CreateNodesResultV2>;
44
45
/**
46
* Function to create project dependencies
47
*/
48
type CreateDependencies = (
49
opts: CreateDependenciesContext
50
) => RawProjectGraphDependency[] | Promise<RawProjectGraphDependency[]>;
51
52
/**
53
* Function to process the project graph
54
*/
55
type ProcessProjectGraph = (
56
graph: ProjectGraph,
57
context: CreateDependenciesContext
58
) => ProjectGraph | Promise<ProjectGraph>;
59
```
60
61
### Plugin Context Types
62
63
Context objects provided to plugin functions.
64
65
```typescript { .api }
66
interface CreateNodesContext {
67
/** Nx workspace configuration */
68
nxJsonConfiguration: NxJsonConfiguration;
69
/** Workspace root directory */
70
workspaceRoot: string;
71
/** All configuration files found */
72
configFiles: string[];
73
/** File map for workspace files */
74
fileMap?: ProjectFileMap;
75
}
76
77
interface CreateDependenciesContext {
78
/** All workspace files */
79
projects: Record<string, ProjectConfiguration>;
80
/** Nx workspace configuration */
81
nxJsonConfiguration: NxJsonConfiguration;
82
/** File map for workspace files */
83
fileMap: ProjectFileMap;
84
/** File map for external dependencies */
85
externalNodes?: Record<string, ProjectGraphExternalNode>;
86
}
87
88
interface CreateNodesResult {
89
/** Map of project root to project configuration */
90
projects?: Record<string, CreateNodesProjectConfiguration>;
91
/** External dependencies discovered */
92
externalNodes?: Record<string, ProjectGraphExternalNode>;
93
}
94
95
interface CreateNodesResultV2 {
96
/** Map of config file to create nodes result */
97
[configFile: string]: CreateNodesResult;
98
}
99
100
interface CreateNodesProjectConfiguration {
101
/** Project name */
102
name?: string;
103
/** Build targets */
104
targets?: Record<string, TargetConfiguration>;
105
/** Project metadata */
106
metadata?: ProjectMetadata;
107
/** Project tags */
108
tags?: string[];
109
/** Named inputs */
110
namedInputs?: NamedInputs;
111
}
112
```
113
114
### Plugin Registration
115
116
Functions for loading and registering plugins.
117
118
```typescript { .api }
119
/**
120
* Loads Nx plugins from the workspace configuration
121
* @param nxJson - Nx workspace configuration
122
* @param paths - Additional paths to search for plugins
123
* @returns Array of loaded plugins
124
*/
125
function loadNxPlugins(
126
nxJson: NxJsonConfiguration,
127
paths?: string[]
128
): Promise<LoadedNxPlugin[]>;
129
130
/**
131
* Checks if an object implements the NxPlugin interface
132
* @param plugin - Object to check
133
* @returns True if object is an NxPlugin
134
*/
135
function isNxPlugin(plugin: any): plugin is NxPlugin;
136
137
/**
138
* Reads package.json for a plugin package
139
* @param pluginName - Name of the plugin package
140
* @param paths - Paths to search for the plugin
141
* @returns Plugin package.json content
142
*/
143
function readPluginPackageJson(
144
pluginName: string,
145
paths?: string[]
146
): { packageJson: PackageJson; path: string };
147
148
interface LoadedNxPlugin {
149
/** Plugin name */
150
name: string;
151
/** Plugin implementation */
152
plugin: NxPlugin;
153
/** Plugin options from nx.json */
154
options: any;
155
}
156
```
157
158
### Project Graph Dependencies
159
160
Types for defining project dependencies through plugins.
161
162
```typescript { .api }
163
interface RawProjectGraphDependency {
164
/** Source project name */
165
source: string;
166
/** Target project name */
167
target: string;
168
/** Type of dependency */
169
type: DependencyType;
170
/** Source file that created the dependency */
171
sourceFile?: string;
172
}
173
174
interface ProjectGraphExternalNode {
175
/** Package type (npm, etc.) */
176
type: 'npm' | string;
177
/** Package name */
178
name: string;
179
/** Package data */
180
data: {
181
version: string;
182
packageName: string;
183
hash?: string;
184
};
185
}
186
187
type DependencyType =
188
| 'static' // Import/require statements
189
| 'dynamic' // Dynamic imports
190
| 'implicit' // Configured dependencies
191
| 'direct' // Direct project dependencies
192
| 'indirect'; // Transitive dependencies
193
```
194
195
### Built-in Plugin Utilities
196
197
Utilities for common plugin development tasks.
198
199
```typescript { .api }
200
/**
201
* Creates a project node from package.json
202
* @param packageJsonPath - Path to package.json
203
* @param context - Create nodes context
204
* @returns Project configuration
205
*/
206
function createNodeFromPackageJson(
207
packageJsonPath: string,
208
context: CreateNodesContext
209
): CreateNodesProjectConfiguration | null;
210
211
/**
212
* Parses target configuration from executor strings
213
* @param executor - Executor string (e.g., "@nx/webpack:webpack")
214
* @returns Parsed executor information
215
*/
216
function parseExecutor(executor: string): {
217
package: string;
218
executor: string;
219
};
220
221
/**
222
* Resolves workspace relative paths
223
* @param workspaceRoot - Workspace root directory
224
* @param path - Path to resolve
225
* @returns Resolved absolute path
226
*/
227
function resolveWorkspacePath(workspaceRoot: string, path: string): string;
228
229
/**
230
* Gets project root from configuration file path
231
* @param configFilePath - Path to configuration file
232
* @param workspaceRoot - Workspace root directory
233
* @returns Project root directory
234
*/
235
function getProjectRootFromConfigFile(
236
configFilePath: string,
237
workspaceRoot: string
238
): string;
239
```
240
241
## Usage Examples
242
243
### Basic Plugin Implementation
244
245
```typescript
246
import {
247
NxPlugin,
248
CreateNodes,
249
CreateNodesContext,
250
CreateNodesResult,
251
TargetConfiguration,
252
logger
253
} from "nx/src/devkit-exports";
254
255
const createNodes: CreateNodes = (
256
configFilePath: string,
257
options: any,
258
context: CreateNodesContext
259
): CreateNodesResult => {
260
logger.info(`Processing config file: ${configFilePath}`);
261
262
// Get project root from config file path
263
const projectRoot = path.dirname(configFilePath);
264
const projectName = path.basename(projectRoot);
265
266
// Read configuration file
267
const configContent = readFileSync(configFilePath, 'utf-8');
268
const config = JSON.parse(configContent);
269
270
// Create build target based on configuration
271
const buildTarget: TargetConfiguration = {
272
executor: '@my-plugin/executor:build',
273
options: {
274
outputPath: `dist/${projectRoot}`,
275
...config.buildOptions
276
}
277
};
278
279
// Create test target if tests are configured
280
const targets: Record<string, TargetConfiguration> = { build: buildTarget };
281
282
if (config.testFramework) {
283
targets.test = {
284
executor: '@my-plugin/executor:test',
285
options: {
286
testFramework: config.testFramework,
287
...config.testOptions
288
}
289
};
290
}
291
292
return {
293
projects: {
294
[projectRoot]: {
295
name: projectName,
296
targets,
297
tags: config.tags || [],
298
metadata: {
299
framework: config.framework,
300
version: config.version
301
}
302
}
303
}
304
};
305
};
306
307
const myPlugin: NxPlugin = {
308
name: 'my-custom-plugin',
309
createNodes: [
310
// Glob pattern to match configuration files
311
'**/my-config.json',
312
createNodes
313
]
314
};
315
316
export default myPlugin;
317
```
318
319
### Advanced Plugin with Dependencies
320
321
```typescript
322
import {
323
NxPlugin,
324
CreateNodes,
325
CreateDependencies,
326
CreateDependenciesContext,
327
RawProjectGraphDependency,
328
logger
329
} from "nx/src/devkit-exports";
330
331
const createDependencies: CreateDependencies = (
332
context: CreateDependenciesContext
333
): RawProjectGraphDependency[] => {
334
const dependencies: RawProjectGraphDependency[] = [];
335
336
// Analyze workspace files to find dependencies
337
for (const [projectName, project] of Object.entries(context.projects)) {
338
const projectFiles = context.fileMap.projectFileMap[projectName] || [];
339
340
// Look for custom import patterns
341
for (const file of projectFiles) {
342
if (file.file.endsWith('.ts') || file.file.endsWith('.js')) {
343
const filePath = path.join(context.workspaceRoot, file.file);
344
const content = readFileSync(filePath, 'utf-8');
345
346
// Find custom import statements
347
const importRegex = /import.*from\s+['"]@workspace\/([^'"]+)['"]/g;
348
let match;
349
350
while ((match = importRegex.exec(content)) !== null) {
351
const targetProject = match[1];
352
353
if (context.projects[targetProject]) {
354
dependencies.push({
355
source: projectName,
356
target: targetProject,
357
type: 'static',
358
sourceFile: file.file
359
});
360
}
361
}
362
}
363
}
364
}
365
366
logger.info(`Found ${dependencies.length} custom dependencies`);
367
return dependencies;
368
};
369
370
const advancedPlugin: NxPlugin = {
371
name: 'advanced-plugin',
372
createNodes: ['**/project.config.json', createNodes],
373
createDependencies
374
};
375
376
export default advancedPlugin;
377
```
378
379
### Plugin with Project Graph Processing
380
381
```typescript
382
import {
383
NxPlugin,
384
ProcessProjectGraph,
385
ProjectGraph,
386
CreateDependenciesContext,
387
logger
388
} from "nx/src/devkit-exports";
389
390
const processProjectGraph: ProcessProjectGraph = (
391
graph: ProjectGraph,
392
context: CreateDependenciesContext
393
): ProjectGraph => {
394
logger.info('Processing project graph for optimization');
395
396
// Add virtual nodes for shared resources
397
const modifiedGraph = { ...graph };
398
399
// Find projects that share common patterns
400
const sharedLibraries = new Set<string>();
401
402
for (const [projectName, dependencies] of Object.entries(graph.dependencies)) {
403
dependencies.forEach(dep => {
404
if (dep.target.startsWith('shared-')) {
405
sharedLibraries.add(dep.target);
406
}
407
});
408
}
409
410
// Add metadata to shared library nodes
411
for (const sharedLib of sharedLibraries) {
412
if (modifiedGraph.nodes[sharedLib]) {
413
modifiedGraph.nodes[sharedLib].data = {
414
...modifiedGraph.nodes[sharedLib].data,
415
tags: [...(modifiedGraph.nodes[sharedLib].data.tags || []), 'shared']
416
};
417
}
418
}
419
420
return modifiedGraph;
421
};
422
423
const graphProcessorPlugin: NxPlugin = {
424
name: 'graph-processor-plugin',
425
processProjectGraph
426
};
427
428
export default graphProcessorPlugin;
429
```
430
431
### Multi-Framework Plugin
432
433
```typescript
434
import {
435
NxPlugin,
436
CreateNodes,
437
CreateNodesContext,
438
CreateNodesResult,
439
TargetConfiguration
440
} from "nx/src/devkit-exports";
441
442
interface FrameworkConfig {
443
framework: 'react' | 'vue' | 'angular';
444
buildTool: 'webpack' | 'vite' | 'rollup';
445
features: string[];
446
}
447
448
const createNodes: CreateNodes = (
449
configFilePath: string,
450
options: any,
451
context: CreateNodesContext
452
): CreateNodesResult => {
453
const projectRoot = path.dirname(configFilePath);
454
const config: FrameworkConfig = JSON.parse(
455
readFileSync(configFilePath, 'utf-8')
456
);
457
458
// Create framework-specific targets
459
const targets: Record<string, TargetConfiguration> = {};
460
461
// Build target based on framework and build tool
462
switch (config.framework) {
463
case 'react':
464
targets.build = {
465
executor: config.buildTool === 'vite'
466
? '@nx/vite:build'
467
: '@nx/webpack:webpack',
468
options: {
469
outputPath: `dist/${projectRoot}`,
470
main: `${projectRoot}/src/main.tsx`,
471
index: `${projectRoot}/src/index.html`
472
}
473
};
474
break;
475
476
case 'vue':
477
targets.build = {
478
executor: '@nx/vite:build',
479
options: {
480
outputPath: `dist/${projectRoot}`,
481
configFile: `${projectRoot}/vite.config.ts`
482
}
483
};
484
break;
485
486
case 'angular':
487
targets.build = {
488
executor: '@angular-devkit/build-angular:browser',
489
options: {
490
outputPath: `dist/${projectRoot}`,
491
index: `${projectRoot}/src/index.html`,
492
main: `${projectRoot}/src/main.ts`,
493
polyfills: `${projectRoot}/src/polyfills.ts`,
494
tsConfig: `${projectRoot}/tsconfig.app.json`
495
}
496
};
497
break;
498
}
499
500
// Add feature-specific targets
501
if (config.features.includes('testing')) {
502
targets.test = {
503
executor: '@nx/jest:jest',
504
options: {
505
jestConfig: `${projectRoot}/jest.config.ts`
506
}
507
};
508
}
509
510
if (config.features.includes('e2e')) {
511
targets.e2e = {
512
executor: '@nx/cypress:cypress',
513
options: {
514
cypressConfig: `${projectRoot}/cypress.config.ts`
515
}
516
};
517
}
518
519
if (config.features.includes('storybook')) {
520
targets.storybook = {
521
executor: '@nx/storybook:storybook',
522
options: {
523
port: 4400,
524
configDir: `${projectRoot}/.storybook`
525
}
526
};
527
}
528
529
return {
530
projects: {
531
[projectRoot]: {
532
name: path.basename(projectRoot),
533
targets,
534
tags: [config.framework, config.buildTool, ...config.features],
535
metadata: {
536
framework: config.framework,
537
buildTool: config.buildTool,
538
features: config.features
539
}
540
}
541
}
542
};
543
};
544
545
const multiFrameworkPlugin: NxPlugin = {
546
name: 'multi-framework-plugin',
547
createNodes: ['**/framework.config.json', createNodes]
548
};
549
550
export default multiFrameworkPlugin;
551
```
552
553
### Plugin Configuration in nx.json
554
555
```json
556
{
557
"plugins": [
558
{
559
"plugin": "@my-org/nx-custom-plugin",
560
"options": {
561
"buildCommand": "custom-build",
562
"testPattern": "**/*.test.{js,ts}",
563
"outputDir": "custom-dist"
564
}
565
},
566
{
567
"plugin": "./tools/local-plugin",
568
"options": {
569
"localOption": "value"
570
}
571
}
572
]
573
}
574
```
575
576
### Plugin Development Best Practices
577
578
```typescript
579
import {
580
NxPlugin,
581
CreateNodes,
582
logger,
583
workspaceRoot
584
} from "nx/src/devkit-exports";
585
586
// Cache parsed configurations to improve performance
587
const configCache = new Map<string, any>();
588
589
const createNodes: CreateNodes = (configFilePath, options, context) => {
590
// Use caching for expensive operations
591
if (configCache.has(configFilePath)) {
592
const cachedConfig = configCache.get(configFilePath);
593
return createProjectFromConfig(cachedConfig, configFilePath, options);
594
}
595
596
try {
597
// Read and parse configuration
598
const config = parseConfigFile(configFilePath);
599
configCache.set(configFilePath, config);
600
601
return createProjectFromConfig(config, configFilePath, options);
602
603
} catch (error) {
604
logger.warn(`Failed to parse config file ${configFilePath}: ${error.message}`);
605
return {}; // Return empty result on error
606
}
607
};
608
609
function createProjectFromConfig(config: any, configFilePath: string, options: any) {
610
const projectRoot = path.relative(workspaceRoot, path.dirname(configFilePath));
611
612
// Validate configuration
613
if (!config || typeof config !== 'object') {
614
logger.warn(`Invalid configuration in ${configFilePath}`);
615
return {};
616
}
617
618
// Create targets with error handling
619
const targets: Record<string, TargetConfiguration> = {};
620
621
try {
622
if (config.build) {
623
targets.build = createBuildTarget(config.build, projectRoot, options);
624
}
625
626
if (config.test) {
627
targets.test = createTestTarget(config.test, projectRoot, options);
628
}
629
} catch (error) {
630
logger.error(`Error creating targets for ${projectRoot}: ${error.message}`);
631
return {};
632
}
633
634
return {
635
projects: {
636
[projectRoot]: {
637
name: config.name || path.basename(projectRoot),
638
targets,
639
tags: config.tags || [],
640
metadata: {
641
configFile: configFilePath,
642
pluginVersion: '1.0.0'
643
}
644
}
645
}
646
};
647
}
648
649
const robustPlugin: NxPlugin = {
650
name: 'robust-plugin',
651
createNodes: ['**/plugin.config.{json,js,ts}', createNodes]
652
};
653
654
export default robustPlugin;
655
```