The util Module

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Node.js 26 (26.10.0, released 2026-09-21, the Current line that becomes LTS on 2026-10-28), with the differences of Node.js 24 "Krypton" (24.21.0, Active LTS) and Node.js 22 "Jod" (22.23.3, Maintenance) called out; versions and stability levels were checked against the official documentation on 2026-10-03, and every example below was run on all three lines unless the text says otherwise. The node:util module is Stability 2 (Stable) as a whole. It started life as the grab bag that Node.js' own internals needed and grew into the place where the runtime puts small, dependency-free utilities that application code would otherwise install from npm: argument parsing, terminal colours, deep equality, deprecation warnings, namespaced debug logging, promise wrappers and a faithful console.log() formatter. Several of those APIs replace very popular packages, which is why this page spends most of its time on them. Individual APIs carry their own stability index, shown in the next table. The running scenario is the Bookshelf application that grows across this section, described in Introduction & Architecture. The language-level helpers it complements are in JavaScript standard library utilities.

The stability and version map

Table 1. The node:util APIs covered on this page
API Stability (Node.js 26 docs) Version notes

util.promisify(), util.promisify.custom, util.callbackify()

Stable

Added in v8.0.0 and v8.2.0. Calling promisify() on a function that already returns a promise is deprecated since v20.8.0.

util.inspect(), util.inspect.custom, util.inspect.defaultOptions

Stable

numericSeparator since v17.3.0; the util.inspect.styles.regexp style became a method in v25.0.0.

util.format(), util.formatWithOptions()

Stable

Since v12.0.0 the first argument counts as a format string only if it contains a specifier; %c is ignored since v12.11.0.

util.parseArgs()

Stable

No longer experimental in v20.0.0; tokens since v18.7.0, default values since v18.11.0, allowNegative since v22.4.0 and v20.16.0.

util.styleText()

Stable

Stable since v23.5.0 and v22.13.0; honours isTTY and NO_COLOR, NODE_DISABLE_COLORS, FORCE_COLOR since v22.8.0 and v20.18.0; the 'none' format since v24.2.0 and v22.17.0; hex colours since v26.1.0 in the docs history.

util.parseEnv()

Stable

No longer experimental in v24.10.0 and v22.21.0. See CLI Options, Environment & Config File.

util.stripVTControlCharacters()

Stable

Added in v16.11.0.

util.types

Stable

Exposed as require('util/types') since v15.3.0; util.types.isNativeError() is documentation-only deprecated since v24.2.0 (DEP0197).

util.isDeepStrictEqual()

Stable

The options parameter (skip the prototype comparison) was added in v24.9.0.

util.deprecate()

Stable

Warnings are emitted once per code since v10.0.0; the modifyPrototype option was added in v25.2.0 and v24.12.0.

util.debuglog(), util.debug()

Stable

debug is an alias, added in v14.9.0.

util.MIMEType, util.MIMEParams

Stable

Marked stable in v23.11.0 and v22.15.0 (added in v19.1.0 and v18.13.0).

util.getCallSites()

1.1 Active development

Added in v22.9.0 (as getCallSite, renamed in v23.3.0 and v22.12.0); column was deprecated in favour of columnNumber in v23.7.0 and v22.14.0.

util.aborted()

Stable

Stable since v24.0.0 and v22.16.0 (experimental before).

util.convertProcessSignalToExitCode()

Documented in the 26 API

Added in v25.4.0 and v24.14.0, so it is missing on Node.js 22.

util.debounce(), util.throttle(), util.markPromiseAsHandled()

Documented in the 26 API

All three were added in v26.10.0; they do not exist on Node.js 24 or 22.

util.diff()

1 Experimental

Added in v23.11.0 and v22.15.0.

util.inherits()

3 Legacy

Use ES2015 class syntax and extends instead.

util.isArray(), util._extend()

0 Deprecated

Runtime deprecations since v22.0.0 (DEP0044, DEP0060). Still present on every line.

util.isBoolean(), isBuffer(), isDate(), isError(), isFunction(), isNull(), isNullOrUndefined(), isNumber(), isObject(), isPrimitive(), isRegExp(), isString(), isSymbol(), isUndefined(), util.log()

Removed

End-of-Life in v23.0.0 (DEP0045 to DEP0059), so they are gone on Node.js 24 and 26 but still exist, with a runtime warning, on Node.js 22.

