Commander.js
Complete Commander.js framework guidance for building robust command-line interfaces with proper argument parsing, subcommands, options, and TypeScript support.
How to Use
Read individual reference files for detailed guidance:
- Core Basics
- Options & Flags
- Commands & Structure
- Action Handlers
- TypeScript Setup
- Practices & Patterns
Each reference file contains:
- API documentation and usage patterns
- Practical code examples with TypeScript
- Common patterns and best practices
- Error handling and validation techniques
- Migration guides from other frameworks
Quick Start
import { Command } from 'commander';
const program = new Command();
program
.name('my-cli')
.description('CLI tool description')
.version('1.0.0');
program
.command('build')
.description('Build the project')
.option('-o, --output <path>', 'Output directory')
.option('-w, --watch', 'Watch for changes')
.action((options) => {
console.log('Building with options:', options);
});
await program.parseAsync(process.argv);
Navigation Workflow
- Start with core - Understand program setup and basic structure
- Add options - Define flags, required options, and defaults
- Structure commands - Create subcommands and command hierarchies
- Implement actions - Write action handlers with async/await
- Integrate TypeScript - Add proper typing and type safety
- Validate parsing - Test with
--help and invalid inputs to verify argument parsing and error handling
- Apply best practices - Error handling, validation, and testing
Common Patterns
Single Command CLI
program
.argument('<source>', 'Source file')
.argument('[destination]', 'Destination file')
.option('-f, --force', 'Force overwrite')
.action((source, destination, options) => {
// implementation
});
Multi-Command CLI
program
.command('init')
.description('Initialize project')
.action(() => { /* ... */ });
program
.command('deploy')
.description('Deploy application')
.option('-e, --env <name>', 'Environment')
.action((options) => { /* ... */ });
Modular Subcommands with Typed Options (Recommended)
// types/build-options.ts
export interface BuildOptions {
outDir: string;
minify: boolean;
watch: boolean;
}
// services/build-service.ts
import type { BuildOptions } from '../types/build-options';
export const buildProject = (options: BuildOptions): void => {
console.log('Building to:', options.outDir);
// All options available with type safety
};
// commands/build.ts
import { Command } from 'commander';
import { buildProject } from '../services/build-service';
import type { BuildOptions } from '../types/build-options';
export const buildCommand = new Command('build')
.description('Build project')
.option('-o, --out-dir <path>', 'Output directory', 'dist')
.option('-m, --minify', 'Minify output', false)
.option('-w, --watch', 'Watch mode', false)
.action((options: BuildOptions) => {
// Pass complete typed object to service
buildProject(options);
});
// index.ts
import { buildCommand } from './commands/build';
program.addCommand(buildCommand);
Do's
✓ ALWAYS pass complete typed options objects to services (never individual properties)
✓ Define TypeScript interfaces for all options (e.g., BuildOptions, DeployOptions)
✓ Export commands as Command instances from subcommand modules
✓ Use .addCommand() to attach subcommands to parent Command
✓ Use parseAsync() for async action handlers
✓ Validate options in action handlers
✓ Provide clear descriptions for all commands/options
✓ Use TypeScript for type safety
✓ Handle errors gracefully with try/catch
✓ Use kebab-case for option names
✓ Provide sensible defaults for optional options
✓ Create barrel exports for commands (commands/index.ts)
✓ Test CLI with different argument combinations
✓ Use .exitOverride() for testing
✓ Document expected argument formats
Don'ts
✗ NEVER pass individual option properties to services (pass complete typed object)
✗ DON'T pass options piecemeal (e.g., service(opts.a, opts.b, opts.c))
✗ Don't use parse() with async handlers (use parseAsync())
✗ Don't ignore error handling in action handlers
✗ Don't use camelCase in CLI flags (use kebab-case)
✗ Don't forget to specify option argument types (<required> vs [optional])
✗ Don't mix positional arguments with options ambiguously
✗ Don't forget to call program.parse() or program.parseAsync()
✗ Don't use global state in action handlers
✗ Don't suppress built-in help text without good reason
✗ Don't forget to version your CLI
✗ Don't make all options required (use sensible defaults)
Anti-Patterns
NEVER access process.argv directly when Commander.js is available
- WHY: Commander.js handles argument parsing, validation, and help generation; bypassing it for any argument creates inconsistency in error handling and help output.
- BAD:
const url = process.argv[2] alongside Commander.js commands.
- GOOD: Define all arguments as Commander.js options or arguments:
program.argument('<url>', 'Target URL').
NEVER use .action() callback without handling errors
- WHY: Unhandled rejections in async action callbacks crash the process without helpful error messages.
- BAD:
program.command('fetch').action(async (opts) => { await riskyOp(); })
- GOOD: Wrap in try/catch and call
program.error(err.message) for Commander-formatted error output.
NEVER add .parseAsync() without await
- WHY: Commander.js v8+ requires
await program.parseAsync() for async actions; without await, the process exits before async actions complete.
- BAD:
program.parseAsync(process.argv) without await.
- GOOD:
await program.parseAsync(process.argv) inside an async IIFE or main function.
NEVER define commands with positional arguments and options that share ambiguous prefixes
- WHY: Commander.js can misparse
--option values as positional arguments when options are not consumed before positional parsing.
- BAD:
program.argument('<file>').option('--format <fmt>') with ambiguous ordering in usage examples.
- GOOD: Always place options before positional arguments in usage examples and validate input explicitly.
NEVER use program.opts() to read option values inside a subcommand
- WHY: Each subcommand has its own option scope; reading
program.opts() in a subcommand returns the parent options, not the subcommand options.
- BAD: Reading
program.opts().verbose inside a program.command('deploy').action().
- GOOD: Use
command.opts() (the action's first argument when using .action((opts) =>)) to access subcommand-specific options.
References
1---2name: commanderjs3description: Complete Commander.js CLI framework guidance covering command structure, options, arguments, subcommands, action handlers, version management, and TypeScript integration. Use when: building CLI tools, parsing command-line arguments, implementing subcommands, handling options/flags, creating interactive CLIs, or migrating from other CLI frameworks. Keywords: Commander.js, CLI, command-line, arguments, options, flags, subcommands, action handlers, version, help text, TypeScript, yargs, meow, program, parseAsync, opts, args, variadic, required options, default values, custom help, error handling4---56# Commander.js78Complete Commander.js framework guidance for building robust command-line interfaces with proper argument parsing, subcommands, options, and TypeScript support.910## How to Use1112Read individual reference files for detailed guidance:1314- [Core Basics](references/core-basics.md)15- [Options & Flags](references/options-flags.md)16- [Commands & Structure](references/commands-structure.md)17- [Action Handlers](references/actions-handlers.md)18- [TypeScript Setup](references/typescript-setup.md)19- [Practices & Patterns](references/practices-patterns.md)2021Each reference file contains:22- API documentation and usage patterns23- Practical code examples with TypeScript24- Common patterns and best practices25- Error handling and validation techniques26- Migration guides from other frameworks2728## Quick Start2930```typescript31import { Command } from 'commander';3233const program = new Command();3435program36 .name('my-cli')37 .description('CLI tool description')38 .version('1.0.0');3940program41 .command('build')42 .description('Build the project')43 .option('-o, --output <path>', 'Output directory')44 .option('-w, --watch', 'Watch for changes')45 .action((options) => {46 console.log('Building with options:', options);47 });4849await program.parseAsync(process.argv);50```5152## Navigation Workflow53541. **Start with core** - Understand program setup and basic structure552. **Add options** - Define flags, required options, and defaults563. **Structure commands** - Create subcommands and command hierarchies574. **Implement actions** - Write action handlers with async/await585. **Integrate TypeScript** - Add proper typing and type safety596. **Validate parsing** - Test with `--help` and invalid inputs to verify argument parsing and error handling607. **Apply best practices** - Error handling, validation, and testing6162## Common Patterns6364### Single Command CLI6566```typescript67program68 .argument('<source>', 'Source file')69 .argument('[destination]', 'Destination file')70 .option('-f, --force', 'Force overwrite')71 .action((source, destination, options) => {72 // implementation73 });74```7576### Multi-Command CLI7778```typescript79program80 .command('init')81 .description('Initialize project')82 .action(() => { /* ... */ });8384program85 .command('deploy')86 .description('Deploy application')87 .option('-e, --env <name>', 'Environment')88 .action((options) => { /* ... */ });89```9091### Modular Subcommands with Typed Options (Recommended)9293```typescript94// types/build-options.ts95export interface BuildOptions {96 outDir: string;97 minify: boolean;98 watch: boolean;99}100101// services/build-service.ts102import type { BuildOptions } from '../types/build-options';103104export const buildProject = (options: BuildOptions): void => {105 console.log('Building to:', options.outDir);106 // All options available with type safety107};108109// commands/build.ts110import { Command } from 'commander';111import { buildProject } from '../services/build-service';112import type { BuildOptions } from '../types/build-options';113114export const buildCommand = new Command('build')115 .description('Build project')116 .option('-o, --out-dir <path>', 'Output directory', 'dist')117 .option('-m, --minify', 'Minify output', false)118 .option('-w, --watch', 'Watch mode', false)119 .action((options: BuildOptions) => {120 // Pass complete typed object to service121 buildProject(options);122 });123124// index.ts125import { buildCommand } from './commands/build';126127program.addCommand(buildCommand);128```129130## Do's131132✓ **ALWAYS pass complete typed options objects to services** (never individual properties)133✓ **Define TypeScript interfaces for all options** (e.g., `BuildOptions`, `DeployOptions`)134✓ Export commands as Command instances from subcommand modules135✓ Use `.addCommand()` to attach subcommands to parent Command136✓ Use `parseAsync()` for async action handlers137✓ Validate options in action handlers138✓ Provide clear descriptions for all commands/options139✓ Use TypeScript for type safety140✓ Handle errors gracefully with try/catch141✓ Use kebab-case for option names142✓ Provide sensible defaults for optional options143✓ Create barrel exports for commands (commands/index.ts)144✓ Test CLI with different argument combinations145✓ Use `.exitOverride()` for testing146✓ Document expected argument formats147148## Don'ts149150✗ **NEVER pass individual option properties to services** (pass complete typed object)151✗ **DON'T pass options piecemeal** (e.g., `service(opts.a, opts.b, opts.c)`)152✗ Don't use `parse()` with async handlers (use `parseAsync()`)153✗ Don't ignore error handling in action handlers154✗ Don't use camelCase in CLI flags (use kebab-case)155✗ Don't forget to specify option argument types (`<required>` vs `[optional]`)156✗ Don't mix positional arguments with options ambiguously157✗ Don't forget to call `program.parse()` or `program.parseAsync()`158✗ Don't use global state in action handlers159✗ Don't suppress built-in help text without good reason160✗ Don't forget to version your CLI161✗ Don't make all options required (use sensible defaults)162163## Anti-Patterns164165### NEVER access `process.argv` directly when Commander.js is available166167- **WHY**: Commander.js handles argument parsing, validation, and help generation; bypassing it for any argument creates inconsistency in error handling and help output.168- **BAD**: `const url = process.argv[2]` alongside Commander.js commands.169- **GOOD**: Define all arguments as Commander.js options or arguments: `program.argument('<url>', 'Target URL')`.170171### NEVER use `.action()` callback without handling errors172173- **WHY**: Unhandled rejections in async action callbacks crash the process without helpful error messages.174- **BAD**: `program.command('fetch').action(async (opts) => { await riskyOp(); })`175- **GOOD**: Wrap in try/catch and call `program.error(err.message)` for Commander-formatted error output.176177### NEVER add `.parseAsync()` without `await`178179- **WHY**: Commander.js v8+ requires `await program.parseAsync()` for async actions; without await, the process exits before async actions complete.180- **BAD**: `program.parseAsync(process.argv)` without await.181- **GOOD**: `await program.parseAsync(process.argv)` inside an async IIFE or main function.182183### NEVER define commands with positional arguments and options that share ambiguous prefixes184185- **WHY**: Commander.js can misparse `--option` values as positional arguments when options are not consumed before positional parsing.186- **BAD**: `program.argument('<file>').option('--format <fmt>')` with ambiguous ordering in usage examples.187- **GOOD**: Always place options before positional arguments in usage examples and validate input explicitly.188189### NEVER use `program.opts()` to read option values inside a subcommand190191- **WHY**: Each subcommand has its own option scope; reading `program.opts()` in a subcommand returns the parent options, not the subcommand options.192- **BAD**: Reading `program.opts().verbose` inside a `program.command('deploy').action()`.193- **GOOD**: Use `command.opts()` (the action's first argument when using `.action((opts) =>)`) to access subcommand-specific options.194195## References196197- [Commander.js GitHub Repository](https://github.com/tj/commander.js)198- [Commander.js Documentation](https://github.com/tj/commander.js/blob/master/Readme.md)199- [Commander.js Examples](https://github.com/tj/commander.js/tree/master/examples)