Skip to content
aviral gupta

// B1.3 · ~33 min · Beginner

Arguments with process.argv and util.parseArgs

After this lesson you can read what was typed after node main.js, describe the options of your command with util.parseArgs, and turn a wrong argument into a clear message instead of a crash.

Lesson 3 of 5 in B1 Running Node and project basics

You will be able to

  • Read the arguments from process.argv.slice(2) and convert them from strings
  • Describe options with util.parseArgs (type, short, default, multiple) and read values and positionals
  • Predict the errors parseArgs throws in strict mode and catch them with a helpful message
  1. Warm-up · Activity 1 of 7

    Warm-up: you run node main.js Ada 3. Which expression in main.js is the string "Ada"?

    node main.js Ada 3
  2. Predict · Activity 2 of 7

    Predict before you read on. main.js contains these two lines, and you run node main.js 2 3. What does it print?

    const [a, b] = process.argv.slice(2);
    console.log(a + b);
  3. Practice · Activity 3 of 7

    Fill in the method so that node main.js one two prints only the arguments you typed: [ 'one', 'two' ].

    const args = process.argv.____(2);
    console.log(args);
    const args = process.argv.(2);
  4. Practice · Activity 4 of 7

    Match each setting of a parseArgs option to what it does.

  5. Practice · Activity 5 of 7

    The option name is used twice. What does this program print?

    import {parseArgs} from 'node:util';
    
    const {values} = parseArgs({
      args: ['--name', 'Ada', '--name', 'Grace'],
      options: {name: {type: 'string'}}
    });
    console.log(values.name);
  6. Brain teaser · Activity 6 of 7

    Brain teaser. You run node main.js -v report.txt. What happens?

    import {parseArgs} from 'node:util';
    
    const {values, positionals} = parseArgs({
      options: {verbose: {type: 'boolean', short: 'v'}}
    });
    console.log(values.verbose, positionals);
  7. Apply · Activity 7 of 7

    Mini-task. Write main.js for a greeting command: node main.js Ada Grace prints Hello, Ada! and Hello, Grace!. Describe three options with parseArgs: --shout (-s) prints in capitals, --greeting (-g) replaces Hello, and --times (-t) repeats each line, converted with Number(). Allow positionals. When parseArgs throws, print its message and a usage line instead of a stack trace. Try it with no names, with -st 2 Ada, and with --colour Ada.

    Check your work against this list

Build it yourself

Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.

Worked example

One parser, three command lines

This program first shows what process.argv holds when you run plain node main.js: two entries, the node program and the script, and nothing after them. Then it describes two options, a switch --loud (-l) and --times (-t) with a default, and gives parseArgs three command lines as arrays of strings, the way process.argv.slice(2) would deliver them. Look at how each value arrives: as true, as a string, or as the default.

main.js

import {basename} from 'node:path';
import {parseArgs} from 'node:util';

// Run as plain node main.js: no arguments after the script.
console.log(process.argv.length, basename(process.argv[1]), process.argv.slice(2));

const options = {
  loud: {type: 'boolean', short: 'l'},
  times: {type: 'string', short: 't', default: '1'}
};

// The same parser, given three command lines as arrays of strings.
for (const args of [['Ada'], ['-l', '--times', '3', 'Ada'], ['--times=2', 'Ada', 'Grace']]) {
  const {values, positionals} = parseArgs({args, options, allowPositionals: true});
  console.log(args.join(' '), '->', values, positionals);
}

Run it with

node main.js

Output

2 main.js []
Ada -> [Object: null prototype] { times: '1' } [ 'Ada' ]
-l --times 3 Ada -> [Object: null prototype] { loud: true, times: '3' } [ 'Ada' ]
--times=2 Ada Grace -> [Object: null prototype] { times: '2' } [ 'Ada', 'Grace' ]
  • process.argv has two entries even with no arguments; slice(2) is empty.
  • times is always a string, '3' and not 3, and '1' when the option is missing: convert it with Number() before you count with it.
  • loud appears only when -l is given. A boolean option that is absent has no entry, so values.loud is undefined, not false.
  • [Object: null prototype] is how Node.js prints the values object, which has no prototype. Its properties work as usual.

Exercises

Exercise 1 of 2

Parse a small command line by hand

A command takes a text and an optional count: node main.js hello 3. Write readArgs(args), where args is what process.argv.slice(2) gives. Return {text, times} with times as a number, 1 when it is missing. Throw an Error whose message starts with Usage when no text is given, and an Error when times is not a whole number of at least 1. The function takes an array instead of reading process.argv itself, so the tests can try any command line, and it runs in the browser too.

Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.

The first run downloads the JavaScript runner (up to 0.1 MB) and keeps it cached. Your code runs in your browser’s own engine and stays on your device.

Hints
  1. Hint 1

    Destructuring can give a default: const [text, count = '1'] = args;

  2. Hint 2

    Number(count) turns "3" into 3; Number.isInteger tells you whether the result is a whole number.

  3. Hint 3

    An argument nobody typed is undefined. Check text === undefined before you use it.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