Turning callbacks into promises: promisify and callbackify

Before promises were part of the language, every asynchronous Node.js API took an error-first callback as its last argument. Most of the core now has a promise twin (node:fs/promises, node:timers/promises, node:dns/promises, node:stream/promises; see Callbacks, Promises & async/await), so the first choice is to import the promise variant. util.promisify(original) is for everything else: older libraries, your own callback code and the few core functions that have no twin. It returns a function that takes the same arguments without the callback and returns a promise that resolves with the value or rejects with the error.

Two behaviours are worth knowing. First, some core functions define util.promisify.custom, a symbol-keyed property that tells promisify() what to do instead of wrapping the callback; setTimeout() and setImmediate() use it, which is why promisify(setTimeout) takes a delay and an optional value rather than a callback. You can define the same property on your own function when the callback does not follow the (err, value) convention. Second, callbackify() goes the other way and turns an async function into an error-first one, wrapping a falsy rejection reason in an Error whose reason property holds the original value; the callback is always invoked asynchronously. Since v20.8.0, applying promisify() to a function that already returns a promise is deprecated: the result would only work by accident.

Wrapping callback APIs with promisify, a custom promisify symbol and callbackify
import { promisify, callbackify } from 'node:util';
import { readFile } from 'node:fs';
import { setTimeout as sleepCb } from 'node:timers';
import { readFile as readFilePromise } from 'node:fs/promises';

// 1. Standard error-first callback API.
const readFileAsync = promisify(readFile);
const pkg = JSON.parse(await readFileAsync(new URL('../package.json', import.meta.url), 'utf8'));
console.log('name:', pkg.name);

// 2. Core functions with a util.promisify.custom implementation.
const sleep = promisify(sleepCb);
const t0 = performance.now();
await sleep(50);
console.log('slept about 50 ms:', Math.round(performance.now() - t0) >= 49);

// 3. Your own function: honour the (err, value) contract.
function findBook(id, callback) {
  setImmediate(() => (id === 1 ? callback(null, { id, title: 'Dune' }) : callback(new Error(`no book ${id}`))));
}
const findBookAsync = promisify(findBook);
console.log(await findBookAsync(1));
await findBookAsync(2).catch((err) => console.log('rejected:', err.message));

// 4. A custom promisification when the callback does not follow the convention.
function exists(path, cb) { readFile(path, (err) => cb(!err)); } // legacy: callback(boolean)
exists[promisify.custom] = (path) => readFilePromise(path).then(() => true, () => false);
console.log(await promisify(exists)(new URL('../package.json', import.meta.url)), await promisify(exists)('/nope'));

// 5. The other direction.
const greet = callbackify(async (name) => `hello ${name}`);
greet('reader', (err, value) => console.log(err, value));
Output (identical on Node.js 26, 24 and 22)
name: bookshelf
slept about 50 ms: true
{ id: 1, title: 'Dune' }
rejected: no book 2
true false
null hello reader

Inspecting values: util.inspect

util.inspect(object, options) returns the string that console.log() prints for an object, and it is the formatter behind the REPL, the %o and %O format specifiers and uncaught error output. It handles circular references (shown as [Circular *1] with a <ref *1> marker), Map, Set, typed arrays, Proxy (with showProxy), getters (with getters) and non-enumerable properties (with showHidden). The options you reach for most often:

Option Effect Default

depth

How many nesting levels to expand; deeper values print as [Object]. null or Infinity means unlimited.

2

compact

false puts every property on its own line; a number groups up to that many inner levels on one line when they fit.

3

breakLength

The line width at which output wraps; Infinity keeps each value on one line.

80

maxArrayLength, maxStringLength

Truncate long arrays, Set and Map entries or strings, reporting the number of omitted items.

100 and 10000

sorted

Sort object keys (a function gives a custom order), useful for stable snapshots.

false

numericSeparator

Group digits with underscores, 1_234_567.891.

false

colors, getters, showHidden, showProxy

ANSI colours, getter values, non-enumerable properties, the proxy target and handler.

false

An object can control how it is printed by implementing the method keyed by util.inspect.custom (the shared symbol Symbol.for('nodejs.util.inspect.custom'), so libraries do not need to import node:util). It receives the remaining depth, the options and the inspect function itself, and options.stylize(text, styleType) applies the current colour theme. Defaults can be changed globally through util.inspect.defaultOptions, although touching a global default affects every library in the process.

