Concepts

Validate CLI input

Validate command-line values with Zod and handle missing input.

On this page

Give an option or positional argument a Zod schema to check the value a user supplies. The schema can also convert text, such as turning --count 3 into a number.

In src/cli.ts, a Zod schema can convert an option’s string into a number and reject invalid input. The detected check distinguishes an omitted option from a supplied value.

TypeScript
import { option, parser } from '@hulla/args'
import { argv } from 'node:process'
import { z } from 'zod'

const cli = parser({
  name: 'serve',
  arguments: [option({ name: 'port', schema: z.coerce.number().int().min(1) })],
})

const portArg = cli.parse(argv.slice(2)).arguments.port
const port = portArg.detected ? portArg.value : 3000
console.log(port)

Try the command with three inputs:

CommandResult
bun src/cli.ts --port 8080Prints 8080.
bun src/cli.tsPrints the fallback, 3000.
bun src/cli.ts --port nopeThrows a ParserError from Zod validation.
Equals-form values can lose their casing

With the default caseSensitive: false, --label=Sam becomes sam before validation. Space-separated values and positionals keep their spelling, and raw keeps the original token. Set caseSensitive: true to preserve equals-form values too; argument names will then be case-sensitive.

Schemas only check supplied input

An omitted option or positional has detected: false and value: undefined; its schema isn’t called. Check detected to require it. Flags always validate a boolean, including false when absent.

Use detected to require that the user supplied a positional. A schema such as .min(1) validates a present string, but it cannot reject an omitted positional because the schema is skipped in that case.

TypeScript
import { ParserError, parser, positional } from '@hulla/args'
import { argv } from 'node:process'
import { z } from 'zod'

const cli = parser({
  name: 'notes',
  arguments: [
    positional({ name: 'title', schema: z.string().min(1, 'Enter a title') }),
  ],
})

Parse at the application’s entrypoint. Check for a missing title explicitly, and catch validation errors from a title that was supplied:

TypeScript
try {
  const title = cli.parse(argv.slice(2)).arguments.title
  if (title.detected) {
    console.log(title.value)
  } else {
    console.error('A title is required')
    process.exitCode = 1
  }
} catch (error) {
  if (error instanceof ParserError) {
    console.error(error.message)
    process.exitCode = 1
  } else {
    throw error
  }
}

When a title is absent, the CLI’s explicit check reports A title is required. An empty title is present, so Zod rejects it with Enter a title. Catch parser errors at the CLI boundary to choose the message and exit policy; @hulla/args does not print help or set the process exit code.

Zod validates values during parsing; it does not execute command handlers. See arguments for the available input shapes, and commands to route a validated result.