Generators define a sequence of steps that transform your model into code. Each generator contains templates, partials, and configuration that tells Clay exactly what to generate.
{
"partials": ["partials/header.hbs"],
"steps": [
{
"generate": "templates/{{kebabCase name}}.js",
"select": "$.model.types[*]",
"target": "src/models/"
}
],
"formatters": ["clay-generator-formatter-prettier"]
}
Array of Handlebars partial files to include:
"partials": [
"partials/header.hbs",
"partials/footer.hbs"
]
Array of generation steps (run in order):
"steps": [
{ "runCommand": "mkdir -p src/models" },
{ "generate": "templates/{{name}}.js", "select": "$.model.types[*]" },
{ "copy": "foundation", "target": "src/foundation" }
]
Optional formatters to prettify generated code:
"formatters": ["clay-generator-formatter-prettier"]
Generate files from templates. The engine is chosen with the engine field (Handlebars by default), not the file extension. The output file is named after the template's filename (rendered as Handlebars); target is an optional output subdirectory:
{
"generate": "templates/{{kebabCase name}}.model.js",
"select": "$.model.types[*]",
"target": "src/models/",
"touch": false
}
With different template engines (put the per-entity name and final extension in the template filename — don't add .hbs/.ejs, it is stripped):
// EJS — inline JavaScript logic
{ "generate": "templates/{{name}}Service.ts", "select": "$.model.types[*]", "target": "src/", "engine": "ejs" }
// TypeScript — fully programmatic via CodeGenerator class
{ "generate": "templates/index.ts", "select": "$.model", "target": "src/routes/", "engine": "ts" }
Parameters:
generate - Path to the template; its filename (Handlebars-rendered) becomes the output filenameselect - JSONPath to select data (optional)target - Optional output subdirectory (can use Handlebars) — a directory prefix, not the filenametouch - If true, only generate if file doesn't exist
engine - Template engine: "handlebars" (default), "ejs", or "ts"
Copy files or directories:
{
"copy": "foundation",
"select": "$.model.types[*]",
"target": "src/{{kebabCase name}}/foundation"
}
Parameters:
copy - Source path or git repo (e.g., "git+user/repo")
select - JSONPath to iterate (optional)target - Destination path (can use Handlebars)Execute shell commands:
{
"runCommand": "npm install",
"npxCommand": false
}
With model data:
{
"runCommand": "echo Generating {{name}}",
"select": "$.model.types[*]"
}
Parameters:
runCommand - Shell command to executenpxCommand - If true, run with npxselect - JSONPath to iterate (optional){
"partials": [
"partials/license-header.hbs",
"partials/imports.hbs"
],
"steps": [
{
"runCommand": "mkdir -p src/models src/controllers src/routes"
},
{
"generate": "templates/{{kebabCase name}}.model.js",
"select": "$.model.types[*]",
"target": "src/models/"
},
{
"generate": "templates/{{kebabCase name}}.controller.js",
"select": "$.model.types[*]",
"target": "src/controllers/"
},
{
"generate": "templates/{{kebabCase name}}.routes.js",
"select": "$.model.types[*]",
"target": "src/routes/"
},
{
"generate": "templates/index.js",
"target": "src/",
"touch": true
},
{
"copy": "config",
"target": "config"
},
{
"runCommand": "npm install"
}
],
"formatters": ["clay-generator-formatter-prettier"]
}
clay init generator my-generator
This creates:
clay/generators/my-generator/
├── generator.json
└── templates/
clay/generators/api/
├── generator.json # Configuration
├── templates/ # Handlebars templates
│ ├── model.js
│ ├── controller.js
│ └── routes.js
├── partials/ # Reusable template parts
│ ├── header.hbs
│ └── imports.hbs
└── foundation/ # Files to copy
└── config.js
Generators can define preChecks that validate the fully resolved model
(after includes and mixins) before any step runs. Prechecks are pure
validators — model in, verdict out — and must not write files or mutate the model.
Unlike steps and postGenerate hooks, a precheck failure aborts the generation
for that model: nothing is rendered, written, copied, or executed. All prechecks run
even if an early one fails, and every violation is aggregated into a single error.
{
"preChecks": [
{ "run": "checks/cloudkit-invariants.ts" },
{ "runCommand": "node checks/naming.mjs", "select": "$.model.types[*]" }
],
"steps": [...]
}
Use the PreCheck base class, following the same pattern as
PostGenerateHook. Import from clay-generator/types.
Return a non-empty array of violation strings (or throw) to fail the check;
return an empty array or nothing to pass.
import { PreCheck, type PreCheckContext } from 'clay-generator/types';
export default class extends PreCheck {
check({ data }: PreCheckContext): string[] | void {
if (!data.name) return ['every type needs a name'];
}
}
Parameters (TypeScript check):
run - Path to a TypeScript PreCheck fileselect - JSONPath to select model items (check runs once per match); without it the check runs once against the root modelParameters (command check):
runCommand - Shell command (supports Handlebars templating with select); receives the model path as its last argument and fails the generation on non-zero exit, surfacing stderrselect - JSONPath to select model itemsverbose - Log command outputThe check() method receives a PreCheckContext with
data (the selected item), helpers, model
(the full root model), and parent — the same context shape as
CodeGenerator.render().
Generators can define postGenerate hooks that run after all steps complete and files are on disk.
Hooks are best-effort — failures log warnings but never fail the generation.
The primary hook type uses the PostGenerateHook base class, following the same pattern as CodeGenerator.
Import from clay-generator/types.
{
"steps": [...],
"postGenerate": [
{ "run": "hooks/fill-services.ts", "select": "$.model.types[*]", "onlyNewTouchFiles": true },
{ "runCommand": "prettier --write src/" }
]
}
Parameters (TypeScript hook):
run - Path to a TypeScript PostGenerateHook fileselect - JSONPath to select model items (hook runs once per match, in parallel)onlyNewTouchFiles - If true, skip items where no touch files were newly createdParameters (command hook):
runCommand - Shell command (supports Handlebars templating)select - JSONPath to select model itemsverbose - Log command outputThe run() method receives a HookContext with:
| Property | Description |
|---|---|
data |
The selected model item |
helpers |
Clay helpers (pascalCase, camelCase, etc.) |
model |
The full root model |
parent |
Parent object in the JSON hierarchy |
touchFiles |
Only files newly created this run |
outputDir |
Generator output directory |
generatedFiles |
All files generated this run |
Use Claude Code headless mode to fill in touch files after generation. Each hook spawns a focused Claude instance with tight context — the interface contract and entity data — rather than the entire project. Runs on your existing Claude subscription.
import { PostGenerateHook, type HookContext } from 'clay-generator/types';
import { execSync } from 'child_process';
import fs from 'fs';
export default class extends PostGenerateHook {
async run({ data, helpers, touchFiles, outputDir }: HookContext): Promise<void> {
const { pascalCase } = helpers;
// Read the generated interface for context
const iface = fs.readFileSync(
`${outputDir}/src/services/I${pascalCase(data.name)}Service.ts`, 'utf-8'
);
for (const file of touchFiles) {
const prompt = `Implement ${pascalCase(data.name)}ServiceImpl following this interface:\n${iface}`;
execSync(`claude -p '${prompt}'`, { cwd: outputDir, timeout: 60000 });
}
}
}
select, per-item calls run in parallel (concurrency: 5)select to iterate over model arraystouch: true for files users might customize
(not tracked in .clay; never auto-deleted by generate)
render(),
not at module load
clay test-path
Clay uses a local-first approach. All generators are
stored in your project's clay/generators/ directory.
Start from scratch with a template:
clay init generator my-api-generator
Copy a generator from another location on your machine:
clay generator add /path/to/my-generator
clay generator add ../other-project/generators/my-generator
Browse available generators and clone them to your project:
clay generator list-available
clay generator add clay-model-documentation
Clone any generator repository. It will be copied to your local
clay/generators/ directory:
clay generator add https://github.com/user/my-generator
Generators are referenced by their path to the generator.json file:
{
"name": "my-app",
"generators": [
"generators/my-api-generator/generator.json",
"generators/forms/generator.json"
],
"model": {...}
}
clay/generators/ and are part of your codebase. Use
clay watch to see changes immediately!
Since generators are stored locally in your project, sharing them is easy:
clay/generators/ directory is part of your project