Skip to content

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.

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

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')])
})

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.