util.format(format, …​args) is the printf-like function behind console.log(). It understands %s (string; objects without a custom toString() are inspected, BigInt gets an n suffix), %d and %i (number and integer), %f, %j (JSON, with [Circular] for cycles), %o (inspect with showHidden and depth 4), %O (inspect), %c (consumes an argument and is ignored) and %%. Arguments with no matching specifier are appended separated by spaces, and since v12.0.0 a first argument that holds no specifier is not treated as a format string at all. util.formatWithOptions(inspectOptions, …​) is the same but passes inspectOptions to inspect().

Controlling inspect depth, truncation and custom output, and the format specifiers
import { inspect, format, formatWithOptions } from 'node:util';

const book = {
  id: 1, title: 'Dune', tags: new Set(['sf', 'classic']),
  author: { name: 'F. Herbert', born: 1920, address: { country: 'US', city: { name: 'Tacoma' } } },
  reviews: Array.from({ length: 120 }, (_, i) => i),
  secret: Symbol('x'), when: new Date(0), big: 12345678901234567890n,
};
book.self = book;

console.log(inspect(book, { depth: 1, maxArrayLength: 3, breakLength: 60, compact: 2 }));
console.log(inspect({ b: 2, a: 1 }, { sorted: true, numericSeparator: true, getters: true }));
console.log(inspect(1234567.891, { numericSeparator: true }));

class Isbn {
  constructor(v) { this.v = v; }
  [inspect.custom](depth, options, inspectFn) {
    return `Isbn<${options.stylize(this.v, 'string')}>`;
  }
}
console.log(inspect({ isbn: new Isbn('978-0') }), inspect(new Isbn('x'), { colors: true }) !== 'Isbn<x>');

console.log(format('%s has %d pages, %i%%, %j, %o, %O, %c done', 'Dune', 612.5, 7.9, { a: 1 }, [1], { b: { c: {} } }, 'css:ignored'));
console.log(format('no specifiers', { a: 1 }, 'extra', 3));
console.log(format('%s', null, undefined, 5n, Symbol('s')), format('%d', '0x10'), format('%s', { toString() { return 'custom'; } }));
console.log(formatWithOptions({ colors: false, compact: false }, 'obj %O', { a: 1 }));
console.log(inspect.defaultOptions.depth);
Output (identical on Node.js 26, 24 and 22)
<ref *1> {
  id: 1,
  title: 'Dune',
  tags: Set(2) { 'sf', 'classic' },
  author: {
    name: 'F. Herbert',
    born: 1920,
    address: [Object]
  },
  reviews: [ 0, 1, 2, ... 117 more items ],
  secret: Symbol(x),
  when: 1970-01-01T00:00:00.000Z,
  big: 12345678901234567890n,
  self: [Circular *1]
}
{ a: 1, b: 2 }
1_234_567.891
{ isbn: Isbn<978-0> } true
Dune has 612.5 pages, 7%, {"a":1}, [ 1, [length]: 1 ], { b: { c: {} } },  done
no specifiers { a: 1 } extra 3
null undefined 5n Symbol(s) 16 custom
obj {
  a: 1
}
2

Parsing command-line arguments: util.parseArgs

util.parseArgs(config) is the built-in replacement for small argument parsers such as minimist, yargs-parser or commander when a CLI only needs flags, values and positionals. It reads process.argv (without the executable and script) unless you pass args, and returns { values, positionals }; values has a null prototype, so a flag called constructor cannot collide with Object members. It has been Stable since v20.0.0. The options you describe go in options, keyed by the long name:

Setting Meaning

type

'boolean' (a flag) or 'string' (takes a value, from --name value, --name=value or -n value). Required.

short

A single-character alias; short flags can be grouped (-vq) and a value can follow a short option.

multiple

Collect every occurrence in an array; otherwise the last one wins.

default

Value used when the option is absent; its type must match type (an array when multiple is set). It is not applied when the option is present, even with a falsy value.

strict

Default true: unknown options, a missing value or a wrong type throw. With strict: false unknown options are accepted as booleans or strings.

allowPositionals

Default false when strict is true, otherwise true.

allowNegative

Lets --no-color set a boolean option color to false (added in v22.4.0 and v20.16.0).

tokens

