commander
Commander is the most-installed argument parser for Node.js command line programs. You declare options, arguments, and subcommands on a Command object; it parses process.argv, generates help text, errors on unrecognised options, and even suggests the closest match for typos. It covers the whole small-to-medium CLI space in one zero-dependency package.
The default choice for Node CLIs and it earned that spot: small, complete, and boring in the good way. Use built-in parseArgs for trivial scripts and oclif for plugin platforms; use commander for everything in between.
Use it if
- You are building a Node CLI with flags, subcommands, and auto-generated help and want one dependency that does all of it
- You want strict parsing that rejects unknown options and prints a did-you-mean suggestion
- You need option niceties like env-variable fallback, choices, conflicts, and implied values without writing glue code
- You publish CLI tools and care about install size: commander has zero dependencies
- You must support Node older than 22.12: commander 15 requires at least v22.12.0, so you would be pinning an old major and reading archived docs
- You are parsing two or three flags in a script: Node's built-in util.parseArgs does that with no dependency at all
- You want a full CLI framework with plugins, generated doc sites, and multi-command workspaces: that is oclif territory, commander stops at parsing and help
- You expect interactive prompts, spinners, or colored output: commander does none of that, so you will be adding other packages anyway
Setup reality
npm install commander and you are parsing in five lines; there are no peer dependencies and TypeScript definitions ship in the box. The friction is elsewhere: the API has accumulated overlapping ways to declare options (option, addOption, requiredOption), recent majors exist largely to track supported Node LTS versions, and if any action handler is async you must remember to call parseAsync instead of parse or rejections escape silently.
Patterns
Parse basic optionsparse-flags
import { program } from 'commander';
program
.option('--verbose')
.option('-p, --port <number>', 'port to listen on', '3000');
program.parse();
const opts = program.opts(); // { verbose: true, port: '3000' }Option values arrive as strings; pass a parser function as the third-ish argument (after the default) if you need numbers.
Add a subcommand with its own actionsubcommands
import { Command } from 'commander';
const program = new Command();
program.name('tool').version('1.0.0');
program.command('deploy')
.description('deploy to an environment')
.argument('<target>', 'environment name')
.option('--dry-run')
.action((target, options) => {
console.log(target, options.dryRun);
});
program.parse();Options declared on a subcommand are only visible in that subcommand's handler, not on the parent program.
Make an option mandatoryrequired-option
program.requiredOption('-c, --cheese <type>', 'pizza must have cheese');
program.parse();Required means present after parsing, so a default value or env fallback also satisfies it.
Choices, env fallback, and conflictsoption-constraints
import { Command, Option } from 'commander';
const program = new Command();
program
.addOption(new Option('-d, --drink <size>', 'drink size').choices(['small', 'medium', 'large']))
.addOption(new Option('-p, --port <number>', 'port number').env('PORT'))
.addOption(new Option('--disable-server', 'disables the server').conflicts('port'));
program.parse();These extras only exist on the Option class, so you must use addOption rather than the plain .option() shorthand.
Validate and coerce an argumentvalidate-argument
import { program, InvalidArgumentError } from 'commander';
function myParseInt(value) {
const parsed = parseInt(value, 10);
if (isNaN(parsed)) throw new InvalidArgumentError('Not a number.');
return parsed;
}
program.argument('<port>', 'port number', myParseInt);
program.parse();
console.log(program.processedArgs[0]);Throw InvalidArgumentError (not a plain Error) to get commander's standard usage-error output.
Use an async action handlerasync-action
program
.command('fetch <url>')
.action(async (url) => {
const res = await fetch(url);
console.log(res.status);
});
await program.parseAsync(process.argv);If any handler is async you must call parseAsync, not parse, or the promise (and its errors) is dropped.
Run setup before any actionlifecycle-hooks
program
.option('--trace')
.hook('preAction', (thisCommand, actionCommand) => {
if (thisCommand.opts().trace) {
console.log(`about to run ${actionCommand.name()}`);
}
});preAction/postAction fire for the command and its nested subcommands; hooks can be async if you use parseAsync.
Accept multiple values for one optionvariadic-option
program.option('-n, --number <numbers...>', 'specify numbers');
program.parse();
// tool -n 1 2 3 -> program.opts().number === ['1', '2', '3']A variadic option greedily consumes following args until the next dash, which surprises users mixing it with positional arguments.
Offer a --no- prefix to turn a flag offnegatable-boolean
program
.option('--no-sauce', 'Remove sauce')
.option('--cheese <flavour>', 'cheese flavour', 'mozzarella')
.option('--no-cheese', 'plain with no cheese');
program.parse();
// sauce defaults to true; --no-sauce sets it to falseDefining only the --no- form makes the option default to true, which reads backwards until you get used to it.
Catch exits instead of killing the test processtest-cli
program.exitOverride();
try {
program.parse(['--unknown'], { from: 'user' });
} catch (err) {
// err.code === 'commander.unknownOption'
}exitOverride throws a CommanderError instead of calling process.exit, which is the supported way to unit test a CLI.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| yargs | npm | You want command modules, built-in shell completion, and a config-object style instead of chained methods. |
| cac | npm | You want a commander-like API in a smaller package and only need the common 80 percent of features. |
| oclif | npm | You are building a large multi-command CLI product with plugins and generated docs, not just parsing args. |