mrkeyoor.com_
Sat 19 Sept 08:54 UTC
npmCLI & Toolingupdated 19 Sept 2026

commander review

Commander turns a Node.js argv array into named options, positional arguments, and subcommand actions. Declarations also generate usage text, validate required values and choices, reject unknown flags, and suggest nearby option names. Version 15 is an ESM-only implementation and requires Node 22.12 or newer; Node's require(esm) support lets current CommonJS programs call require('commander'). The release also reports surplus command arguments more clearly and changes the default behavior when positive and negative forms of the same option are both declared.

462.6Mdownloads / wk
Verdict

Commander fits ordinary Node CLI products that need real help and subcommands without adopting a full application framework. Version 15 is an easy choice only after the Node 22.12 runtime floor and ESM behavior are acceptable.

We installed it

Lab card: what happened when we installed commanderScreenshot of commander documentation
Install✓ · 0.3s1 package on disk · 1 MB
ImportESM import works · require() works · ESM package with exports map
Browsern/acould not be bundled for the browser (Node-only code, most likely)
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does commander install cleanly?

Yes. In a fresh container with an empty cache, npm install commander finished in 0.3s, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.

Can commander run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does commander work with both ESM and CommonJS?

Yes. Both import 'commander' and require('commander') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does commander include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

commander or yargs: which should you use?

yargs: Choose it for command modules, middleware, completion support, and object-style declarations. Commander fits ordinary Node CLI products that need real help and subcommands without adopting a full application framework.

When should you not use commander?

Your supported runtime is below Node 22.12; Commander 15 will not install within that engine policy, while Commander 14 receives security fixes only through May 2027

API stability4/5Command, Option, argument declarations, action handlers, and opts() remain the center of the API. Version 15 changes packaging to ESM, raises Node to 22.12, removes commander/esm.mjs, and corrects a subtle default for paired positive and negative options. Those are concrete migration items despite familiar command definitions, and the project maintains an older major for security rather than pretending every runtime can follow immediately.
Docs5/5The README is a detailed reference with examples for common and negated options, custom parsers, arguments, subcommands, hooks, help customization, error handling, async parsing, testing, and TypeScript. Extra documents cover ambiguous option values, terminology, help design, and the release policy. The one-page format is long, yet its table of contents and example directory make exact behavior findable.
Maintenance5/5GitHub shows an unarchived repository pushed on August 21, 2026, with only six open issues and pull requests. Version 15.0.0 shipped in May 2026 and includes a written migration path plus a dated security-support promise for Commander 14. That combination of current work, a small tracker, release notes, and explicit old-major policy is unusually clear for a package with this reach.
Ecosystem5/5The npm API recorded 490,953,359 downloads for the latest completed week, and GitHub reports 28,366 stars. Commander appears throughout Node build tools and executable packages, includes its own TypeScript declarations, and loaded from both ESM and CommonJS in our Node 22 check. Its ecosystem advantage is familiarity rather than plugins; prompts, output styling, and packaging still come from separate tools.

Use it if

  • You need a Node CLI with subcommands, generated help, strict unknown-option errors, and typed option declarations
  • Flags need choices, environment-variable sources, conflicts, implied values, custom parsers, or variadic arguments
  • Async command handlers and before or after hooks should share one parsing lifecycle
  • The CLI is large enough that constructing a fresh Command per test is clearer than hand-parsing process.argv
Skip it if

Setup reality

Our fresh Node 22 installation of Commander 15.0.0 took 0.3 seconds. One package occupied 1 MB, and npm audit found no known vulnerabilities. Commander has zero direct and peer dependencies, is 244 KB unpacked, includes TypeScript declarations, and uses the MIT license. It is an ESM package with an exports map. Both ESM import and require() worked on this Node version. Its engine declaration is strict: Node must be 22.12.0 or newer.

Version 15 moved the implementation to ESM. A .cjs program can still require it on a runtime with require(esm), which explains the high Node floor. Tooling that transforms or mocks dependencies may still reject an ESM-only package even when Node itself accepts it. Commander 14 is the documented fallback and receives security updates until May 2027. The former commander/esm.mjs export was removed, so imports should come from commander.