Also return an ordered list of tokens (option, positional, option-terminator) with indexes and raw values, for building subcommands, -- handling or custom validation (added in v18.7.0).

parseArgs() deliberately does not generate --help text, convert numbers, validate ranges, or know about subcommands; those stay your responsibility. Its errors carry codes starting with ERR_PARSE_ARGS_ (for example ERR_PARSE_ARGS_UNKNOWN_OPTION and ERR_PARSE_ARGS_INVALID_OPTION_VALUE), which is how a CLI tells a user mistake from a bug.

parseArgs with repeated options, negation, defaults, tokens and error codes
import { parseArgs } from 'node:util';

const argv = ['add', '--title', 'Dune', '-t', 'sf', '--tag=classic', '--no-color', '-v', '--', '--literal'];
const { values, positionals, tokens } = parseArgs({
  args: argv,
  allowPositionals: true,
  allowNegative: true,
  tokens: true,
  options: {
    title: { type: 'string' },
    tag: { type: 'string', short: 't', multiple: true },
    color: { type: 'boolean', default: true },
    verbose: { type: 'boolean', short: 'v' },
    limit: { type: 'string', default: '10' },
  },
});
console.log(values, positionals);
console.log(tokens.map((t) => `${t.kind}:${t.name ?? t.value}`).join(' '));

try {
  parseArgs({ args: ['--unknown'], options: {} });
} catch (e) { console.log(e.code, '-', e.message.split('\n')[0]); }
try {
  parseArgs({ args: ['--title'], options: { title: { type: 'string' } } });
} catch (e) { console.log(e.code); }
console.log(parseArgs({ args: ['--unknown', 'x'], options: {}, strict: false, allowPositionals: true }).values);
Output (identical on Node.js 26, 24 and 22)
[Object: null prototype] {
  title: 'Dune',
  tag: [ 'sf', 'classic' ],
  color: false,
  verbose: true,
  limit: '10'
} [ 'add', '--literal' ]
positional:add option:title option:tag option:tag option:color option:verbose option-terminator:undefined positional:--literal
ERR_PARSE_ARGS_UNKNOWN_OPTION - Unknown option '--unknown'
ERR_PARSE_ARGS_INVALID_OPTION_VALUE
[Object: null prototype] { unknown: true }

Terminal colours: util.styleText and the chalk migration

util.styleText(format, text, options) wraps text in ANSI escape sequences. format is a name from util.inspect.colors (red, green, bold, underline, bgBlue, and so on), an array of such names applied left to right (a later colour overwrites an earlier one), the no-op value 'none', or a hex colour in #RGB or #RRGGBB form. By default the function checks whether process.stdout can show colours and returns the plain text when it cannot, so piping output to a file does not fill it with escape codes. It follows the NO_COLOR, NODE_DISABLE_COLORS and FORCE_COLOR environment variables; when FORCE_COLOR is set Node.js also prints a warning that it ignores NO_COLOR. Pass { stream: process.stderr } to check a different stream, which is what an error message printed with console.error() needs, or { validateStream: false } to always emit the codes. An unknown format name throws ERR_INVALID_ARG_VALUE. util.stripVTControlCharacters(str) removes ANSI sequences again, which is handy for measuring or snapshotting coloured output.

Three version facts matter when you write a library for several lines. styleText() is stable since v23.5.0 and v22.13.0. The terminal check (isTTY, NO_COLOR and friends) arrived in v22.8.0 and v20.18.0; before it, the function emitted codes unconditionally. Hex colours are listed in the API history under v26.1.0: the 26.10.0 and 24.21.0 runs below accepted them, but Node.js 22.23.3 rejected '#ff5733' with ERR_INVALID_ARG_VALUE, so keep to named colours if Node.js 22 is supported.

styleText: named colours, arrays, hex, none, stream validation and escape codes
import { styleText, stripVTControlCharacters } from 'node:util';
import { stderr } from 'node:process';

const json = (s) => JSON.stringify(s);
console.log(json(styleText('green', 'ok')));
console.log(json(styleText(['bold', 'red'], 'fail')));
console.log(json(styleText('#ff5733', 'orange')));       // Node.js 22 throws here
console.log(json(styleText('none', 'plain')));
console.log(json(styleText('green', 'forced', { validateStream: false })));
console.log(json(styleText('red', 'to stderr', { stream: stderr })));
console.log(stripVTControlCharacters(styleText('blue', 'clean', { validateStream: false })));
try { styleText('chartreuse', 'x'); } catch (e) { console.log(e.code); }
Output with stdout piped (not a terminal), Node.js 26 and 24
"ok"
"fail"
"orange"
"plain"
"\u001b[32mforced\u001b[39m"
"to stderr"
clean
ERR_INVALID_ARG_VALUE

