“Ex argumentis, veritas”
by @boneskull
npm install bargsMost argument parsers make you choose: either a simple API with weak types, or a complex and overengineered DSL. bargs uses function helpers that instead provide a well-typed and composable API.
Each helper returns a fully-typed option definition:
const verbose = bargs.boolean({ aliases: ['v'] });
// Type: BooleanOption & { aliases: ['v'] }
const level = bargs.enum(['low', 'medium', 'high'], { default: 'medium' });
// Type: EnumOption<'low' | 'medium' | 'high'> & { default: 'medium' }When you pass these to bargs(), the result is always well-typed; options with defaults are non-nullable.
Note: Options cannot be required because that is silly and options are supposed to be optional. Use a positional instead.
Since helpers are just functions returning objects, composition is trivial:
// Shared options across commands
const verboseOpt = { verbose: bargs.boolean({ aliases: ['v'] }) };
const outputOpt = {
output: bargs.string({ aliases: ['o'], default: 'stdout' }),
};
// Merge with spread
const result = bargs({
name: 'tool',
options: {
...verboseOpt,
...outputOpt,
format: bargs.enum(['json', 'text']),
},
});Or use bargs.options(), if you please:
// Throws if aliases collide
const sharedOpts = bargs.options(verboseOpt, outputOpt);Only Node.js v22+.
import { bargs } from 'bargs';
const result = bargs({
name: 'greet',
options: {
name: bargs.string({ default: 'world' }),
loud: bargs.boolean({ aliases: ['l'] }),
},
});
const greeting = `Hello, ${result.values.name}!`;
console.log(result.values.loud ? greeting.toUpperCase() : greeting);$ greet --name Alice --loud
HELLO, ALICE!bargs() runs synchronously. If a handler returns a Promise, it will break and you will be sorry.
// Sync - no await needed
const result = bargs({
name: 'my-cli',
options: { verbose: bargs.boolean() },
handler: ({ values }) => {
console.log('Verbose:', values.verbose);
},
});Instead, use bargsAsync():
import { bargsAsync } from 'bargs';
// Async - handlers can return Promises
const result = await bargsAsync({
name: 'my-cli',
options: { url: bargs.string({ required: true }) },
handler: async ({ values }) => {
const response = await fetch(values.url);
console.log(await response.text());
},
});Define subcommands with bargs.command():
bargs({
name: 'db',
commands: {
migrate: bargs.command({
description: 'Run database migrations',
options: { dry: bargs.boolean({ aliases: ['n'] }) },
handler: ({ values }) => {
console.log(values.dry ? 'Dry run...' : 'Migrating...');
},
}),
seed: bargs.command({
description: 'Seed the database',
positionals: [bargs.stringPos({ required: true })],
handler: ({ positionals }) => {
const [file] = positionals;
console.log(`Seeding from ${file}...`);
},
}),
},
});$ db migrate --dry
Dry run...
$ db seed data.sql
Seeding from data.sql...bargs.string({ default: 'value' }); // --name value
bargs.number({ default: 42 }); // --count 42
bargs.boolean({ aliases: ['v'] }); // --verbose, -v
bargs.enum(['a', 'b', 'c']); // --level a
bargs.array('string'); // --file x --file y
bargs.count(); // -vvv → 3bargs.stringPos({ required: true }); // <file>
bargs.numberPos({ default: 8080 }); // [port]
bargs.enumPos(['dev', 'prod']); // [env]
bargs.variadic('string'); // [files...]Positionals are defined as an array and accessed by index:
const result = bargs({
name: 'cp',
positionals: [
bargs.stringPos({ required: true }), // source
bargs.stringPos({ required: true }), // destination
],
});
const [source, dest] = result.positionals;
console.log(`Copying ${source} to ${dest}`);Use variadic for rest arguments (must be last):
const result = bargs({
name: 'cat',
positionals: [bargs.variadic('string')],
});
const [files] = result.positionals; // string[]
files.forEach((file) => console.log(readFileSync(file, 'utf8')));Copyright © 2025 Christopher "boneskull" Hiller. Licensed under the Blue Oak Model License 1.0.0.
