mrkeyoor.com_
Wed 05 Aug 05:03 UTC
npmCLI & Toolingupdated 05 Aug 2026

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.

Verdict

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.

API stability4/5The Command/Option API has been steady for years; majors land regularly but mostly raise the minimum Node version rather than change parsing behavior.
Docs5/5One long README covers nearly every feature with runnable examples, backed by an examples directory and extra docs pages; there is no separate site to go stale.
Maintenance5/5Pushed days ago (August 2026), v15 current, and only 4 open issues on the tracker despite half a billion weekly downloads.
Ecosystem4/5At 475M+ weekly downloads every question has an answer somewhere and every tool integrates with it; there is no plugin ecosystem, but it does not need one.

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
Skip it if

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 false

Defining 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

PackageRegistryPick it when
yargsnpmYou want command modules, built-in shell completion, and a config-object style instead of chained methods.
cacnpmYou want a commander-like API in a smaller package and only need the common 80 percent of features.
oclifnpmYou are building a large multi-command CLI product with plugins and generated docs, not just parsing args.