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
| API | Stability (Node.js 26 docs) | Version notes |
|---|---|---|
|
Stable |
Added in v8.0.0 and v8.2.0. Calling |
|
Stable |
|
|
Stable |
Since v12.0.0 the first argument counts as a format string only if it contains a specifier; |
|
Stable |
No longer experimental in v20.0.0; |
|
Stable |
Stable since v23.5.0 and v22.13.0; honours |
|
Stable |
No longer experimental in v24.10.0 and v22.21.0. See CLI Options, Environment & Config File. |
|
Stable |
Added in v16.11.0. |
|
Stable |
Exposed as |
|
Stable |
The |
|
Stable |
Warnings are emitted once per code since v10.0.0; the |
|
Stable |
|
|
Stable |
Marked stable in v23.11.0 and v22.15.0 (added in v19.1.0 and v18.13.0). |
|
1.1 Active development |
Added in v22.9.0 (as |
|
Stable |
Stable since v24.0.0 and v22.16.0 (experimental before). |
|
Documented in the 26 API |
Added in v25.4.0 and v24.14.0, so it is missing on Node.js 22. |
|
Documented in the 26 API |
All three were added in v26.10.0; they do not exist on Node.js 24 or 22. |
|
1 Experimental |
Added in v23.11.0 and v22.15.0. |
|
3 Legacy |
Use ES2015 |
|
0 Deprecated |
Runtime deprecations since v22.0.0 (DEP0044, DEP0060). Still present on every line. |
|
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.
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));
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 |
|---|---|---|
|
How many nesting levels to expand; deeper values print as |
2 |
|
|
3 |
|
The line width at which output wraps; |
80 |
|
Truncate long arrays, |
100 and 10000 |
|
Sort object keys (a function gives a custom order), useful for stable snapshots. |
|
|
Group digits with underscores, |
|
|
ANSI colours, getter values, non-enumerable properties, the proxy target and handler. |
|
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().
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);
<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 |
|---|---|
|
|
|
A single-character alias; short flags can be grouped ( |
|
Collect every occurrence in an array; otherwise the last one wins. |
|
Value used when the option is absent; its type must match |
|
Default |
|
Default |
|
Lets |
|
Also return an ordered list of tokens ( |
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.
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);
[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 }
Source: Node.js API: Util (parseArgs)
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.
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); }
"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.
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]));
["\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.
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 }));
}
}
$ 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' } ]
Source: Node.js API: Util
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.
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
);
true false true true true true true false true true
Source: Node.js API: Util (types)
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.
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);
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().
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(' '));
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.
| Removed | Use instead |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 }));
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 |
|
v26.1.0 |
|
v25.4.0 and v24.14.0 |
|
v25.2.0 and v24.12.0 |
|
v24.10.0 and v22.21.0 |
|
v24.9.0 |
|
v24.2.0 |
|
v24.0.0 and v22.16.0 |
|
v23.11.0 and v22.15.0 |
|
v23.5.0 and v22.13.0 |
|
v23.0.0 |
The |
v22.4.0 and v20.16.0 |
|
Source: Node.js API: Util
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.