With FORCE_COLOR=1 the first three lines print "\u001b[32mok\u001b[39m", "\u001b[1m\u001b[31mfail\u001b[39m\u001b[22m" and "\u001b[38;2;255;87;51morange\u001b[39m" (a 24-bit colour sequence).

Replacing chalk, kleur and ansi-colors

For code that uses the chalk package, the Node.js project publishes a codemod that rewrites imports and calls: run npx codemod @nodejs/chalk-to-util-styletext in the project. It replaces the chalk import with { styleText } from 'node:util' and turns chained styles into an array, so chalk.red.bold(text) becomes styleText(['red', 'bold'], text). According to its documentation it leaves calls that have no direct equivalent (hex(), rgb(), ansi256(), bgAnsi256(), visible() and new chalk.Chalk()) unchanged and prints a warning for each, so review those by hand. Sibling codemods exist for kleur and ansi-colors. Running a codemod fetches and executes a package, so run it on a clean working tree and read the diff; the result below was written by hand to match the documented transformation and executed.

The shape of a chalk migration after the codemod has run
import { styleText } from 'node:util';

// Before: chalk.red.bold('Error: Operation failed')
const failed = styleText(['red', 'bold'], 'Error: Operation failed');
// Before: chalk.green('ok')
const ok = styleText('green', 'ok');
// Colours are applied only when the stream is a colour-capable terminal;
// FORCE_COLOR=1 makes the escape codes visible in a pipe.
console.log(JSON.stringify([failed, ok]));
Output with FORCE_COLOR=1 (identical on Node.js 26, 24 and 22)
["\u001b[31m\u001b[1mError: Operation failed\u001b[22m\u001b[39m","\u001b[32mok\u001b[39m"]

A small Bookshelf command line

The pieces combine naturally in a command-line tool: parseArgs() for the arguments, styleText() with the right stream for errors, debuglog() (below) for diagnostics, inspect() for human-readable output and process.exitCode rather than process.exit() so pending output is flushed. The full treatment of binaries, prompts, exit codes and signals is in Building CLI Tools and Process, Signals & Exit Codes.

A Bookshelf search command built from parseArgs, styleText, debuglog and inspect
import { parseArgs, styleText, debuglog, inspect } from 'node:util';
import { stderr } from 'node:process';

const debug = debuglog('bookshelf:cli');
const fail = (message) => {
  console.error(styleText('red', message, { stream: stderr }));
  process.exitCode = 2;
};

let parsed;
try {
  parsed = parseArgs({
    allowPositionals: true,
    options: {
      json: { type: 'boolean', default: false },
      limit: { type: 'string', short: 'n', default: '10' },
      help: { type: 'boolean', short: 'h' },
    },
  });
} catch (error) {
  if (error.code?.startsWith('ERR_PARSE_ARGS_')) fail(error.message.split('\n')[0]);
  else throw error;
}

if (parsed) {
  const { values, positionals } = parsed;
  debug('parsed %o', { values, positionals });
  const limit = Number(values.limit);
  if (values.help) {
    console.log('usage: bookshelf-cli [--json] [-n N] <query>');
  } else if (!Number.isInteger(limit) || limit < 1) {
    fail(`invalid --limit: ${values.limit}`);
  } else {
    const rows = [{ id: 1, title: 'Dune' }, { id: 2, title: 'Emma' }].slice(0, limit);
    console.log(values.json ? JSON.stringify(rows) : inspect(rows, { compact: true, breakLength: Infinity }));
  }
}
Sample runs (Node.js 26; the --json run was repeated on 24 and 22 with the same result)
$ node bookshelf-cli.mjs --json -n 1 dune
[{"id":1,"title":"Dune"}]
$ node bookshelf-cli.mjs -n 0 ; echo "exit $?"
invalid --limit: 0
exit 2
$ node bookshelf-cli.mjs --bogus ; echo "exit $?"
Unknown option '--bogus'. To specify a positional argument starting with a '-', place it at the end of the command after '--', as in '-- "--bogus"
exit 2
$ NODE_DEBUG=bookshelf:cli node bookshelf-cli.mjs -n 1 dune
BOOKSHELF:CLI 79298: parsed {
  values: [Object: null prototype] { limit: '1', json: false },
  positionals: [ 'dune', [length]: 1 ]
}
[ { id: 1, title: 'Dune' } ]

