Building Generators
Actions Reference
Actions are the steps that change your project. They run in order, each with full access to the merged context — project signals, prompt answers, and config values.
Conditional execution
Every action supports an if field — a JavaScript expression evaluated against the merged context of signals + prompt answers. The action is skipped when falsy.
{
"type": "template",
"source": "templates/docker-compose.yml.hbs",
"target": "docker-compose.yml",
"if": "withDocker && packageManager === 'pnpm'"
}Action types
Copies a file from the generator directory to the target path verbatim — no rendering.
| Field | Type | Required | Description |
|---|---|---|---|
| source | string | Yes | Path relative to the generator directory |
| target | string | Yes | Destination path relative to project root |
{
"type": "copy",
"source": "assets/logo.svg",
"target": "public/logo.svg"
}Renders a Handlebars template using the current context (signals + prompt answers) and writes the result to the target path.
| Field | Type | Required | Description |
|---|---|---|---|
| source | string | Yes | Path to .hbs file, relative to generator directory |
| target | string | Yes | Destination path. Supports {{variable}} interpolation |
{
"type": "template",
"source": "templates/service.ts.hbs",
"target": "src/{{kebabCase name}}/{{kebabCase name}}.service.ts"
}Appends content to an existing file. Creates the file if it doesn't exist. Content supports Handlebars rendering.
| Field | Type | Required | Description |
|---|---|---|---|
| target | string | Yes | Path to the file to append to |
| content | string | Yes | Content to append. Supports Handlebars rendering |
| newline | boolean | No | Prefix content with a newline (default: true) |
{
"type": "append",
"target": ".env.example",
"content": "DATABASE_URL=postgresql://localhost:5432/{{snakeCase name}}"
}Inserts content immediately after or before a marker string in an existing file. Throws if the marker is not found.
| Field | Type | Required | Description |
|---|---|---|---|
| target | string | Yes | Path to the file to modify |
| content | string | Yes | Content to insert. Supports Handlebars rendering |
| after | string | No* | Insert after this marker string |
| before | string | No* | Insert before this marker string |
{
"type": "inject",
"target": "src/app.module.ts",
"after": "// GENERATORS",
"content": "import { {{pascalCase name}}Module } from './{{kebabCase name}}/{{kebabCase name}}.module';"
}Performs a regex find-and-replace inside a file. Search and replace strings support {{variable}} interpolation.
| Field | Type | Required | Description |
|---|---|---|---|
| target | string | Yes | Path to the file to modify |
| search | string | Yes | Regex pattern to search for |
| replace | string | Yes | Replacement string. Supports Handlebars and $1/$2 capture groups |
| flags | string | No | Regex flags (default: "g") |
{
"type": "replace",
"target": "README.md",
"search": "\$\{APP_NAME\}",
"replace": "{{name}}",
"flags": "g"
}Merges a JSON object into an existing JSON file. Creates the file if it doesn't exist. Supports shallow (default) or deep merge.
| Field | Type | Required | Description |
|---|---|---|---|
| target | string | Yes | Path to the JSON file |
| merge | object | Yes | Object to merge into the file |
| deep | boolean | No | Use deep merge instead of shallow (default: false) |
{
"type": "json",
"target": "package.json",
"deep": true,
"merge": {
"scripts": {
"db:migrate": "prisma migrate dev",
"db:push": "prisma db push"
},
"dependencies": {
"@prisma/client": "^6.0.0"
}
}
}Sets or updates a KEY=value line in a .env file. If the key already exists it is updated in place; otherwise it is appended.
| Field | Type | Required | Description |
|---|---|---|---|
| key | string | Yes | The environment variable key |
| value | string | Yes | The value. Supports {{variable}} interpolation |
| target | string | No | Path to the env file (default: .env) |
{
"type": "env",
"key": "DATABASE_URL",
"value": "postgresql://localhost:5432/{{snakeCase name}}_dev"
}Adds a TypeScript import declaration using AST manipulation (ts-morph). Never duplicates an existing import.
| Field | Type | Required | Description |
|---|---|---|---|
| target | string | Yes | Path to the TypeScript file |
| from | string | Yes | The module specifier (e.g. '@nestjs/common') |
| import | string | string[] | Yes | Named import(s) to add |
| isDefault | boolean | No | Add as default import instead of named |
{
"type": "ast-add-import",
"target": "src/app.module.ts",
"from": "@nestjs/config",
"import": ["ConfigModule", "ConfigService"]
}Runs a shell command in the project directory. Streams output to the terminal.
| Field | Type | Required | Description |
|---|---|---|---|
| command | string | Yes | The shell command to run. Supports {{variable}} interpolation |
| cwd | string | No | Subdirectory to run in, relative to project root |
{
"type": "command",
"command": "pnpm add {{dependencies}}"
}Runs a script file from the generator directory. Useful for complex post-install logic.
| Field | Type | Required | Description |
|---|---|---|---|
| script | string | Yes | Path to the script file, relative to generator directory |
| cwd | string | No | Subdirectory to run in, relative to project root |
{
"type": "script",
"script": "scripts/postinstall.sh"
}Invokes another generator from within this one. Useful for composing generators without requiring the user to run them separately.
| Field | Type | Required | Description |
|---|---|---|---|
| generator | string | Yes | The generator name to invoke (same resolution rules as xo add) |
{
"type": "xo-call",
"generator": "acme/eslint-setup"
}Dry run mode
Pass --dry-run to any CLI command to preview actions without writing files or running commands. Each action prints a log line describing what it would do.
$ xo add acme/auth-jwt --dry-run
[dry-run] template: templates/auth.service.ts.hbs → src/auth/auth.service.ts
[dry-run] template: templates/jwt.guard.ts.hbs → src/auth/jwt.guard.ts
[dry-run] ast-add-import: JwtModule from @nestjs/jwt in src/app.module.ts
[dry-run] json: package.json
[dry-run] command: pnpm install