Skip to content
zodline
Esc
navigateopen⌘Jpreview
On this page

Options

Declare flags with defineOptions — aliases, kebab-case keys, arrays, defaults, and coercion.

Options are the flags a command accepts. defineOptions pairs a Zod object schema with an optional alias map:

import { z } from 'zod';
import { defineOptions } from 'zodline';

const options = defineOptions(
  z.object({
    name: z.string().describe('Name to greet'),
    loud: z.boolean().default(false).describe('Use uppercase'),
  }),
  { n: 'name', l: 'loud' },
);

The alias map is { shortFlag: schemaKey } — read it as “-n means --name”.

Values arrive as strings

zodline hands Zod exactly what the shell produced: every flag value is a string, and a flag with no value is the boolean true. Schemas that expect other types need coercion.

z.object({
  port: z.coerce.number().default(3000).describe('Port to listen on'),
  retries: z.coerce.number().int().min(0).default(3).describe('Retry count'),
  since: z.coerce.date().optional().describe('Only include entries after this date'),
});
my-cli serve --port 8080    # options.port === 8080 (number)

Boolean flags

A flag whose next argument is absent or starts with - becomes true. That makes z.boolean() right for presence-only switches:

z.object({
  loud: z.boolean().default(false).describe('Use uppercase'),
});
my-cli greet --name World --loud     # loud: true
my-cli greet --name World            # loud: false (from the default)

To accept an explicit value, use z.stringbool(), which parses "true"/"false" and friends:

z.object({
  color: z.stringbool().default(true).describe('Colorize output'),
});
my-cli build --color false    # color: false

kebab-case keys

Schema keys are camelCase; command lines are kebab-case. zodline maps between them, so no extra configuration is needed:

z.object({
  androidMax: z.coerce.number().optional().describe('Maximum Android version'),
  dryRun: z.boolean().default(false).describe('Do not write anything'),
});
my-cli deploy --android-max 34 --dry-run
# { androidMax: 34, dryRun: true }

Both spellings are accepted. If both appear in one invocation, the camelCase one wins.

Aliases

Any option can have a short form. Aliases are resolved before validation, so the schema only ever sees the long key:

defineOptions(
  z.object({
    verbose: z.boolean().default(false).describe('Show detailed output'),
    output: z.string().optional().describe('Write results to this file'),
  }),
  { v: 'verbose', o: 'output' },
);
my-cli build -v -o out.json

Short flags cluster: -abc is three boolean flags, a, b and c. If both -v and --verbose are passed, the alias wins.

Arrays

Repeat a flag to collect values. A single occurrence is wrapped into an array automatically, so the schema does not need a union:

z.object({
  customProperty: z.array(z.string()).default([]).describe('key=value pair, repeatable'),
});
my-cli deploy --custom-property key1=value1 --custom-property key2=value2
# { customProperty: ['key1=value1', 'key2=value2'] }

my-cli deploy --custom-property key1=value1
# { customProperty: ['key1=value1'] }

Normalization looks through .optional() and .default() wrappers, so z.array(z.string()).optional() behaves the same way.

Defaults and optionality

.default() fills a value in when the flag is absent and shows up in the generated help as (default: …). .optional() leaves the key undefined.

z.object({
  format: z.enum(['json', 'text']).default('text').describe('Output format'),
  config: z.string().optional().describe('Path to a config file'),
});

Unknown options are rejected

Any flag that is not a schema key (in either spelling), an alias, or help/version throws before validation runs:

my-cli greet --name World --shout
# Error: Unknown option: --shout

This holds for commands that declare no options at all. See Error handling for the error types.

Resolution order

Understanding the pipeline explains most edge cases:

Unknown-option check

Runs against the raw flag names, before anything is rewritten.

Alias resolution

-v becomes verbose; the alias value wins over an explicit long form.

kebab-case conversion

android-max becomes androidMax, unless androidMax is already set.

Array normalization

Single values for array-typed fields are wrapped in an array.

Schema validation

schema.parse() applies coercion, defaults and refinements.

Next: Arguments

Validate positional arguments with tuples, arrays and unions.

Last updated on August 6, 2026

Was this page helpful?