Community JavaScript Snippet
An argv Parser Without yargs or commander
Eighty percent of the time I just need --flag and --opt=value parsing for a one-off script. Adding yargs feels heavy. This is the 30-line zero-dependency parser I copy into every Node script.
An argv Parser Without yargs or commander
Eighty percent of the time I just need --flag and --opt=value parsing for a one-off script. Adding yargs feels heavy. This is the 30-line zero-dependency parser I copy into every Node script.
By @nehanasser
May 1, 2026
·
Updated May 18, 2026
460 views
8
4.4 (12)
The parser handles the four shapes I actually use: bare --flag, --key=value, --key value, and --no-flag for negation. The -- sentinel is critical for any script that forwards args to a child process (think npm run start -- --port=3000); without it, downstream tools see your script's flag parser eating their flags. The positional collection at the end keeps trailing arguments simple: anything that does not look like a flag, or anything after --, is positional. I deliberately do not support clumped short flags (-abc meaning -a -b -c) because it is rarely worth the parser complexity.
The schema layer is what turns raw key/value pairs into a typed argument object. Required-and-missing args are collected into one error block rather than crashing on the first one, so the user sees every problem in a single run; this is exactly the UX yargs and clap give you and is worth keeping. I cap this at three types (string, number, boolean) because anything more elaborate (arrays of, comma-separated lists) belongs in a real arg-parser library; if you find yourself adding a fourth type, that is the signal to switch. One footgun worth flagging: parseArgs greedily consumes the next non-dash token as the option's value, so positionals must come BEFORE bare boolean flags in the argv stream (or you spell the flag explicitly as --verbose=true).
Subcommands are just a string switch over argv[0]. The trick that keeps the code readable is to give every handler the same signature (rest) => void so the dispatcher does not need to know anything about each subcommand's args. The fallback to commands.help for unknown commands is what makes the tool feel polished; the unknown branch normally collapses into the help branch, which is a one-line change rather than a separate error path. I have shipped this exact shape for two internal CLIs; the moment a subcommand grows nested subcommands, you replace commands with a tree and recurse, which is still less code than yargs' command builder API.