Define a local Command instance for any CLI that will be tested or embedded. The exported program singleton is convenient, but definitions and parsed state persist on it. Option values begin as strings unless an argument parser converts them. Variadic options keep consuming tokens until another option appears, and optional values ignore dash-prefixed text unless supplied with equals syntax. Environment-backed options expose their source through getOptionValueSource(), which helps when a default masks missing configuration.

Call parseAsync() if an action or lifecycle hook returns a promise. parse() does not wait for that work. Command errors and help normally terminate the process; exitOverride() converts exits into CommanderError exceptions for tests, while configureOutput() captures text. The package is meant for Node command lines. Our browser bundle attempt failed, so shared libraries should keep Commander in the executable entry point rather than code that a frontend bundler traverses.

Patterns

Parse boolean and valued options parse-options

import { Command } from 'commander';

const cli = new Command()
  .option('-v, --verbose', 'print details')
  .option('-p, --port <number>', 'listen port', '3000')
  .parse();

const options = cli.opts();

Values are strings unless a parser is supplied. A local Command avoids state leaking between tests.

Attach a subcommand action create-subcommand

const cli = new Command().name('store');

cli.command('remove')
  .description('remove one item')
  .argument('<id>', 'item identifier')
  .option('--force', 'skip confirmation')
  .action((id, options) => {
    console.log({ id, force: options.force });
  });

cli.parse();

The action receives positional arguments first, then the subcommand's local options.

Fail when a value is missing require-option

cli
  .requiredOption('--token <value>', 'API token')
  .parse();

const { token } = cli.opts();

A default or environment source also satisfies requiredOption, so inspect the source if presence on argv matters.

Restrict an option to known values validate-option-choice

import { Option } from 'commander';

cli.addOption(
  new Option('--format <type>', 'output type')
    .choices(['json', 'text'])
    .default('text')
);

choices is available on an Option instance. Invalid input receives Commander's standard usage error.

Use an environment fallback read-option-from-env

cli.addOption(
  new Option('--port <number>', 'listen port')
    .env('PORT')
    .argParser((value) => Number.parseInt(value, 10))
);

cli.parse();
console.log(cli.getOptionValueSource('port'));

Command-line input takes precedence. getOptionValueSource reports cli, env, default, config, or implied.

Parse an integer with a useful error reject-invalid-number

import { InvalidArgumentError } from 'commander';

function integer(value) {
  const parsed = Number.parseInt(value, 10);
  if (!Number.isSafeInteger(parsed)) {
    throw new InvalidArgumentError('expected a safe integer');
  }
  return parsed;
}

cli.option('--count <number>', 'item count', integer);

InvalidArgumentError lets Commander format the failure as a normal command usage error.

Wait for an asynchronous handler run-async-action

cli.command('sync')
  .action(async () => {
    const response = await fetch('https://api.example.com/sync');
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
  });

await cli.parseAsync(process.argv);

Use parseAsync whenever any action or hook returns a promise. parse does not await it.

Run setup before command actions add-lifecycle-hook

cli.hook('preAction', async (_root, actionCommand) => {
  await openLog(actionCommand.name());
});

cli.hook('postAction', async () => {
  await closeLog();
});

Async hooks also require parseAsync. Parent hooks run around nested command actions.

Accept several values after one flag collect-variadic-values

cli.option('--tag <values...>', 'one or more tags');
cli.parse();

console.log(cli.opts().tag);
// --tag red blue green -> ['red', 'blue', 'green']

Collection stops at the next option. Use -- to stop all option parsing before positional text.

Keep a parse failure inside a test test-parser-errors

const cli = new Command()
  .exitOverride()
  .configureOutput({ writeErr: () => {} })
  .option('--known');

try {
  cli.parse(['--unknown'], { from: 'user' });
} catch (error) {
  console.log(error.code);
}

exitOverride throws CommanderError instead of calling process.exit, and configureOutput can silence or capture usage text.

Alternatives

PackageRegistryPick it when
yargsnpmChoose it for command modules, middleware, completion support, and object-style declarations
minimistnpmChoose it when a tiny script only needs argv converted into an object
clipanionnpmChoose it for class-based, TypeScript-oriented commands with stronger structured typing

More cli & tooling guides

chalk · typescript · esbuild · yargs · click · vite · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.