What This Template Is For
A code generation specification defines what your tool generates, from what inputs, and how users customize the output. Whether you are building an API client generator, a project scaffolder, a schema-to-types converter, or an AI-assisted code tool, the same core questions apply: what goes in, what comes out, and how does the user control the process.
Code generation tools fail when they produce output that does not match the team's coding standards, when they cannot be extended for edge cases, or when the generated code is so opaque that developers refuse to touch it. This template forces you to think through input validation, output formatting, customization hooks, and the boundary between generated and hand-written code.
This template pairs well with the technical spec template for the engineering implementation plan. If you are evaluating whether to build a code generator versus maintaining hand-written code, score both options with the RICE framework. The Technical PM Handbook covers how to gather requirements from engineering teams, and the developer experience glossary entry has principles that apply to any tool developers interact with.
When to Use This Template
Use this template when you are building a tool that produces source code files from structured input (schemas, configs, API definitions, or user prompts). It is equally useful for template-based generators and AI-powered code assistants.
Skip this template for simple string interpolation (e.g., populating a README from package.json). Those can be documented in a script comment.
How to Use This Template
- Define the input format and validation rules first. The quality of generated code depends entirely on the quality and structure of the input.
- Document every output target (language, framework, file structure) with a concrete example of what the generated code looks like.
- Specify the customization model explicitly. Users will always need to override defaults. Define whether customization happens via config files, CLI flags, template overrides, or code annotations.
- Write the "do not generate" boundaries clearly. Generated code that mixes with hand-written code needs clear seams so manual edits are preserved on regeneration.
- Include a validation and testing strategy. Generated code must compile, pass lint checks, and satisfy type checkers. Define how the generator verifies its own output.
The Template
Generator Overview
| Field | Details |
|---|---|
| Generator Name | [name] |
| Purpose | [What it generates and why] |
| Input Source | [OpenAPI spec, GraphQL schema, database schema, user prompt, etc.] |
| Output Languages | [TypeScript, Python, Go, etc.] |
| Distribution | [npm package, CLI binary, IDE plugin, CI step] |
| Trigger | [Manual CLI command, file watcher, CI pipeline, on-save] |
Input Specification
Supported input formats:
| Format | Version | Parser | Example |
|---|---|---|---|
| [Format 1] | [Version] | [Library/custom] | [Link or inline example] |
| [Format 2] | [Version] | [Library/custom] | [Link or inline example] |
Validation rules:
| Rule | Severity | Description |
|---|---|---|
| [Rule 1] | Error | [What must be true for input to be valid] |
| [Rule 2] | Error | [What must be true for input to be valid] |
| [Rule 3] | Warning | [What triggers a non-blocking warning] |
Input preprocessing:
- [Step 1: Normalize/resolve references]
- [Step 2: Apply defaults for missing fields]
- [Step 3: Validate against schema]
Output Specification
Output structure:
generated/
āāā [file-1].[ext] [What this file contains]
āāā [file-2].[ext] [What this file contains]
āāā [directory]/
ā āāā [file-3].[ext] [What this file contains]
ā āāā [file-4].[ext] [What this file contains]
āāā index.[ext] [Barrel exports or entry point]
Per-file specification:
| File | Purpose | Regeneration Behavior |
|---|---|---|
| [file-1] | [Description] | Overwrite on every run |
| [file-2] | [Description] | Generate once, never overwrite |
| [file-3] | [Description] | Merge (preserve manual edits in marked sections) |
Code style rules:
| Rule | Value |
|---|---|
| Indentation | [spaces/tabs, count] |
| Naming convention | [camelCase, snake_case, PascalCase for types] |
| Import style | [named imports, namespace imports] |
| Comment style | [JSDoc, inline, none] |
| Line length | [max characters] |
| Trailing comma | [yes/no] |
Customization Model
Configuration file: [filename] (e.g., codegen.config.yaml)
generator:
output_dir: [path]
language: [language]
overrides:
[entity-name]:
rename: [custom-name]
skip: [true/false]
custom_type: [type override]
plugins:
- [plugin-name]
templates:
[template-key]: [path-to-custom-template]
Template override system:
| Hook | Description | Default |
|---|---|---|
| [hook-1] | [What this template controls] | [Built-in template name] |
| [hook-2] | [What this template controls] | [Built-in template name] |
Custom plugins:
- [How to write a plugin]
- [Plugin lifecycle hooks: beforeGenerate, afterGenerate, transformOutput]
- [Plugin API surface]
Regeneration and Manual Edits
Protected regions:
// @generated-start
[Generated code that will be overwritten]
// @generated-end
// @custom-start
[Hand-written code preserved on regeneration]
// @custom-end
Lock file. [Does the generator produce a lock file tracking generated files?]
Diff behavior. [What happens when input changes? Full regeneration or incremental?]
Validation and Testing
| Check | Tool | When |
|---|---|---|
| Type checking | [tsc, mypy, go vet] | After every generation |
| Lint | [eslint, ruff, golangci-lint] | After every generation |
| Compile | [Language compiler] | After every generation |
| Snapshot tests | [jest, pytest] | CI pipeline |
| Round-trip test | [Custom] | CI pipeline |
Filled Example: API Client Generator (apigen)
Generator Overview
| Field | Details |
|---|---|
| Generator Name | apigen |
| Purpose | Generate typed API clients from OpenAPI 3.1 specifications |
| Input Source | OpenAPI 3.1 YAML or JSON files |
| Output Languages | TypeScript (v1), Python (planned v2) |
| Distribution | npm package (@acme/apigen) |
| Trigger | CLI command (apigen generate) or file watcher (apigen watch) |
Input Specification
Supported input formats:
| Format | Version | Parser | Example |
|---|---|---|---|
| OpenAPI | 3.0, 3.1 | @apidevtools/swagger-parser | openapi.yaml |
| Swagger | 2.0 | Auto-converted to OpenAPI 3.1 | swagger.json |
Validation rules:
| Rule | Severity | Description |
|---|---|---|
| Valid OpenAPI schema | Error | Input must pass OpenAPI spec validation |
| All $ref targets resolve | Error | No broken schema references |
| operationId on every endpoint | Error | Required for function naming |
| No duplicate operationIds | Error | Each operation must have a unique ID |
| Response schema defined | Warning | Endpoints without response schemas generate unknown return types |
Output Specification
Output structure:
generated/api/
āāā client.ts Base client class with fetch configuration
āāā types.ts All request/response type definitions
āāā endpoints/
ā āāā users.ts Users endpoint methods
ā āāā projects.ts Projects endpoint methods
ā āāā billing.ts Billing endpoint methods
āāā index.ts Barrel exports
Generated client usage:
import { ApiClient } from './generated/api';
const client = new ApiClient({
baseUrl: 'https://api.acme.com',
token: process.env.API_TOKEN,
});
// Fully typed: params, body, and response
const user = await client.users.getById({ userId: '123' });
// user is typed as UserResponse
Configuration
# apigen.config.yaml
input: ./specs/openapi.yaml
output:
dir: ./src/generated/api
language: typescript
overrides:
User:
rename: AppUser
AdminEndpoints:
skip: true
options:
runtime: fetch
date_library: dayjs
enum_style: union
optional_nullable: true
Regeneration
All files in generated/api/ are fully regenerated on every run. The generator writes a .apigen-lock.json tracking which files it manages. Running apigen generate deletes all tracked files before regenerating to prevent stale artifacts.
Manual code belongs outside the generated/ directory. Wrapper functions, custom hooks, and business logic should import from the generated client but live in separate files.
Key Takeaways
- Define input validation rules before output formats. Bad input produces bad generated code regardless of template quality
- Support at least two output formats (human-readable and machine-parseable) for CLI-based generators
- Document regeneration behavior explicitly. Users need to know which files are safe to edit and which will be overwritten
- Include a customization model (config file, template overrides, or plugins) from day one. Every team has naming conventions the defaults will not match
- Validate generated output with the same linters and type checkers the team uses on hand-written code
- Write snapshot tests that catch unintended changes in generated output across generator upgrades
About This Template
Created by: Tim Adair
Last Updated: 3/5/2026
Version: 1.0.0
License: Free for personal and commercial use
