util.parseArgs()
Parses command-line arguments into named values and positionals from a declared option spec.
util.parseArgs([config])- Added in Node.js v18.3.0, v16.17.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$config.argsoptional | string[]= process.argv with execPath and filename removed | Array of argument strings to parse. |
$config.optionsoptional | Object= {} | Describes the options known to the parser. Each key is a long option name; each value takes type, multiple, short and default. |
$config.options[name].type | string | 'boolean' or 'string'. Required for every declared option. |
$config.options[name].multipleoptional | boolean= false | When true all occurrences are collected into an array; when false the last occurrence wins. |
$config.options[name].shortoptional | string | A single-character alias for the option. |
$config.options[name].defaultoptional | string | boolean | string[] | boolean[] | Value assigned when the option does not appear in args. Must match `type`, and must be an array when `multiple` is true. |
$config.strictoptional | boolean= true | Throw when an unknown argument is seen or when a value does not match the configured type. |
$config.allowPositionalsoptional | boolean= false if strict is true, otherwise true | Whether the command accepts positional arguments. |
$config.allowNegativeoptional | boolean= false | Allow boolean options to be set to false with a --no- prefix. |
$config.tokensoptional | boolean= false | Also return the parsed tokens, for extending the built-in behaviour. |
Return value
ObjectAn object with `values` (a null-prototype map of parsed options), `positionals` (an array of strings) and, when `config.tokens` is true, `tokens`.
Errors
In strict mode throws ERR_PARSE_ARGS_UNKNOWN_OPTION for an undeclared option, ERR_PARSE_ARGS_INVALID_OPTION_VALUE for a type mismatch, and ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL when positionals are not allowed.
Changelog
- PHP v22.4.0, v20.16.0Added allowNegative, so --no-color can set a boolean option to false.
- PHP v20.0.0The API is no longer experimental.
- PHP v18.11.0, v16.19.0Added support for default values in the options config.
- PHP v18.7.0, v16.17.0Added tokens: true for detailed parse information.
util.parseArgs() turns process.argv into a structured result without a dependency. You declare the options you accept, it returns values and positionals.
When to reach for it
Reach for it in scripts and small CLIs — build tooling, migrations, internal utilities — where adding yargs or commander is more machinery than the job needs. Note the boundary: parseArgs() parses. It does not generate help text, define subcommands, coerce numbers, or validate that a required option was supplied. Those are yours to write, and once you need several of them a full framework is the better trade.
What trips people up
- Everything is a string or a boolean.
--port 8080gives you'8080'; there is notype: 'number'. Convert and range-check yourself. - Positionals are off by default in strict mode — a bare filename throws
ERR_PARSE_ARGS_UNEXPECTED_POSITIONALuntil you setallowPositionals: true. - Unknown options throw. That is usually right for a CLI, but it means you must declare every flag, including
--help. valueshas a null prototype, sovalues.hasOwnProperty(...)is not a function. UseObject.hasOwn()or spread it into a plain object.
Example
When to use it
- Add --dry-run and --out flags to a maintenance script without pulling in a CLI framework.
- Parse an argument array you built yourself, such as arguments forwarded from another tool.
- Implement mutually exclusive flags where the last one on the command line wins, using tokens.
Frequently asked questions
How do I parse a number option with util.parseArgs?
You cannot directly — `type` accepts only `'string'` and `'boolean'`. Declare the option as `'string'` and convert afterwards with `Number(values.port)`, checking `Number.isInteger` or `Number.isFinite` yourself, because `parseArgs` performs no coercion or range validation.
Why does util.parseArgs throw ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL?
Because `allowPositionals` defaults to `false` whenever `strict` is `true`, so a bare filename or command name is rejected. Set `allowPositionals: true` in the config and the argument appears in the returned `positionals` array.
Is util.parseArgs a replacement for yargs or commander?
For single-command scripts, yes — it handles long and short flags, repeated values, defaults and `--no-` negation with no dependency. It does not provide help text generation, subcommands, type coercion or required-option validation, so a CLI that needs several of those is still better served by a framework.
More examples
A real CLI entry point with help and validation
This is the shape most scripts end up with. Three things `parseArgs` does not do are all visible: `--help` has to be declared and handled like any other flag, `--port` arrives as the string `'3000'` and needs `Number()` plus a range check, and the throw on an unknown option must be caught if you want a clean message instead of a stack trace. `allowNegative: true` is what makes `--no-color` set `color` to `false`.
#!/usr/bin/env node
const util = require('node:util');
const options = {
help: { type: 'boolean', short: 'h', default: false },
port: { type: 'string', short: 'p', default: '3000' },
color: { type: 'boolean', default: true },
};
let values;
let positionals;
try {
// Defaults to process.argv minus execPath and filename.
({ values, positionals } = util.parseArgs({
options,
allowPositionals: true,
allowNegative: true, // enables --no-color
}));
} catch (err) {
console.error(err.message); // e.g. Unknown option '--prot'. Did you mean '--port'?
process.exit(2);
}
if (values.help) {
console.log('usage: serve [--port N] [--no-color] [dir]');
process.exit(0);
}
// parseArgs never coerces types — do it yourself.
const port = Number(values.port);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
console.error(`invalid --port: ${values.port}`);
process.exit(2);
}
console.log({ port, color: values.color, dir: positionals[0] ?? '.' });Strict mode, and the errors it produces
Each failure has its own `err.code`, so a CLI can distinguish a typo from a missing value and respond differently. The last line shows the loose mode: with `strict: false`, `allowPositionals` flips to `true`, unknown flags are accepted as booleans, and nothing throws — useful for a wrapper that forwards unrecognised arguments to another command.
const util = require('node:util');
const options = { name: { type: 'string' }, force: { type: 'boolean' } };
const attempts = [
['--nope'], // undeclared option
['file.txt'], // positional, not allowed by default
['--force=yes'], // value given to a boolean
['--name'], // string option with no value
];
for (const args of attempts) {
try {
util.parseArgs({ args, options });
console.log(args, '-> ok');
} catch (err) {
console.log(args, '->', err.code);
}
}
// [ '--nope' ] -> ERR_PARSE_ARGS_UNKNOWN_OPTION
// [ 'file.txt' ] -> ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL
// [ '--force=yes' ] -> ERR_PARSE_ARGS_INVALID_OPTION_VALUE
// [ '--name' ] -> ERR_PARSE_ARGS_INVALID_OPTION_VALUE
// strict: false collects everything instead of throwing.
console.log(util.parseArgs({ args: ['--nope', 'file.txt'], options, strict: false }));
// { values: [Object: null prototype] { nope: true }, positionals: [ 'file.txt' ] }Using tokens to implement last-flag-wins
`values` records only the final state, so it cannot tell you that `--verbose` came after `--quiet`. `tokens: true` returns the arguments in order with `index`, `rawName` (the short or long form actually typed) and `inlineValue` (whether the value came from `--a=1` or a separate argument), which is the hook for order-sensitive behaviour like mutually exclusive flags, counting `-vvv`, or stopping at a subcommand name.
const util = require('node:util');
// --verbose and --quiet are opposites; whichever appears last should win.
const { values, tokens } = util.parseArgs({
args: ['--verbose', '--quiet', '--verbose'],
options: {
verbose: { type: 'boolean' },
quiet: { type: 'boolean' },
},
tokens: true,
});
let level = 'normal';
for (const token of tokens) {
if (token.kind !== 'option') continue;
if (token.name === 'verbose') level = 'verbose';
if (token.name === 'quiet') level = 'quiet';
}
console.log('values:', { ...values }); // { verbose: true, quiet: true }
console.log('level:', level); // verbose
// Tokens carry the raw form and position, which values cannot.
console.log(util.parseArgs({
args: ['--a=1', 'x'],
options: { a: { type: 'string' } },
allowPositionals: true,
tokens: true,
}).tokens);
// [
// { kind: 'option', name: 'a', rawName: '--a', index: 0,
// value: '1', inlineValue: true },
// { kind: 'positional', index: 1, value: 'x' }
// ]
Discussion