z
import { z } from 'clibuilder'clibuilder re-exports zod as z. Import it from clibuilder
rather than adding zod as your own dependency — that guarantees the schemas you build are instances
of the same zod the library validates with, which is the usual cause of “this schema isn’t being
recognized” bugs.
Where schemas are used
Section titled “Where schemas are used”| Position | What the schema does |
|---|---|
An argument’s type |
Coerces and validates the value, marks it optional (z.optional) or variadic (z.array), and types args.<name> |
An option’s type |
Coerces and validates the value, and types args.<name> |
A command’s config |
Validates the loaded config file, and types this.config |
Types the parser understands
Section titled “Types the parser understands”For arguments and options, the parser converts raw argv strings using these:
z.string()z.number()z.boolean()z.array(z.string()),z.array(z.number()),z.array(z.boolean())z.optional(...)around any of the above
Richer zod types — refinements, unions, transforms — get no string conversion. The raw argv string is
handed to the schema as-is, so ones that accept a string (z.enum, z.literal('on')) validate
correctly, while ones expecting a converted value do not. Do that validation inside run() instead.
Config schemas have no such restriction: the config file is already structured data, so any zod schema works.
config: z.object({ presets: z.string(), port: z.number().default(3000), mode: z.union([z.literal('fast'), z.literal('thorough')])})Inference
Section titled “Inference”z.infer<> is what makes the types flow. An option declared as type: z.number() produces
args.port: number; a config declared as z.object({ presets: z.string() }) produces
this.config: { presets: string }. You never write those types out.