Search
Search documentation
JSON mode
Updated Oct 21, 2026
--json is a built-in flag on every fireargs command. It lets the same
binary serve human users at the shell, scripts in a pipeline, and LLM
agents — all backed by one zod-validated handler.
Enabled by default. Disable per-command with jsonOption: false, or
customize the flag string/description with jsonOption: { flags?, description? }.
Three modes
greet world # CLI in, .render output (if any)
greet world --json # CLI in, JSON out
greet --json '{"name":"world"}' # JSON in, JSON outThe flag takes an optional value (--json [value]):
- Absent — normal CLI parsing, handler runs, and the validated output
goes to
.render(...)if the command has one; otherwise nothing is printed. - Present without a value — CLI parsing for input, but the handler's return is serialized as JSON to stdout.
- Present with a JSON value — input comes from the JSON blob; CLI positionals/options are skipped, so required positionals may be left off; output is JSON.
What happens on --json '<value>'
JSON.parse's the value.- Validates the parsed object through the input zod schema.
- Calls the handler with the validated input and
context.json === true. - Validates the handler's return through the output schema.
- Writes
JSON.stringify(result, null, 2) + "\n"to commander'swriteOut—configureOutput.writeOutcan intercept it (handy for tests).
When --json is passed alone (no value), step 1 is skipped and input is
collected from the regular CLI parse instead. Steps 3–5 are identical.
Keeping stdout clean
The handler's second argument tells it which mode it runs in. In JSON mode,
anything else written to stdout corrupts the result, so spinners, colors and
progress output should check context.json:
.handler(async (input, context) => {
const spinner = context.json ? undefined : startSpinner("Loading")
// ...
})Errors
Without a hook, a value that is not JSON and input that fails the zod schema are reported like a commander parse error, written to stderr with exit code 1:
error: --json value is not valid JSON: ...
error: invalid --json input at 'a.b[0]': Invalid input: expected number, received stringWith exitOverride: true they throw a CommanderError instead, coded
fireargs.invalidJson or fireargs.invalidInput. Output-schema failures and
handler exceptions reject the returned Command's parseAsync(...).
Set renderError in the command's or program's config to turn all of them
into output instead — the hook receives the original SyntaxError or
ZodError for input failures:
f.program({
name: "myapp",
renderError: (error, context) => {
const message = error instanceof Error ? error.message : String(error)
return context.json ? { error: message } : `error: ${message}`
},
})- In JSON mode a returned value is written as JSON to
writeOut; otherwise a returned string is written towriteErr. Return nothing to write it yourself; rethrow to rejectparseAsync(...)as before. process.exitCodeis then set to1. The process is not exited, so a large payload still drains into a pipe — andexitOverridedoes not apply.- It also catches
.render(...)failures. The nearest hook wins: the leaf's own, then each enclosing program's.
Commander parse errors (unknown option, bad choice, missing positional, too
many arguments) happen before the run starts: they are written to stderr and
exit with code 1, or throw a CommanderError with exitOverride: true.
Composes with subcommands
Every leaf in a program tree gets its own --json:
myapp greet --json '{"name":"world"}'
myapp api deploy --json '{"env":"prod"}'The program (root) doesn't have its own --json — programs dispatch to
subcommands; only leaves take input. See Programs.
Pairs with --llms
Together with LLMs mode, --json is the call-time half of
fireargs's agentic interface: an LLM reads the --llms manifest to learn
the schemas, then calls back through --json with a value matching the
documented inputSchema.
Created with ❤ and Livemark