Reliable type checks: util.types

util.types holds checks for the internal types that JavaScript cannot test reliably on its own: it asks V8 about the internal type rather than relying on instanceof, Object.prototype.toString() or duck typing, which makes the answer independent of prototype changes and of realm boundaries such as vm contexts. It is also available as the node:util/types module. The set includes isPromise(), isRegExp(), isDate(), isProxy(), isAsyncFunction(), isGeneratorFunction(), isGeneratorObject(), isMap(), isSet(), isWeakMap(), isTypedArray() and one function per typed array class (isUint8Array(), isFloat16Array() and so on), isArrayBuffer(), isSharedArrayBuffer(), isAnyArrayBuffer(), isArrayBufferView(), isDataView(), isBoxedPrimitive(), isModuleNamespaceObject(), isKeyObject() and isCryptoKey(). Two notes: isPromise() is true only for real promises, not thenables, and util.types.isNativeError() is documentation-only deprecated since v24.2.0 in favour of Error.isError(). Node.js 22.23.3 does not have Error.isError() (the run below failed with "Error.isError is not a function" there), so keep isNativeError() or instanceof Error while Node.js 22 is supported; the API docs also note that isNativeError() is currently faster.

Type checks that instanceof and duck typing get wrong
import { types } from 'node:util';

console.log(
  types.isPromise(Promise.resolve()), types.isPromise({ then() {} }),   // true false
  types.isRegExp(/x/), types.isDate(new Date()),                        // true true
  types.isProxy(new Proxy({}, {})), types.isAsyncFunction(async () => {}),  // true true
  types.isUint8Array(Buffer.alloc(1)), types.isTypedArray([]),          // true false
  types.isGeneratorObject((function* () {})()), types.isBoxedPrimitive(Object(1)), // true true
);
Output (identical on Node.js 26, 24 and 22)
true false true true true true true false true true

Equality, deprecation, debug logging, MIME types, call sites and aborted signals

The remaining APIs are small, and each solves one problem.

util.isDeepStrictEqual(a, b) compares like assert.deepStrictEqual() without throwing. It uses Object.is() semantics for primitives (so NaN equals NaN, and 0 does not equal -0), compares prototypes, own enumerable properties, Map and Set contents and typed arrays, and since v24.9.0 accepts an options argument that skips the prototype comparison.

util.deprecate(fn, msg, code) returns a wrapper that emits a DeprecationWarning the first time it is called and then calls fn. Supplying the same code to several wrappers makes the warning appear only once for that code, and --no-deprecation, --trace-deprecation and --throw-deprecation (and the matching process.* properties) control what happens. Use your own prefix for the code rather than a DEP number, which belongs to Node.js core.

util.debuglog(section, callback) returns a logging function that does nothing unless the environment variable NODE_DEBUG lists the section (comma-separated, as in NODE_DEBUG=fs,net, with trailing wildcards such as foo allowed). When enabled, output goes to stderr prefixed with the upper-cased section and the process id. The optional callback is invoked the first time the logging function is called with logging enabled and can replace it with an optimised one; the returned function has an enabled boolean for skipping expensive argument construction. util.debug is an alias. The format is printf-like, as in util.format(), which is why %o printed the bracketed structure in the CLI example above.

util.MIMEType parses and serialises media types and their parameters (util.MIMEParams, a map-like object) according to the WHATWG MIME Sniffing standard: type, subtype, essence and params are accessors, and setting subtype or a parameter re-serialises the string. Invalid input throws ERR_INVALID_MIME_SYNTAX.

util.getCallSites(frameCount, options) (Stability 1.1, Active development) returns the caller’s stack as objects with functionName, scriptName, scriptId, lineNumber and columnNumber, without parsing error.stack and unaffected by Error.prepareStackTrace. Pass sourceMap: true (the default with --enable-source-maps) to map locations back to the original source, which matters for transpiled code. The older column property is deprecated.

