The short answer
Pass the options object to parseArgs inline. tsc reads each option’s type, multiple and default and types values to match:
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(","));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):
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'.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
parseArgsthrows on an option it does not know;strict: falseturns that off, and then every value is typedstring | boolean | undefined, because any flag may appear. - Positionals need
allowPositionals. Without it, a plain argument also throws;positionalsis astring[]either way. shortis one character.short: "p"maps-ptoport; 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
- util.parseArgs([config]) · Node.js documentation
- @types/node · npm
Last reviewed 21 October 2026.