// args is what process.argv.slice(2) gives: an array of strings.
export function readArgs(args) {
  const [text, count = '1'] = args;
  if (text === undefined) throw new Error('Usage: node main.js <text> [times]');
  const times = Number(count);
  if (!Number.isInteger(times) || times < 1) throw new Error('times must be a whole number from 1, got "' + count + '"');
  return {text, times};
}

console.log(readArgs(['hello', '3']));
Run it on your computer

Install Node.js 24 LTS or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.js

// args is what process.argv.slice(2) gives: an array of strings.
export function readArgs(args) {
  const text = args[0];
  const times = args[1];
  return {text, times};
}

console.log(readArgs(['hello', '3']));

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {readArgs} from './main.js';

test('readArgs(["hello", "3"]) is {text: "hello", times: 3}', () => {
  const got = readArgs(['hello', '3']);
  assert.deepEqual(got, {text: 'hello', times: 3}, `readArgs(["hello", "3"]) returned ${JSON.stringify(got)}`);
});

test('times is 1 when only the text is given', () => {
  const got = readArgs(['hi']);
  assert.deepEqual(got, {text: 'hi', times: 1}, `readArgs(["hi"]) returned ${JSON.stringify(got)}`);
});

test('no arguments: throws an Error starting with Usage', () => {
  assert.throws(() => readArgs([]), {message: /^Usage/}, 'readArgs([]) should throw an Error whose message starts with Usage');
});

test('times that is not a whole number from 1 throws', () => {
  assert.throws(() => readArgs(['hi', 'two']), Error, 'readArgs(["hi", "two"]) should throw');
  assert.throws(() => readArgs(['hi', '0']), Error, 'readArgs(["hi", "0"]) should throw');
});

package.json

{
  "type": "module"
}

package.json tells Node.js that the .js files are modules; keep it in the folder.

Run the program:

node main.js

Run the checks (needs learnrun.js in the same folder):

node --test
Download learnrun.js

Exercise 2 of 2

An echo command with parseArgs

Complete echo(args), which returns the text the command prints. Describe two options for parseArgs: --upper (-u), a switch that turns the words into capitals, and --times (-t), a string with the default '1' that repeats the line, one per line. Allow positionals: they are the words, joined with spaces. When parseArgs throws, do not let it crash: return its message, a newline and USAGE. Try node main.js -u -t 2 hello world, then node --test.

This exercise needs Node.js on your computer (the browser version cannot run it). The files and commands are below.

Hints
  1. Hint 1

    Describe each option by its long name: upper: {type: 'boolean', short: 'u'}.

  2. Hint 2

    times is a string option with default: '1'. Array(Number(values.times)).fill(line).join('\n') repeats the line.

  3. Hint 3

    Wrap the parseArgs call in try/catch and return error.message + '\n' + USAGE from the catch.

Show a solution

One way to solve it. Yours can look different and still pass the checks.

import {parseArgs} from 'node:util';

export const USAGE = 'Usage: node main.js [--upper] [--times N] <words...>';

// Returns what the command prints for these arguments.
export function echo(args) {
  try {
    const {values, positionals} = parseArgs({
      args,
      options: {
        upper: {type: 'boolean', short: 'u'},
        times: {type: 'string', short: 't', default: '1'}
      },
      allowPositionals: true
    });
    const text = positionals.join(' ');
    const line = values.upper ? text.toUpperCase() : text;
    return Array(Number(values.times)).fill(line).join('\n');
  } catch (error) {
    return error.message + '\n' + USAGE;
  }
}

console.log(echo(process.argv.slice(2)));
Run it on your computer

Install Node.js 24 LTS or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.

main.js

import {parseArgs} from 'node:util';

export const USAGE = 'Usage: node main.js [--upper] [--times N] <words...>';

// Returns what the command prints for these arguments.
export function echo(args) {
  const {values, positionals} = parseArgs({args, options: {}, allowPositionals: true});
  return positionals.join(' ');
}

console.log(echo(process.argv.slice(2)));

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {runMain} from './learnrun.js';
import {echo, USAGE} from './main.js';

const attempt = (args) => {
  try {
    return echo(args);
  } catch (error) {
    return `it threw ${error.message}`;
  }
};

test('echo(["hello", "world"]) is "hello world"', () => {
  const got = attempt(['hello', 'world']);
  assert.equal(got, 'hello world', `echo(["hello", "world"]) returned ${JSON.stringify(got)}`);
});

test('--upper and -u turn the words into capitals', () => {
  assert.equal(attempt(['--upper', 'hi']), 'HI', `echo(["--upper", "hi"]) returned ${JSON.stringify(attempt(['--upper', 'hi']))}`);
  assert.equal(attempt(['-u', 'hi']), 'HI', `echo(["-u", "hi"]) returned ${JSON.stringify(attempt(['-u', 'hi']))}`);
});

