Skip to content

Arguments Metadata

Padrone uses Zod schemas with .meta() to configure CLI-specific behavior. This reference covers all available metadata configuration.

Use .meta() on individual Zod schema properties to configure their CLI behavior:

z.object({
output: z.string()
.describe('Output file path')
.meta({
flags: 'o',
alias: 'out',
examples: ['output.json', './dist/bundle.js'],
}),
})
PropertyTypeDescription
flagsstring | string[]Single-character short flags (e.g., 'p' for -p). Stackable: -abc = -a -b -c
aliasstring | string[]Multi-character long aliases (e.g., 'dry-run' for --dry-run)
negativestring | string[]Custom negative keyword(s) for booleans. Disables --no- prefix
examplesunknown[]Example values shown in help
deprecatedstring | booleanMark as deprecated with optional message
hiddenbooleanHide from help output
groupstringGroup name for organizing under a labeled section in help output

The second argument to .arguments() configures positional arguments and per-argument metadata:

.arguments(schema, {
positional: ['source', '...files', 'dest'],
fields: {
verbose: { flags: 'v' },
dryRun: { alias: 'dry' },
format: { deprecated: 'Use --output instead' },
},
})

Array of argument names to accept as positional arguments.

{ positional: ['source', 'dest'] }

Positional argument order:

  • Arguments are matched in the order specified
  • Optional arguments are skipped if not provided
  • Position matters: ['source', 'dest'] means first arg is source, second is dest

Variadic arguments:

  • Prefix with ... to capture multiple values: ['...files']
  • Variadic args must be arrays in the schema: z.array(z.string())
  • Only one variadic argument is allowed per command
  • Variadic can be at any position
// Capture all args between fixed positions
{ positional: ['command', '...args', 'output'] }
// command = first arg
// args = all middle args
// output = last arg

Per-argument configuration that supplements or overrides .meta():

{
fields: {
verbose: { flags: 'v' },
dryRun: { alias: 'dry' },
format: {
deprecated: 'Use --output instead',
hidden: true,
},
},
}

This is equivalent to using .meta() on the schema property but allows configuration to be kept separate from the schema definition. Fields accept the same properties as Zod .meta(): flags, alias, negative, description, examples, deprecated, hidden, group.

Automatically generate kebab-case aliases for camelCase argument names. Enabled by default.

// Default (autoAlias: true): --dry-run automatically maps to dryRun
.arguments(z.object({ dryRun: z.boolean() }))
// Disable auto-aliases
.arguments(z.object({ dryRun: z.boolean() }), { autoAlias: false })

Read from stdin and inject the data into a specified argument field. Only reads when stdin is piped (not a TTY) and the field wasn’t already provided via CLI flags. The read mode is inferred from the schema: string fields read all stdin as text, string[] fields read line-by-line.

// Read all stdin as text into 'data' field
.arguments(z.object({ data: z.string() }), { stdin: 'data' })
// Read stdin line-by-line into an array field (inferred from array schema)
.arguments(
z.object({ lines: z.array(z.string()) }),
{ stdin: 'lines' }
)
// Stream stdin lazily as AsyncIterable (for large inputs)
import { zodAsyncStream } from 'padrone/zod';
.arguments(
z.object({ lines: zodAsyncStream() }),
{ stdin: 'lines' }
)
// Typed stream with JSON codec (each line JSON.parse'd and validated)
import { zodAsyncStream, jsonCodec } from 'padrone/zod';
const itemSchema = z.object({ name: z.string(), age: z.number() });
.arguments(
z.object({ records: zodAsyncStream(jsonCodec(itemSchema)) }),
{ stdin: 'records' }
)

Declare which fields should be interactively prompted when their values are missing after CLI/env/config resolution. Only takes effect in cli() and eval() when the runtime has interactive: true.

// Prompt all missing required fields
{ interactive: true }
// Prompt specific fields
{ interactive: ['name', 'template'] }

When interactive is set, parse() and cli() return Promises (the command becomes async).

Prompt types are auto-detected from the schema:

Schema TypePrompt
z.boolean()Confirm (yes/no)
z.enum([...])Select (single choice)
z.array(z.enum([...]))Multi-select
z.string()Text input
Any other typeText input

The prompt message is derived from the field’s .describe() text, or from fields meta description, falling back to the field name.

Additional fields offered after required interactive prompts. Users are shown a multi-select to choose which of these fields to configure.

// Offer all missing optional fields
{ optionalInteractive: true }
// Offer specific fields
{ optionalInteractive: ['verbose', 'format'] }

Example combining both:

.arguments(
z.object({
name: z.string().describe('Project name'),
template: z.enum(['react', 'vue', 'svelte']).describe('Starter template'),
typescript: z.boolean().default(false).describe('Use TypeScript'),
eslint: z.boolean().default(false).describe('Add ESLint'),
}),
{
positional: ['name'],
interactive: ['name', 'template'],
optionalInteractive: ['typescript', 'eslint'],
}
)

When running without arguments, this will:

  1. Prompt for name and template (required interactive fields)
  2. Show a multi-select: “Would you also like to configure: TypeScript, ESLint”
  3. Prompt individually for any selected optional fields

