Skip to content
On this page

I wrote this library because I like unambiguity of the GNU style (and terseness of the POSIX style) of the gnu/linux ecosystem: I'm not interested in a parser that speaks --name val instead of --name=val for the former, and for the latter: I'd rather write -abc than -a -b -c.


var args = require('@ashnazg/args'); = 'SUMMARY TEXT'; = 'FISH';
args.define({gnu: 'name',      type: 'string',                desc: 'helpful stuff', default: 'trout'});
args.define({gnu: 'count',     type: 'int',                   desc: 'helpful stuff', default: 42});
args.define({gnu: 'weight',    type: 'number',                desc: 'helpful stuff', default: 4.2});
args.define({gnu: 'kingdom',   type: ['animal', 'vegetable'],                        default: 'mineral'}); // note that validators don't run on defaults, just user inputs.
args.define({gnu: 'male',      type: 'flag',                  desc: 'helpful stuff'}); // defaults really shouldn't be used on flags.
args.define({gnu: 'nicknames', type: 'csv',                   desc: 'helpful stuff', default: ['flipper', 'fishstick']});
args.define({gnu: 'manifest',  type: 'file_in',               desc: 'helpful stuff'});
args.define({gnu: 'report',    type: 'file_out',              desc: 'helpful stuff', default: `report-${new Date().toISOString()}.csv`});
var {params, opts} = args();

use dash-dash to stop interpreting options

This library follows the convention that -- will cause all following terms to be parameters even if they look like options.

cli -a -- -b results in option a being turned on, and the one param is -b.

later options overwrite early options

In the unixy world, most options follow the convention of allowing you to overrule an earlier setting: cli --mode=foo ...[many options and params]... --mode=baz will pretend like foo never happened.

This is so that you can write stuff like aliases without needing a bunch of boilerplate wrapper code to allow you to turn an option off:

alias prettyprint="ls --color=always"
prettyprint /root/ --color=never

But in other cases, all values on the command line are preserved and processed. While this library defaults to 'overwrite', there's a multi flag in define() to switch to preserving all. (And type:csv has this behavior implicitly.)



If there's no --help or errors, args() returns {opts:

For legacy support, you can also get at opts and params under their old weird name: args.opts and args.params

bad inputs

If there was an unrecognized option or a failed validator, args() will print the error and exit(1).


The library constructs a --help handler for you, using the defines(), plus summary and noun. If the caller adds --help args() will print that and exit(0);

The help page generated by the above sample setup looks like:

USAGE: yourscriptname [-opts] FISH

  -n, --name=STRING       helpful stuff
  -c, --count=INT         helpful stuff
  -w, --weight=NUMBER     helpful stuff
  -k, --kingdom=ENUM      one of: animal, vegetable
  -m, --male              helpful stuff
      --nicknames=CSV     helpful stuff
      --manifest=FILE_IN  helpful stuff
  -r, --report=FILE_OUT   helpful stuff

Note that posix names weren't added for nicknames or manifest; that's because there already was a gnu param that had used that first letter. You can add a posix: 'Z' member to the define() config to pick an arbitrary letter.

Release 2.5.2

  1. bugfix: default: '' is no longer ignored.

Release 2.5.1