test('--times 3 repeats the line three times, one per line', () => {
  const got = attempt(['--times', '3', 'hi']);
  assert.equal(got, 'hi\nhi\nhi', `echo(["--times", "3", "hi"]) returned ${JSON.stringify(got)}`);
});

test('an unknown option returns its message and the usage line', () => {
  const got = attempt(['--colour', 'hi']);
  assert.ok(got.startsWith("Unknown option '--colour'") && got.endsWith('\n' + USAGE), `echo(["--colour", "hi"]) returned ${JSON.stringify(got)}`);
});

test('node main.js -u -t 2 hello world prints HELLO WORLD twice', async () => {
  const got = (await runMain({args: ['-u', '-t', '2', 'hello', 'world']})).trimEnd();
  assert.equal(got, 'HELLO WORLD\nHELLO WORLD', `the program printed ${JSON.stringify(got)}`);
});

package.json

{
  "type": "module"
}

package.json tells Node.js that the .js files are modules; keep it in the folder.

Run the program:

node main.js

Run the checks (needs learnrun.js in the same folder):

node --test
Download learnrun.js

Common mistakes

Using an argument that was not given

const name = process.argv[2];
console.log("Hello, " + name.toUpperCase() + "!");

What Node.js prints

TypeError: Cannot read properties of undefined (reading 'toUpperCase')

Why, and the fix

Run with node main.js and nothing after it, process.argv[2] is undefined, and calling a method on undefined throws. Check before you use an argument: if (name === undefined), print a usage line such as Usage: node main.js <name> and stop, or give a default with process.argv[2] ?? "world".

Asking parseArgs for type: 'number'

import {parseArgs} from 'node:util';

const {values} = parseArgs({options: {port: {type: 'number'}}});
console.log(values.port);

What Node.js prints

TypeError [ERR_INVALID_ARG_TYPE]: The "options.port.type" property must be ('string|boolean'). Received type string ('number')

Why, and the fix

parseArgs knows only two types: 'boolean' for a switch and 'string' for an option with a value. Describe the port as type: 'string', then convert it yourself: const port = Number(values.port), and check it with Number.isInteger before you use it. The error comes before any argument is read, because the description itself is wrong.

Forgetting allowPositionals

import {parseArgs} from 'node:util';

// What node main.js --lines 5 notes.txt passes to the program:
const args = ['--lines', '5', 'notes.txt'];
const {values, positionals} = parseArgs({args, options: {lines: {type: 'string'}}});
console.log(values.lines, positionals);

What Node.js prints

TypeError [ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL]: Unexpected argument 'notes.txt'. This command does not take positional arguments

Why, and the fix

In strict mode, which is the default, allowPositionals is false: every argument must be an option you described. A file name such as notes.txt is a positional, so add allowPositionals: true. Then values.lines is '5' and positionals is [ 'notes.txt' ].

JavaScript in the browser: your browser’s own engine, in a sandboxed worker. Syntax errors are located with acorn 8.18.0, MIT. Licence and source

Exit ticket

5 questions, no hints. Score 80% or more to complete the lesson.

Finish every activity above to unlock the exit ticket.

Report a problem

Spotted something wrong or unclear? Say what, and it will be checked and fixed.

#

At least 20 characters.

Only if you want a reply.

Key ideas

process.argv: the command line as strings

process.argv is an array of strings. Index 0 is the path of the node program, index 1 the absolute path of your script, and whatever was typed after the script starts at index 2: process.argv.slice(2) is what your program was given. Flags for Node.js itself, written before the script, as in node --env-file=.env main.js, are not in it; they are in process.execArgv. The shell splits the line at spaces, and quotes keep "Ada Lovelace" together as one argument. Every argument is a string, so "2" + "3" is "23": convert with Number(). An argument nobody typed is undefined.

util.parseArgs describes your options

import {parseArgs} from 'node:util' and describe each option by its long name: type is 'boolean' or 'string' (there is no number type), short is a one-letter alias, default is used when the option is absent, and multiple: true collects every use in an array; otherwise the last use wins. It understands --name Ada, --name=Ada, -n Ada and grouped flags like -ln. It returns values, printed as [Object: null prototype] { … }, and positionals, the plain arguments. args defaults to process.argv without its first two entries; pass your own array to try the parser. The docs mark util Stability: 2 - Stable, and parseArgs has not been experimental since v20.0.0.

Strict by default: a wrong argument throws

strict is true unless you turn it off, and then allowPositionals is false. parseArgs throws a TypeError with a code: ERR_PARSE_ARGS_UNKNOWN_OPTION for an option you did not describe (Unknown option '--colour'), ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL for a plain argument such as notes.txt when positionals are not allowed, and ERR_PARSE_ARGS_INVALID_OPTION_VALUE when a string option has no value or a boolean one gets a value. If your command takes file names, set allowPositionals: true. Catch the error and print its message with a usage line. strict: false accepts any option instead, so a typo passes unnoticed. Everything after -- is a positional.

Sources

Last reviewed September 30, 2026