Recipe · Node and platforms

How do I type a CLI's options with util.parseArgs?

  • Node.js (ES modules)
  • parseArgs
  • CLI
  • @types/node
  • Checked with TypeScript 5.9

The problem My command-line tool parses --port and --open with util.parseArgs from Node.js. Can tsc know that values.port is a string and values.open a boolean, without a library and without writing the types twice?

The short answer

Pass the options object to parseArgs inline. tsc reads each option’s type, multiple and default and types values to match:

cli.js cli.ts TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
import { parseArgs } from "node:util";

const { values, positionals } = parseArgs({
  options: {
    port: { type: "string", short: "p", default: "3000" },
    open: { type: "boolean", short: "o" },
    exclude: { type: "string", multiple: true },
  },
  allowPositionals: true,
});

const port = Number.parseInt(values.port, 10);
const exclude = values.exclude ?? [];
console.log(`Serving ${positionals[0] ?? "."} on ${port}`, values.open === true, exclude.join(","));
import { parseArgs } from "node:util";

const { values, positionals } = parseArgs({
  options: {
    port: { type: "string", short: "p", default: "3000" },
    open: { type: "boolean", short: "o" },
    exclude: { type: "string", multiple: true },
  },
  allowPositionals: true,
});

const port = Number.parseInt(values.port, 10);
const exclude = values.exclude ?? [];
console.log(`Serving ${positionals[0] ?? "."} on ${port}`, values.open === true, exclude.join(","));
A command line's options with util.parseArgs.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": ["node"]
  },
  "include": ["*.js"]
}

values.port is a string (it has a default), values.open is boolean | undefined, and values.exclude is string[] | undefined because multiple is set.

Why it works

@types/node declares parseArgs as a generic function over its config. Written inline, the object literal keeps its literal values (type: "string", not just string), and the declaration maps each option to a value type: "string" to string, "boolean" to boolean, an array when multiple is true, and | undefined unless there is a default. No JSDoc is needed in the calling code.

The same thing in TypeScript

The TypeScript equivalent tab is the same call. The typing comes from inference in both languages, which is why the inline object matters more than the language.

What goes wrong

An option without a default may be missing, so using it directly is TS18048; a misspelled option name is TS2339. And a config object built in a variable first widens type to string, which parseArgs refuses (TS2322):

cli.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: cli.js(11,13): error TS18048: 'values.port' is possibly 'undefined'. cli.js(13,20): error TS2339: Property 'opne' does not exist on type '{ port?: string | undefined; open?: boolean | undefined; }'. cli.js(17,13): error TS2322: Type '{ verbose: { type: string; }; }' is not assignable to type 'ParseArgsOptionsConfig'. Property 'verbose' is incompatible with index signature. Type '{ type: string; }' is not assignable to type 'ParseArgsOptionDescriptor'. Types of property 'type' are incompatible. Type 'string' is not assignable to type 'ParseArgsOptionsType'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. cli.js(11,13): error TS18048: 'values.port' is possibly 'undefined'. cli.js(13,20): error TS2339: Property 'opne' does not exist on type '{ port?: string | undefined; open?: boolean | undefined; }'. cli.js(17,13): error TS2322: Type '{ verbose: { type: string; }; }' is not assignable to type 'ParseArgsOptionsConfig'. Property 'verbose' is incompatible with index signature. Type '{ type: string; }' is not assignable to type 'ParseArgsOptionDescriptor'. Types of property 'type' are incompatible. Type 'string' is not assignable to type 'ParseArgsOptionsType'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. cli.js(11,13): error TS18048: 'values.port' is possibly 'undefined'. cli.js(13,20): error TS2339: Property 'opne' does not exist on type '{ port?: string | undefined; open?: boolean | undefined; }'. cli.js(17,13): error TS2322: Type '{ verbose: { type: string; }; }' is not assignable to type 'ParseArgsOptionsConfig'. Property 'verbose' is incompatible with index signature. Type '{ type: string; }' is not assignable to type 'ParseArgsOptionDescriptor'. Types of property 'type' are incompatible. Type 'string' is not assignable to type 'ParseArgsOptionsType'. checked 21 October 2026
import { parseArgs } from "node:util";

const { values } = parseArgs({
  options: {
    port: { type: "string", short: "p" },
    open: { type: "boolean" },
  },
});

console.log(values.port.length);error TS18048: 'values.port' is possibly 'undefined'.
console.log(values.opne);error TS2339: Property 'opne' does not exist on type '{ port?: string | undefined; open?: boolean | undefined; }'.

const options = { verbose: { type: "boolean" } };
parseArgs({ options });error TS2322: Type '{ verbose: { type: string; }; }' is not assignable to type 'ParseArgsOptionsConfig'.
  Property 'verbose' is incompatible with index signature.
    Type '{ type: string; }' is not assignable to type 'ParseArgsOptionDescriptor'.
      Types of property 'type' are incompatible.
        Type 'string' is not assignable to type 'ParseArgsOptionsType'.
An option that may be missing, a misspelled option, and a config built apart.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": ["node"]
  },
  "include": ["*.js"]
}

To keep the config in a variable, cast it with /** @type {const} */ ({ … }) so the literal values survive.

Gotchas

  • Unknown flags throw at runtime. By default parseArgs throws on an option it does not know; strict: false turns that off, and then every value is typed string | boolean | undefined, because any flag may appear.
  • Positionals need allowPositionals. Without it, a plain argument also throws; positionals is a string[] either way.
  • short is one character. short: "p" maps -p to port; the value lands under the long name.

Questions

Why is values.port a string and not a number?

parseArgs only knows "string" and "boolean" options; converting is your code's job. Parse the number once, as the example does with Number.parseInt, and check it if a bad value should stop the program.

Which Node.js versions have parseArgs?

util.parseArgs is stable in current Node.js releases (the Node.js documentation lists its history); the default option arrived later than the function itself, so check the documentation for your version.

Sources

Last reviewed 21 October 2026.