Imported from pradeepmouli/langium-zod (
skills/langium-zod/SKILL.md). Install upstream withnpx skills add pradeepmouli/langium-zod --skill langium-zod. Copyright stays with the author (MIT).
langium-zod
Langium generator plugin that derives Zod schemas from grammar definitions
langium-zod — generate Zod validation schemas from Langium grammar definitions.
Before generating, ask:
- Recursive rules? Grammar rules that reference themselves (e.g.
Expression: ... | left=Expression) require cycle detection.generateZodSchemashandles this automatically via detectRecursiveTypes, but if you build a custom pipeline you must run detection on the full descriptor set before any projection. - Cross-references? Langium
ref:properties becomeReferenceSchemaby default. EnablecrossRefValidation: trueonly when you have a live document model to validate against at runtime; otherwise every cross-ref property will emit an unconstrainedReferenceSchema. - Discriminated unions? Every Langium union type (e.g.
Expression = Literal | BinaryExpr) maps to a Zod discriminated union keyed on$type. All member types must be emitted — useincludewith care when union types are involved. - Grammar changes? Generated schemas are compile-time artifacts. Any grammar edit (new rule, renamed property, changed cardinality) requires regeneration. Wire
langium-zod generateinto your build step so stale schemas are caught early. - AST imports path? The conformance artifact imports from your grammar's
ast.ts. If the generated file moves, updateconformance.astTypesPathto point to the new location or import errors will appear at compile time.
Entry points:
- generateZodSchemas — main programmatic API (grammar → TypeScript source string)
- generateZodCode — low-level emitter operating on pre-built descriptors
- extractTypeDescriptors — extracts the descriptor tree from
AstTypesLike - detectRecursiveTypes — identifies reference cycles before code generation
- zRef — cross-reference validation helper for generated schema factories
- ZodSchemaGeneratorModule — Langium DI module for service-based integration
Quick Start
import { generateZodSchemas } from 'langium-zod';
const source = generateZodSchemas({ grammar, services });
When to Use
Use this skill when:
- You have a parsed Langium
Grammarobject and want Zod schemas as a TypeScript string. → usegenerateZodSchemas - You are integrating langium-zod into a build pipeline (Vite plugin, codegen script, etc.). → use
generateZodSchemas - You need conformance artifacts (type-guard files) alongside the schema output. → use
generateZodSchemas - You want to write generated schemas to disk in a single call. → use
generateZodSchemas - You want to inspect or transform the intermediate descriptor representation before generating code (e.g. to add custom properties or change types). → use
extractTypeDescriptors - You are caching descriptors across multiple calls to generateZodCode with different options, so you only want to pay the extraction cost once. → use
extractTypeDescriptors - You are writing tests against the descriptor model rather than the generated source. → use
extractTypeDescriptors - You already have descriptors and a recursion set and want to run code generation in isolation (e.g. for testing the emitter with synthetic descriptors). → use
generateZodCode - You need to generate code multiple times with different
optionsfrom the same descriptor set without re-running extraction. → usegenerateZodCode - You are building a custom pipeline that inserts descriptor transformations between extraction and code generation. → use
generateZodCode - You need to know which grammar types are recursive before calling generateZodCode (e.g. to log or filter them). → use
detectRecursiveTypes - You are building a custom code emitter and need the same cycle information that the built-in generator uses. → use
detectRecursiveTypes - You need runtime cross-reference validation and are using
crossRefValidation: trueto have the generator emitcreate*Schema()factories that callzRef. → usezRef - You are extending a generated schema with custom cross-reference validation using the same empty-string leniency semantics as generated factories. → use
zRef - You are building a Langium language server plugin that needs live document validation with lazily-resolved reference targets. → use
zRef - You are wrapping generateZodSchemas in a try/catch and want to surface actionable error messages to the user. → use
ZodGeneratorError - You are building a Vite/webpack plugin and need to map generation failures to build-time warnings. → use
ZodGeneratorError - You are using the Langium DI lifecycle and want the default generation behaviour accessible as
services.shared.ZodSchemaGenerator. → useDefaultZodSchemaGenerator - You want to extend or override the generator within the DI system. → use
DefaultZodSchemaGenerator
Do NOT use when:
- You only need to inspect the intermediate type descriptors without generating code — use extractTypeDescriptors directly instead. (
generateZodSchemas) - You are running inside the Langium DI container — prefer DefaultZodSchemaGenerator which injects services automatically. (
generateZodSchemas) - You want to generate schemas for only a subset of types at runtime — pass
include/excludein the config rather than post-processing the output. (generateZodSchemas) - You just want generated Zod schemas — use generateZodSchemas instead, which calls this function internally. (
extractTypeDescriptors) - You want to apply
regexOverrides— those are applied in generateZodSchemas after extraction and are not visible in the raw descriptor array. (extractTypeDescriptors) - You are doing a standard end-to-end generation — use generateZodSchemas instead, which orchestrates all pipeline stages and handles disk writes. (
generateZodCode) - You need
regexOverridesapplied — those are applied by generateZodSchemas before this function is called. (generateZodCode) - You are using the standard pipeline — generateZodSchemas and generateZodCode call this function internally; you do not need to call it yourself. (
detectRecursiveTypes) - You are doing offline/batch validation with a fully-resolved document — use a plain
z.string().refine(v => knownSet.has(v))instead which is simpler and faster. (zRef) - You do not need cross-reference validation at all — omit
crossRefValidationin the config to skip generating these factories entirely. (zRef) - You are validating Langium's
$container/$documentmetadata fields — those are not cross-references and should not be validated withzRef. (zRef) - You do not need structured context — a plain
Error.messagecheck is sufficient for simple pipelines. (ZodGeneratorError) - You need the full ZodGeneratorConfig surface (projection, conformance, formMetadata, etc.) — use generateZodSchemas directly. (
DefaultZodSchemaGenerator)
API surface: 6 functions, 2 classes, 9 types, 2 constants
NEVER
- NEVER omit both
grammarandastTypes— the function throws ZodGeneratorError immediately. BECAUSE there is no default grammar source and no way to recover silently. FIX: provide at least{ grammar: parsedGrammar }or{ astTypes: collectAst(grammar) }. - NEVER enable
conformancewithout settingoutputPath— the function will throw before writing any output. BECAUSE the conformance module needs to derive a sibling output path from the schema file's directory. FIX: always setoutputPathwhenconformanceis truthy. - NEVER pass a
Grammar[]array when grammars share type names across files without verifying that Langium'scollectAst()merges them correctly. BECAUSE duplicate type names will silently overwrite each other in the type map, producing truncated schemas. FIX: runcollectAstseparately and inspect the merged map before generation. - NEVER call with
crossRefValidation: trueon grammars with no cross-reference properties — it emits deadcreate*Schemafactory functions that add noise without benefit. FIX: only enablecrossRefValidationwhen your grammar has at least oneref:property. - NEVER remove the
// @ts-nocheckcomment from generated output files. BECAUSE the getter-based recursive property syntax (emitted for self-referential types) is not always accepted by TypeScript's strict object-literal type checker — removing the comment causes immediate TS build failures in grammars with recursive rules. FIX: treat generated files as opaque artifacts; place any hand-written extensions in a separate file that imports the schema. - NEVER commit generated schemas as the sole copy of your schema logic. BECAUSE any grammar edit (new rule, renamed property, changed cardinality) produces stale schemas that pass TypeScript but fail at Zod validation runtime. FIX: wire
langium-zod generateas a pre-build or CI step so schema freshness is enforced automatically. - NEVER filter by
includewithout including stub types that are transitively referenced (e.g.ValidID). BECAUSE phase 3 only emits stubs for names thatshouldInclude()passes; missing stubs produceundefinedschema references at code-gen time. - NEVER assume the returned array order matches the grammar declaration order. BECAUSE the array is grouped by phase (object, then union, then stub); use generateZodCode which topologically sorts object schemas.
- NEVER mutate the returned descriptors and re-pass them to extraction — descriptors are consumed by the generator as values, but the super-type resolution cache lives inside a single
extractTypeDescriptorscall; mutations do not propagate. - NEVER pass a grammar with a union type whose only member is itself filtered out by
include. BECAUSE the union will have zero members and produce a broken discriminated union schema. - NEVER pass a
recursiveTypesset that was computed from a different descriptor set thandescriptors. BECAUSE the generator uses the set to decide which properties need getter syntax; a stale set will produceconstdeclarations that reference variables before they are initialised, causing runtime errors. - NEVER rely on emission order outside the documented phases. BECAUSE topological sort is applied only to object descriptors; union and enum schemas appear in their extraction order. Post-processing the string is fragile — transform descriptors before calling this function instead.
- NEVER assume
formMetadata: trueaddsdescriptionto every property. BECAUSEdescriptionis only included when the grammar comment for that property/type is non-empty; title is always emitted viahumanize-string. - NEVER pass descriptors that have already had projection applied (via
applyProjectionToDescriptors) to this function if the projection strips properties that close cycles. BECAUSE the cycle detection graph will miss the back-edge and fail to mark those types as recursive, leading toundefinedreference errors in the generated schemas at runtime. - NEVER assume the returned set is stable across different filter configurations. BECAUSE filtering with
include/excludecan remove types that close a cycle, making previously recursive types appear acyclic. - NEVER pass a static snapshot of the reference array when calling
zRefinside a Langium validator that runs repeatedly. BECAUSE the snapshot will not reflect document edits; pass a getter() => myLiveListinstead. - NEVER expect
zRefto fail on empty strings. BECAUSE empty strings are intentionally allowed to represent unresolved/placeholder references — this matches Langium's own handling of incomplete cross-references during editing. - NEVER use
zRefas the sole cross-reference validation mechanism in a security context. BECAUSE it only checks string membership; it does not validate that the referenced object is of the correct type or that it exists in the correct scope. - NEVER use
instanceof ZodGeneratorErrorin a plugin host that bundles its own copy of langium-zod. BECAUSEinstanceoffails across module boundaries when multiple instances of the class exist; useerror.name === 'ZodGeneratorError'instead. - NEVER call
new DefaultZodSchemaGenerator(services)manually in production code if you are already using the DI container. BECAUSE the container may inject a different instance (e.g. a mock), and constructing a second instance bypasses DI overrides set up for tests.
Configuration
4 configuration interfaces — see references/config.md for details.
Quick Reference
Generation: generateZodSchemas (Main entry point for programmatic Zod schema generation from a Langium grammar), generateZodCode (Generates a TypeScript source string containing Zod schema exports for all
provided type descriptors), zRef (Creates a Zod string schema that validates a cross-reference value against an
allowlist of known identifiers, evaluated lazily at parse time), ZodGeneratorError (Custom error class thrown by the langium-zod code generator when it
encounters a condition it cannot recover from)
Analysis: extractTypeDescriptors (Extracts ZodTypeDescriptor records from a Langium grammar's type model), detectRecursiveTypes (Detects type names that participate in a reference cycle across the descriptor
graph), AstTypesLike (Duck-typed representation of the type model returned by Langium's collectAst()
function), InterfaceTypeLike (Duck-typed representation of a Langium InterfaceType, carrying only the fields
that langium-zod needs), ZodTypeDescriptor (Union of all type descriptor shapes that the extractor can produce and the
code generator can consume), ZodTypeExpression (A discriminated union that represents a single Zod type node in the descriptor
tree produced by the extractor and consumed by the code generator)
cli: generate (Programmatic entry point for the langium-zod generate command)
DI: DefaultZodSchemaGenerator (Default implementation of ZodSchemaGenerator), ZodSchemaGenerator (Service interface for generating Zod schemas from a parsed Langium grammar), ZodSchemaGeneratorServices (Langium DI service container shape for the langium-zod extension), ZodSchemaGeneratorModule (Langium Module definition that registers DefaultZodSchemaGenerator
under shared) **types:** PropertyLike(Duck-typed representation of a single property within a LangiumInterfaceType), UnionTypeLike(Duck-typed representation of a LangiumUnionType(including datatype rules that alias primitives or terminal regex patterns)),ZodPropertyDescriptor(Describes a single property of a Langium interface type after extraction, capturing all information the code generator needs to emit a Zod property expression) **Configuration:**DEFAULT_OUTPUT_PATH(Default output path used when no explicitoutputPathis provided and the project'slangium-config)
References
Load these on demand — do NOT read all at once:
- When calling any function → read
references/functions.mdfor full signatures, parameters, and return types - When using a class → read
references/classes/for properties, methods, and inheritance - When defining typed variables or function parameters → read
references/types.md - When using exported constants → read
references/variables.md - When configuring options → read
references/config.mdfor all settings and defaults
Links
- Repository
- Author: Pradeep Mouli pmouli@mac.com (https://github.com/pradeepmouli)