Documentation article

Create a custom generator

Map a stable slice of Shape IR into a target format incrementally.

Guides15 min

Custom generators are the clearest example of why the toolkit is organized around Shape IR rather than format-to-format adapters.

You do not need to solve every advanced edge case on day one. The first milestone is a generator that can consume a stable subset of Shape IR and report the gaps honestly.

Audience
Extenders
Difficulty
Advanced
Last updated
July 27, 2026
Shape IR
Understand the shared intermediate representation before designing output rules.
Traversal
See how traversal keeps your generator pipeline modular as it grows.

Start from a narrow contract

A generator accepts Shape IR and emits a target representation such as TypeScript, JSON Schema, or a framework-specific config format.

The goal is not to mirror parser internals. The goal is to translate stable IR nodes into output decisions while preserving enough diagnostic context to explain loss or unsupported semantics.

Design the first milestone

A scoped first release is easier to test and much easier to explain in docs. That is especially important while the generator layer is still expanding.

  • Pick the exact output surface you want to support first.
  • List the Shape IR node kinds that matter for that surface.
  • Decide which unsupported cases should fail, warn, or degrade.
  • Keep formatting concerns separate from semantic mapping.

Prefer explicit fallbacks

The implementation can stay small as long as it makes unsupported cases explicit. Silent fallbacks make round-trip behavior much harder to trust later.

Minimal generator shape

ts
type GeneratorResult = {
  code: string;
  diagnostics: string[];
};

export function emitShape(shape: Shape): GeneratorResult {
  switch (shape.kind) {
    case "string":
      return {code: "string", diagnostics: []};
    case "number":
      return {code: "number", diagnostics: []};
    default:
      return {
        code: "unknown",
        diagnostics: [`Unsupported shape: ${shape.kind}`],
      };
  }
}