Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nrwl/nx/llms.txt

Use this file to discover all available pages before exploring further.

Generators are functions that make changes to the file system. They can create new files, update existing ones, add project configuration, and install packages. Because they run against an in-memory virtual file system (the Tree), you can preview every change before it is written to disk.

Creating a generator

1

Add the plugin package if you don't have one yet

nx add @nx/plugin
nx g @nx/plugin:plugin tools/my-plugin
2

Scaffold a new generator

nx generate @nx/plugin:generator tools/my-plugin/src/generators/my-generator
This creates the following files inside your plugin:
tools/my-plugin/src/generators/my-generator/
├── generator.spec.ts   # Unit tests
├── generator.ts        # Implementation
├── schema.d.ts         # TypeScript interface for options
└── schema.json         # JSON Schema for validation and CLI prompts

Generator function signature

Every generator exports a default async function that receives a Tree and a typed options object:
// tools/my-plugin/src/generators/my-generator/generator.ts
import { Tree, formatFiles, installPackagesTask } from '@nx/devkit';
import { libraryGenerator } from '@nx/js';

export default async function myGenerator(tree: Tree, schema: MyGeneratorSchema) {
  // 1. Call other generators to compose behavior
  await libraryGenerator(tree, { name: schema.name });

  // 2. Format all modified files with Prettier
  await formatFiles(tree);

  // 3. Optionally return a callback for side effects that run after
  //    the virtual file system is flushed to disk (e.g. installing packages)
  return () => {
    installPackagesTask(tree);
  };
}
The Tree is a virtual file system. No changes are written to disk until the generator finishes successfully. This is what makes --dry-run possible.

Defining the schema

The schema.json file describes available options, their types, default values, and CLI prompt behavior.
// tools/my-plugin/src/generators/my-generator/schema.json
{
  "cli": "nx",
  "id": "my-generator",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "The name of the library to generate",
      "$default": {
        "$source": "argv",
        "index": 0
      },
      "x-prompt": "What name would you like to use?",
      "x-priority": "important"
    },
    "directory": {
      "type": "string",
      "description": "Directory where the library will be placed"
    },
    "skipFormat": {
      "type": "boolean",
      "default": false,
      "x-priority": "internal"
    }
  },
  "required": ["name"]
}

Schema property extensions

Nx recognizes several extensions beyond standard JSON Schema:
Maps positional CLI arguments and workspace context to schema properties.
"name": {
  "type": "string",
  "$default": { "$source": "argv", "index": 0 }
}
SourceDescription
{ "$source": "argv", "index": 0 }First positional CLI argument
{ "$source": "projectName" }Current project name; also enables project autocomplete
{ "$source": "workingDirectory" }CWD relative to workspace root
{ "$source": "unparsed" }Any unmatched extra arguments
Defines a prompt shown when the option is not provided on the command line.
"style": {
  "type": "string",
  "x-prompt": {
    "message": "Which stylesheet format would you like to use?",
    "type": "list",
    "items": [
      { "value": "css", "label": "CSS" },
      { "value": "scss", "label": "SASS (.scss)" },
      { "value": "less", "label": "LESS" }
    ]
  }
}
Use the shorthand "x-prompt": "What name would you like?" for a simple text prompt.
Controls visibility and order in the Nx Console generator form.
ValueEffect
"important"Appears near the top of the form, after required fields
"internal"Hidden from the form by default
Fields are ordered: required > important > regular > deprecated > internal.
Deprecated options appear at the bottom of the Nx Console form with a warning.
"oldOption": {
  "type": "string",
  "x-deprecated": "Use 'newOption' instead."
}
Tells Nx Console to populate a dropdown with live workspace data.
"projectName": {
  "type": "string",
  "x-dropdown": "projects"
}
Currently "projects" is the only supported value.

Using generateFiles to template files

generateFiles copies a directory of EJS template files into the workspace, substituting variables along the way.
// tools/my-plugin/src/generators/my-generator/generator.ts
import {
  Tree,
  generateFiles,
  formatFiles,
  joinPathFragments,
  names,
  addProjectConfiguration,
  readProjectConfiguration,
} from '@nx/devkit';
import * as path from 'path';

export default async function myGenerator(tree: Tree, options: MyGeneratorSchema) {
  const projectRoot = `libs/${options.name}`;

  // Register the new project with Nx
  addProjectConfiguration(tree, options.name, {
    root: projectRoot,
    projectType: 'library',
    sourceRoot: `${projectRoot}/src`,
    targets: {},
  });

  // Copy template files from ./files/ into the new project root,
  // substituting <%= name %> and other EJS variables
  generateFiles(
    tree,                                          // virtual file system
    path.join(__dirname, 'files'),                 // path to templates
    projectRoot,                                   // destination path
    { ...options, ...names(options.name) }         // template variables
  );

  await formatFiles(tree);
}
Template files use EJS syntax. File names may contain __variable__ placeholders:
<!-- tools/my-plugin/src/generators/my-generator/files/README.md.template -->
# <%= name %>

This library was generated by the `my-generator` generator.
// Control flow is supported too
<% if(addTests) { %>
## Testing
Run `nx test <%= name %>` to execute unit tests.
<% } %>
Pass helper functions as template variables. For example, { ...options, upper: (s) => s.toUpperCase() } lets templates call <%= upper(name) %>.

Modifying existing files

Update JSON files

import { updateJson } from '@nx/devkit';

export default async function (tree: Tree, schema: any) {
  updateJson(tree, 'package.json', (pkgJson) => {
    pkgJson.scripts = pkgJson.scripts ?? {};
    pkgJson.scripts.greet = 'echo "Hello!"';
    return pkgJson;
  });
}

String replacement

export default async function (tree: Tree, schema: any) {
  const filePath = `libs/${schema.name}/src/index.ts`;
  const contents = tree.read(filePath).toString();
  const newContents = contents.replace(/OldName/g, schema.name);
  tree.write(filePath, newContents);
}

Running a generator

# Run the generator (creates real files)
nx generate @myorg/my-plugin:my-generator mylib

# Preview changes without writing to disk
nx generate @myorg/my-plugin:my-generator mylib --dry-run
Always use --dry-run first to review the list of files that will be created or modified before committing to the changes.
When referencing your plugin, use the name field from tools/my-plugin/package.json, not the folder path.

Debugging generators

To use VS Code’s debugger:
  1. Open the Command Palette and choose Debug: Create JavaScript Debug Terminal.
  2. Set breakpoints in generator.ts.
  3. Run nx g my-generator in the debug terminal.

Key devkit utilities for generators

FunctionDescription
generateFiles(tree, src, dest, vars)Copy EJS templates into the workspace
addProjectConfiguration(tree, name, config)Register a new project in project.json
readProjectConfiguration(tree, name)Read an existing project’s configuration
updateProjectConfiguration(tree, name, config)Update an existing project’s configuration
updateJson(tree, path, fn)Modify a JSON file with a callback
readNxJson(tree)Read nx.json
updateNxJson(tree, config)Write changes to nx.json
formatFiles(tree)Format all modified files with Prettier
installPackagesTask(tree)Trigger package installation after the tree is flushed
names(str)Derive camelCase, PascalCase, kebab-case, and underscore variants
joinPathFragments(...parts)Safely join path segments

Build docs developers (and LLMs) love