"--help" system no longer forces exitCode to be zero. This is in support of the following etiquette:

  1. tool --help is not a failed command.
  2. tool --key=nonsense could just bail out, but for errors that are directly related to missing or invalid options use, I like printing the usage guide as well:
    FAIL: option A requires option B to be set
    USAGE: tool ....
    (blah blah blah; everything you see on --help)
  3. On error, (user-input or otherwise) the unixy thing to do is to return an error status.
  4. To support printing help on user input errors, args(['--help']) can be used, and as of 2.5.1, the following recipe doesn't incorrectly report "status=0"
    function dieNoisily(why) {
    	if (process.stdout.isTTY) {
    		process.exitCode = 1;
    	} else {

Release 2.5

Now supports showing examples below the option table: = 'appname'; = 'paramthing'; = 'does things with things'; = `
use thing as such
or like this`;

args.define({gnu: 'slicer'});

Will output:

USAGE: appname [-opts] paramthing
does things with things

  -s, --slicer=STRING

use thing as such
or like this

Release 2.4

  • new optional key: {set: 'foo'} will cause all options with that key to output to 'foo' instead of their own name.
    • use cases: --quiet --verbose --quiet -> 'quiet 1', not 'quiet 2 verbose 1' or --markdown vs --jsonl
    • 2.4.1 bugfix: the above didn't work when wired up backwards.
  • tweak: you can disable auto-posix-naming by passing a falsy but !== undefined value.
  • tweak: an explicit {posix: 'k'} overrides any previous opt keyed to that. (this is intended to override automatically selected ones.)

Release 2.3

  • new {max: int} option for flags: define({posix: 'v', type: 'flag', max: 2}) means that cli -v -v -v will only report two levels of verbosity instead of three.

Release 2.2

  • unset flag options now return 0 instead of undefined
  • bugfix: args.defaults was being polluted by user's actual choices; they're kept distinct now.

Release 2.1.1

  • exposed args.UserError so I can instanceof in the client tools

Release 2.1.0

  • Fixed a few bugs in args.load.{json,raw,lines,yaml} that was making that family of utils dead broken:
    • unnecessary async keyword
    • missing set-handling function
    • reprocess wasn't calling the hook in set mode.

Release 2.0.1

removed erroneous line from documentation.

Release 2.0.0

  1. added "default:" and "env:" and refactored "multi:"
  2. breaking changes
    1. the posix defaulting behavior has reversed from 'later defines() trump early defines()' to first-come-first serve.
      • You're app is immune to this change if you're avoiding collisions by either: specifying any colliding defines() with explicit posix fields or by not having params with the same first letter.
      • Otherwise, this change could make posix-using calls to your script change behavior.
      • In that case, you should add posix: fields to all your defines to pin the legacy choices in place, instead of using auto-picked posix letters.
    2. args() now returns {params, opts} and not just opts
  3. added args.load.json and the others.

Release 1.1.0

v1.1.0 now allows you to define() your CLI options using {module: style} instead of the very weird positional style used til now. (Weird in that when you send less than all parameters, the way the library maps your actual params to its various formal signatures crosses even my eyes, and I wrote it.) That weird style is pushing signature overloads so deep into stupid-clever territory that it should burn, but I'm not removing support for legacy usage til either a future 2.0.0.

New signature

args.define({gnu, posix, validator, desc, suffix});

Config Defaults

  • gnu is not optional; this is the name of the param in the output object as well as the --name form.
  • posix will default to the first letter in 'gnu', unless that letter's already taken by a previously defined option, in which case it won't default to having a posix name at all.
  • validator defaults to string.
    • type is a synonym for 'validator'
  • desc defaults to an empty string
  • suffix is used during --help's option table printing. It defaults to a human friendly label for your validator (if you're not using a custom function validator.) Specifying suffix yourself means that --help will use
  • loader defaults to disabled. if it's one of json/yaml/lines/raw, then the option's value will be run through args.load[loader]()
  • multi defaults to false. if it's true, then the option will preserve all user inputs instead of having "last option wins"
  • env defaults to disabled. if set to a string, then this option will default to that environment variable.
    • if default and env are both present, it'll try to load the env first, and use the default if it's not set (or is empty string.)
  • default sets the default value used if the user does not specify that option. The default default is:
    • flags: 0
    • csvs or where multi=true: []
    • everything else: undefined


  • a validator can be a:
    • function whose signature works with (user_provided_string_value, args_lib, invoked_as, dashes_used) (these last two are really there for the convenience of auto generating error messages that correctly reflect what the user said -- you don't want your -cFuzzbutt validation to say "error: --cat=Fuzzbutt is a dumb name" when the user didn't say --cat=.)
    • a js typeof name like 'string' or 'number'
    • one of the args.js defined types: 'int', 'flag', 'file_in', 'file_out', 'csv'
    • an enum (expressed as a list of stringvalues: ['foo','bar'] means that --param=foo will be accepted but --param=baz will exit with an error message.)


Flags don't accept values. The value of the option after parse is the count of times the flag was set. This allows cli -v -v to be distinguished from cli -v if needed.

Flags are allowed to be clumped together posix-style: -abc could be three flags, in which case it's the same meaning as -a -b -c, or b could be a non-flag option, in which case option b has a value of c, making -abc the same as -a -bc. If -b is the short form of your app's --bees=STRING, then -abc is parsed as equivalent to -a --bees=c


  • this has nothing to do with files; I literally mean 'comma separated values' here. An option of this type accepts one or more strings; it treats both of the following user behaviors as the same:
clitool --entry=a --entry=b
clitool --entry=a,b

This of course means you can't use validator:'csv' if you want your entry values to include commas, as this will treat them as delimiters.

file_in and file_out

  • file_in will error out if the file is not present or accessible
  • file_out has no more validation behavior than a 'string' option would, BUT the --help display will use FILE_OUT as the value placeholder instead of STRING.

file_in utility

Because I'm often using file_in to read config files, I have some common file load-and-parse utilities. (If your file is too big to just pull into memory, don't use these.)

If a file a user specified

args.load.json('conf.json').then(obj => console.log(obj));
args.load.lines('file').then(lines =>;
args.load.yaml('file.yml').then(yaml => console.log(yaml));
args.load.raw('file').then(rawstring => console.log("the whole file:", rawstring));

Each format() can take either a single filename as above, or an array of them, in which case it returns a map of filename:contents

args.load.json(['conf.json', 'manifest.json']).then(confs => {


  • BUG: can't use a function as validator for a flag or value-optional option; providing a function implies that option requires a value.
  • evaluate switching to
  • a way of setting all defaults on opts{} before (or at) parse time
    • like: parse(argv, defaults) (and for parse(defaults), isArray is enough.)
  • refactor out my atst's validation types and merge these two kits into one common package; that way, args.js can re-use the other custom validators like ISODATE, ISODATE_PAST and ISODATE_FUTURE
  • support --no-${option} as a way to clear the value of a non-multi.
  • low priority todo: contrast my UserError definition with those at and with my pubsub's DroppedMessageError

Legacy Signatures

args.define('c', 'cat', 'string', 'stuff', 'NAME');
args.define('c', 'cat', 'string', 'stuff');
args.define(     'cat', 'string', 'stuff');
args.define(     'cat', 'string');

JavaScript/Bash source released under the MIT License.