Command-Line Arguments (process.argv)
Command-line tools take input as arguments. Node exposes them as process.argv — a plain array of strings, assembled once at startup from what the shell handed your process. Understanding its exact layout, how the shell mangles your input before Node ever sees it, and how to parse it robustly is the foundation of every CLI you will build.
The shape of process.argv
args.js
console.log(process.argv)
node args.js hello world --verbose
[ '/usr/local/bin/node', // [0] absolute path to the Node executable '/path/to/args.js', // [1] absolute path to the script being run 'hello', // [2] your first argument 'world', // [3] '--verbose' // [4] ]
argv vs execArgv — two different lists
Flags meant for Node itself (the runtime) are split out into process.execArgv and never appear in process.argv. This trips people up constantly:
node --max-old-space-size=4096 app.js --port 8080
Array | Contents for the command above |
|---|---|
|
|
|
|
The shell parses before Node does
node app.js "two words" $HOME *.md # argv[2] = 'two words' (quotes consumed by the shell) # argv[3] = '/home/you' (variable expanded by the shell) # argv[4..] = each matching .md file (glob expanded by the shell)
Reading positional arguments
const [, , command, target] = process.argv
// node deploy.js build production
// command = 'build', target = 'production'
if (!command) {
console.error('Usage: node deploy.js <command> <target>')
process.exit(1) // non-zero: signal misuse to the shell / CI
}Parsing flags by hand — and why it gets messy
Hand-rolling works for one or two flags, but notice how many shapes a "flag" can take. Each is a separate branch you must handle:
Form | Example | Meaning |
|---|---|---|
Long boolean |
| presence = true |
Long with |
| value after the equals sign |
Long with space |
| value is the next token |
Short |
| single-letter alias |
Bundled short |
| equals |
Terminator |
| everything after is positional, not a flag |
const args = process.argv.slice(2)
const verbose = args.includes('--verbose')
// --port=3000 → read the value after '='
const portArg = args.find((a) => a.startsWith('--port='))
const port = portArg ? Number(portArg.split('=')[1]) : 3000Built-in parser: util.parseArgs
Since Node 18.3 / 20 the runtime ships a structured parser, so you do not have to reinvent the table above. It handles =, spaces, short aliases, bundling, and the -- terminator for you:
import { parseArgs } from 'node:util'
const { values, positionals } = parseArgs({
options: {
port: { type: 'string', short: 'p', default: '3000' },
verbose: { type: 'boolean', short: 'v' },
},
allowPositionals: true,
})
// node app.js serve -p 8080 --verbose
console.log(values) // { port: '8080', verbose: true }
console.log(positionals) // [ 'serve' ]When to reach for a library
Small scripts —
process.argv.slice(2)orutil.parseArgsis plenty.Full CLIs — use
commander,yargs, orcacfor subcommands, auto-generated help, type coercion, and validation. Covered in Parsing CLI Arguments.