util.aborted(signal, resource) returns a promise that resolves when the signal aborts. The resource is held weakly: if it is garbage collected before the signal fires, the promise stays pending and nothing leaks, which is the purpose of the parameter. It is Stable since v24.0.0 and v22.16.0; see Async Context & Cancellation for AbortSignal itself.

isDeepStrictEqual, deprecate, debuglog, MIMEType, getCallSites and aborted
import { isDeepStrictEqual, deprecate, debuglog, MIMEType, getCallSites, aborted } from 'node:util';

console.log(isDeepStrictEqual({ a: [1, { b: new Set([1]) }] }, { a: [1, { b: new Set([1]) }] }),
  isDeepStrictEqual([1], ['1']), isDeepStrictEqual(NaN, NaN), isDeepStrictEqual(0, -0));

const oldApi = deprecate((x) => x * 2, 'oldApi() is deprecated, use newApi()', 'BOOKSHELF001');
const oldApi2 = deprecate(() => 1, 'other', 'BOOKSHELF001');
oldApi(2); oldApi(3); oldApi2(); // one warning in total

const log = debuglog('bookshelf', () => console.log('callback: enabled'));
console.log('enabled:', log.enabled);
log('loaded %d books', 3);

const m = new MIMEType('text/html; charset=UTF-8; q=1');
console.log(m.type, m.subtype, m.essence, m.params.get('charset'), String(m));
m.params.set('charset', 'utf-16'); m.subtype = 'plain';
console.log(String(m), [...m.params.keys()]);
try { new MIMEType('nonsense'); } catch (e) { console.log(e.code); }

function inner() { return getCallSites(2).map((s) => `${s.functionName}:${s.lineNumber}`); }
function outer() { return inner(); }
console.log(outer());

const ac = new AbortController();
const p = aborted(ac.signal, ac).then(() => 'aborted resolved');
setTimeout(() => ac.abort(), 10);
console.log(await p);
Output with NODE_DEBUG=bookshelf (Node.js 26; the process ids vary, and 24 and 22 printed the same lines)
true false true false
enabled: true
callback: enabled
BOOKSHELF 32563: loaded 3 books
text html text/html UTF-8 text/html;charset=UTF-8;q=1
text/plain;charset=utf-16;q=1 [ 'charset', 'q' ]
ERR_INVALID_MIME_SYNTAX
[ 'inner:20', 'outer:21' ]
(node:32563) [BOOKSHELF001] DeprecationWarning: oldApi() is deprecated, use newApi()
(Use `node --trace-deprecation ...` to show where the warning was created)
aborted resolved

Without NODE_DEBUG the callback: enabled and BOOKSHELF lines disappear and enabled is false; the warning is printed to stderr, so its position in a terminal can vary.

The removed util.is* helpers

Node.js 0.x shipped a family of one-line type predicates (util.isString(), util.isNumber(), util.isObject() and a dozen more) that were documentation-deprecated in v4.0.0, given runtime deprecations in v22.0.0 and finally removed in v23.0.0 (End-of-Life deprecations DEP0045 to DEP0058, plus util.log() as DEP0059). They are therefore absent on Node.js 24 and 26. Node.js 22 still contains them and prints a deprecation warning when they are called. Not everything was removed: util.isArray() (DEP0044) and util._extend() (DEP0060) are still present as runtime-deprecated functions on all three lines, as the run below shows, as are util.inherits() (Legacy) and util.types.isNativeError() (documentation-only deprecated). util.print() and util.puts() (DEP0026, DEP0027) were removed long ago; util.toUSVString() (DEP0175) is documentation-only deprecated in favour of String.prototype.toWellFormed().

Which of the old helpers exist, observed with typeof on each line
import util from 'node:util';
const names = ['isArray','isBoolean','isBuffer','isDate','isError','isFunction','isNull','isNullOrUndefined','isNumber','isObject','isPrimitive','isRegExp','isString','isSymbol','isUndefined','log','print','puts','_extend','isDeepStrictEqual'];
console.log(process.version, names.map((n) => `${n}=${typeof util[n] === 'function' ? 'yes' : 'no'}`).join(' '));
Output on Node.js 26 (24.21.0 is identical; on 22.23.3 only print and puts are no)
v26.10.0 isArray=yes isBoolean=no isBuffer=no isDate=no isError=no isFunction=no isNull=no isNullOrUndefined=no isNumber=no isObject=no isPrimitive=no isRegExp=no isString=no isSymbol=no isUndefined=no log=no print=no puts=no _extend=yes isDeepStrictEqual=yes

