TypeScript CLI: Parsing Arguments with node:util parseArgs and Narrowing the Result
November 6, 2026
Welcome to episode 27 of TypeScript from Zero, a series for complete beginners (no JavaScript needed). Welcome back to TypeScript from zero! Our task tracker needs commands, like add and list, and options, like -- title. Node has a tool for that, and today we use it. We continue in the tasks project, with a new file called cli.ts. Every code block below is the exact code and output from the video, ready to copy.
- All episodes are in the TypeScript from Zero playlist.
- Versions used: Node.js 26.13.0, npm 11.20.0, TypeScript 7.0.2.
Setup
We start in the terminal and open the project in VS Code:
cd tasks
touch src/cli.ts
code .1. parseArgs
Parse args comes from the module node colon util. We give it the arguments after the file name, the same slice from episode twelve. We allow positional arguments, which are the plain words, and we describe two options: title, which is a string, and all, which is a boolean. Each option can have a short name, one letter after a single dash. We run it, with the word add, a title, and the short all option. The words arrive in positionals, and the options in values.
src/cli.ts:
import { parseArgs } from "node:util";
import { describe } from "./task.ts";
import type { Task } from "./task.ts";
const { values, positionals } = parseArgs({
args: process.argv.slice(2),
allowPositionals: true,
options: {
title: { type: "string", short: "t" },
all: { type: "boolean", short: "a" },
},
});
console.log(positionals, values);$ node src/cli.ts add --title "Buy milk" -a
[ 'add' ] [Object: null prototype] { title: 'Buy milk', all: true }2. Command
Now we use it. The first positional word is the command. If it is add, we look at the title. The type of the title is string or undefined, because the option may be missing. So we check it first. If it is there, we make a todo task, and print it with describe. Without the title, we show a hint instead.
src/cli.ts:
import { parseArgs } from "node:util";
import { describe } from "./task.ts";
import type { Task } from "./task.ts";
const { values, positionals } = parseArgs({
args: process.argv.slice(2),
allowPositionals: true,
options: {
title: { type: "string", short: "t" },
all: { type: "boolean", short: "a" },
},
});
const command = positionals[0];
if (command === "add") {
const title = values.title;
if (title === undefined) {
console.log("Please give a title: add --title \"Buy milk\"");
} else {
const task: Task = { id: 1, title, status: "todo" };
console.log(describe(task));
}
}$ node src/cli.ts add --title "Buy milk"
#1 Buy milk (not started)$ node src/cli.ts add
Please give a title: add --title "Buy milk"3. Error
What if we skip the check and read the length of the title right away? Run without a title, and Node crashes: cannot read properties of undefined. tsc had seen it coming: values dot title is possibly undefined. TS18048.
src/cli.ts:
import { parseArgs } from "node:util";
import { describe } from "./task.ts";
import type { Task } from "./task.ts";
const { values, positionals } = parseArgs({
args: process.argv.slice(2),
allowPositionals: true,
options: {
title: { type: "string", short: "t" },
all: { type: "boolean", short: "a" },
},
});
const command = positionals[0];
if (command === "add") {
const title = values.title;
if (title === undefined) {
console.log("Please give a title: add --title \"Buy milk\"");
} else {
const task: Task = { id: 1, title, status: "todo" };
console.log(describe(task));
}
}
console.log(values.title.length);$ node src/cli.ts add
Please give a title: add --title "Buy milk"
file:///home/mah/tasks/src/cli.ts:23
console.log(values.title.length);
^
TypeError: Cannot read properties of undefined (reading 'length')
at file:///home/mah/tasks/src/cli.ts:23:26
at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
at async node:internal/modules/esm/loader:650:26
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:105:5)
Node.js v26.13.0$ npx tsc --noEmit
src/cli.ts:23:13 - error TS18048: 'values.title' is possibly 'undefined'.
23 console.log(values.title.length);
~~~~~~~~~~~~
Found 1 error in src/cli.ts:234. Fix
We delete the line, and tsc is quiet again.
src/cli.ts:
import { parseArgs } from "node:util";
import { describe } from "./task.ts";
import type { Task } from "./task.ts";
const { values, positionals } = parseArgs({
args: process.argv.slice(2),
allowPositionals: true,
options: {
title: { type: "string", short: "t" },
all: { type: "boolean", short: "a" },
},
});
const command = positionals[0];
if (command === "add") {
const title = values.title;
if (title === undefined) {
console.log("Please give a title: add --title \"Buy milk\"");
} else {
const task: Task = { id: 1, title, status: "todo" };
console.log(describe(task));
}
}$ npx tsc --noEmit5. Unknown option
One more thing: parse args is strict. An option that we did not describe, like color, stops the program with an error code, unknown option. That is useful: a typo in an option does not slip through.
$ node src/cli.ts add --color red
node:internal/util/parse_args/parse_args:102
throw new ERR_PARSE_ARGS_UNKNOWN_OPTION(
^
TypeError [ERR_PARSE_ARGS_UNKNOWN_OPTION]: Unknown option '--color'. To specify a positional argument starting with a '-', place it at the end of the command after '--', as in '-- "--color"
at checkOptionUsage (node:internal/util/parse_args/parse_args:102:13)
at node:internal/util/parse_args/parse_args:377:9
at Array.forEach (<anonymous>)
at parseArgs (node:internal/util/parse_args/parse_args:374:3)
at file:///home/mah/tasks/src/cli.ts:5:33
at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
at async node:internal/modules/esm/loader:650:26
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:105:5) {
code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION'
}
Node.js v26.13.0Recap
Today: parse args from node util, positionals and values, short names, narrowing the string or undefined type of an option, and the strict mode for unknown options. Next episode: saving the tasks in a file.