See the Interactive Prompting guide for full details.


Bind arguments to environment variables using the padroneEnv extension:

import { createPadrone, padroneEnv } from 'padrone';
import * as z from 'zod/v4';
const program = createPadrone('app')
.command('serve', (c) =>
c
.arguments(
z.object({
port: z.number().default(3000),
apiKey: z.string(),
}),
)
.extend(
padroneEnv(
z.object({
APP_PORT: z.coerce.number().optional(),
API_KEY: z.string().optional(),
}).transform((env) => ({
port: env.APP_PORT,
apiKey: env.API_KEY,
}))
)
)
.action((args) => {
console.log(`Server on port ${args.port}`);
}),
);

The env schema validates process.env and transforms env var names into argument names. Only provided env values are used — undefined values are skipped. padroneEnv can be applied at the program level (inherited by all commands) or at the command level.

Resolution priority:

  1. CLI argument (highest)
  2. Stdin
  3. Environment variable
  4. Config file
  5. Interactive prompt (if runtime supports it)
  6. Default value (lowest)

Load arguments from configuration files using the padroneConfig extension:

import { createPadrone, padroneConfig } from 'padrone';
import * as z from 'zod/v4';
const program = createPadrone('app')
.command('serve', (c) =>
c
.arguments(
z.object({
port: z.number().default(3000),
host: z.string().default('localhost'),
}),
)
.extend(
padroneConfig({
files: 'app.config.json',
schema: z.object({
port: z.number().optional(),
host: z.string().optional(),
}),
})
)
.action((args) => {
console.log(`Server on ${args.host}:${args.port}`);
}),
);

Multiple config file paths can be provided in the files array — the first existing file is used:

.extend(padroneConfig({ files: ['app.config.json', '.apprc'] }))

If no schema is provided, the config file values are matched against the command’s argument schema directly.


Short argument flags allow single-character shortcuts:

z.object({
verbose: z.boolean().optional().meta({ flags: 'v' }),
port: z.number().default(3000).meta({ flags: 'p' }),
output: z.string().optional().meta({ flags: 'o' }),
})

Usage:

Terminal window
app -v -p 8080 -o output.json
# Equivalent to:
app --verbose --port 8080 --output output.json

Combined short flags:

Terminal window
app -vp 8080
# -v (boolean) + -p 8080

Multi-character long aliases provide alternative names for arguments:

z.object({
dryRun: z.boolean().optional().meta({ alias: 'dry' }),
})
Terminal window
app --dry
# Equivalent to:
app --dry-run # (auto-alias from camelCase)
app --dryRun # (original name)

By default, boolean arguments can be negated with the --no- prefix (e.g., --no-verbose). The negative meta option lets you define custom keyword(s) that set a boolean to false, while disabling the default --no- prefix:

z.object({
local: z.boolean().default(true).meta({ negative: 'remote' }),
})
Terminal window
app --remote
# Equivalent to: local = false
# --no-local is NOT recognized (disabled by custom negation)
app --local
# local = true (still works normally)

Multiple negative keywords:

z.object({
local: z.boolean().default(true).meta({ negative: ['remote', 'cloud'] }),
})
Terminal window
app --remote # local = false
app --cloud # local = false

Disable --no- prefix only:

Set negative to an empty string or empty array to disable the --no- prefix without adding any alternative keywords:

z.object({
verbose: z.boolean().default(true).meta({ negative: '' }),
})
Terminal window
app --verbose # verbose = true
app --no-verbose # Error: unknown option

Custom negation can also be set via fields meta:

.arguments(z.object({ local: z.boolean().default(true) }), {
fields: { local: { negative: 'remote' } },
})

The stringify() method uses the first negative keyword when serializing false values:

program.stringify('cmd', { local: false })
// → "cmd --remote" (instead of "cmd --no-local")

Mark arguments as deprecated to warn users:

z.object({
// Simple deprecation
old: z.string().optional().meta({ deprecated: true }),
// With migration message
legacy: z.string().optional().meta({
deprecated: 'Use --new-arg instead',
}),
})

Deprecated arguments still work but display a warning when used.


Hide arguments from help output while keeping them functional:

z.object({
// Visible in help
port: z.number().default(3000),
// Hidden from help
debug: z.boolean().optional().meta({ hidden: true }),
internalFlag: z.string().optional().meta({ hidden: true }),
})

Hidden arguments:

  • Don’t appear in --help output
  • Still work when specified
  • Useful for internal/experimental features

Provide example values for help text:

z.object({
format: z.enum(['json', 'csv', 'xml'])
.describe('Output format')
.meta({ examples: ['json', 'csv'] }),
date: z.string()
.describe('Date filter')
.meta({ examples: ['2024-01-01', 'today', 'last-week'] }),
})

Examples appear in the generated help text to guide users.


Organize arguments into labeled sections in help output:

z.object({
port: z.number().default(3000).meta({ group: 'Server' }),
host: z.string().default('localhost').meta({ group: 'Server' }),
verbose: z.boolean().optional().meta({ group: 'Debug' }),
logLevel: z.enum(['info', 'debug', 'warn']).optional().meta({ group: 'Debug' }),
})

Arguments with the same group name are displayed together under a labeled section in help output.