The replacements are plain language features, listed in the deprecation entries. A migration needs no library; the Node.js project also publishes a codemod (npx codemod @nodejs/util-is) for them.

Table 2. Replacements for the removed helpers
Removed Use instead

util.isArray(x)

Array.isArray(x)

util.isBoolean(x), isNumber(x), isString(x), isSymbol(x), isFunction(x)

typeof x === 'boolean', 'number', 'string', 'symbol', 'function'

util.isUndefined(x), isNull(x), isNullOrUndefined(x)

x === undefined, x === null, x == null

util.isObject(x)

x && typeof x === 'object'

util.isPrimitive(x)

Object(x) !== x

util.isBuffer(x), util.isRegExp(x), util.isDate(x)

Buffer.isBuffer(x), x instanceof RegExp, x instanceof Date

util.isError(x)

Error.isError(x) (not available on Node.js 22; x instanceof Error there)

util.log(msg)

console.log(new Date().toLocaleString(), msg) or a logging library

util._extend(target, source)

Object.assign(target, source)

util.print(), util.puts()

console.log()

The replacements evaluated for a range of values
const values = [[], 'x', 3, null, undefined, () => {}, /r/, new Date(), new Error('e'), Symbol('s'), Buffer.alloc(1), { a: 1 }, true];
const isError = typeof Error.isError === 'function' ? Error.isError : (v) => v instanceof Error;
for (const v of values) {
  console.log(String(typeof v).padEnd(9),
    Array.isArray(v), typeof v === 'string', v === null || v === undefined,
    typeof v === 'function', v instanceof RegExp, v instanceof Date, isError(v), Buffer.isBuffer(v),
    v !== null && typeof v === 'object', Object(v) !== v);
}
console.log(Object.assign({ a: 1 }, { b: 2 }));
First rows of the output (identical on Node.js 26, 24 and 22)
object    true false false false false false false false true false
string    false true false false false false false false false true
number    false false false false false false false false false true

What changed recently

Version Change

v26.10.0

util.debounce(), util.throttle() and util.markPromiseAsHandled() were added to the API reference. The first two return promise-returning wrappers with cancel(), flush(), ref()/unref() and AbortSignal support (see the API page for options such as leading, overflow and strict). They are not available on 24 or 22.

v26.1.0

styleText() accepts hexadecimal colours (observed also on 24.21.0, but not on 22.23.3).

v25.4.0 and v24.14.0

util.convertProcessSignalToExitCode() converts a signal name to the conventional 128 + signal exit code ('SIGTERM' gives 143); useful in the shutdown code described in Process, Signals & Exit Codes.

v25.2.0 and v24.12.0

util.deprecate() gained the modifyPrototype option.

v24.10.0 and v22.21.0

util.parseEnv() is no longer experimental.

v24.9.0

util.isDeepStrictEqual() accepts an options argument to skip the prototype comparison.

v24.2.0

util.types.isNativeError() is documentation-only deprecated in favour of Error.isError().

v24.0.0 and v22.16.0

util.aborted() is Stable.

v23.11.0 and v22.15.0

util.MIMEType is Stable; util.diff() was added (Experimental).

v23.5.0 and v22.13.0

styleText() is Stable.

v23.0.0

The util.is* type helpers (except isArray()) and util.log() are removed.

v22.4.0 and v20.16.0

parseArgs() accepts allowNegative.

Where the books are outdated

Both reference books were published in 2025 and show util.promisify() in their early chapters (Efficient Node.js by Samer Buna when it explains asynchrony, Node.js Projects by Jonathan Wexler when it writes files and reads from readline). That is still valid, but today the promise variants in node:fs/promises and node:readline/promises are the first choice and promisify() is the tool for legacy callback code. The books also lean on npm packages for tasks the runtime now covers: util.parseArgs() replaces argument-parsing packages, util.styleText() replaces colour packages such as chalk (with the isTTY and NO_COLOR handling those packages implement themselves), and util.parseEnv() plus --env-file replace dotenv. Anything written for Node.js 22 or earlier that calls util.isString() and similar helpers, util.log() or util._extend() must be updated as described above.