Outputs
Updated Apr 30, 2026
.output(z.object({...})) defines the shape of whatever the handler
returns. There's no f.argument() / f.option() equivalent for outputs —
outputs aren't CLI inputs, so commander has no per-field surface to
configure. The schema is pure data shape.
.output(z.object({
greeting: z.string(),
iterations: z.number(),
}))The output schema must be a z.object(...). Returning a primitive (string,
number, etc.) directly isn't supported — wrap it in an object so JSON
output is always navigable by key.
Where the output schema is used
- Validation — the handler's return value is parsed through the schema
on every invocation. Type mismatches throw at runtime through
commander's
parseAsync. Zod's transforms apply, soz.string().transform(s => s.toUpperCase())post-processes the return. --jsonserialization — when--jsonis set, the validated value is serialized viaJSON.stringify(value, null, 2)and written to commander'swriteOut. See JSON mode.--llmsmanifest — the schema is run throughz.toJSONSchema(...)and surfaced as the tool'soutputSchema. See LLMs mode.
Serialization
JSON.stringify does most of the work. Native types that need attention:
| Type | What JSON.stringify does | Recommendation |
|---|---|---|
Date | calls .toJSON() → ISO 8601 string | use z.iso.datetime() to document the wire shape (and accept the same on input) |
BigInt | throws (TypeError) | use z.coerce.bigint() and stringify yourself, or store as string in the schema |
Map/Set | become {} / [] (lossy) | model as z.record(...) / z.array(...) |
undefined | property dropped | use .optional() deliberately; null if you want presence |
Function/Symbol | property dropped | don't return these from a handler |
If a richer wire format is needed (preserve Date/BigInt round-trip
faithfully), the right place is not the output schema — it's a
serialization layer like superjson. fireargs deliberately stays plain-JSON
so the wire is consumable by any LLM, shell, or non-fireargs client.
Round-trip caveat for Date
z.date() makes round-trip with --json awkward and is unrepresentable
in z.toJSONSchema(...) (Zod throws). Prefer:
.output(z.object({
createdAt: z.iso.datetime(), // ISO string in, ISO string out
}))…or transform on output:
.output(z.object({
createdAt: z.string(),
}))
.handler(() => ({ createdAt: new Date().toISOString() }))Tracked in issue #1.
What flows to --llms
Per-property, the JSON Schema in the manifest carries:
type,format,pattern— derived from the zod constructor.description— from.describe(...)and.meta({ description }).enum— fromz.enum([...]).required— derived from non-optional/ non-defaultfields.
There's no CLI-side metadata to strip on outputs — f.argument() /
f.option() are inputs-only — so every key zod emits passes through.
Why no separate slots like f.argument()/f.option()?
Commander's Command has no output abstraction. CLI users see whatever
your handler prints (or what --json serializes); there's nothing for
fireargs to "wire" beyond running the validation. Keeping outputs as plain
zod schemas matches commander's surface and avoids inventing concepts the
runtime can't honor.
Created with ❤ and Livemark