The util Module
The util module is Node.js’s toolbox of small, focused helpers that the runtime itself relies on internally. It powers the readable object output you see in console.log, gives you precise runtime type checks, formats strings the way printf does, and lets you mark old APIs as deprecated. Because these helpers ship with Node, there is nothing to install — just import node:util and reach for the right tool. This page covers the helpers you will actually use in day-to-day work and points out which ones are legacy.
Import it with the node: prefix in either module system:
import util from 'node:util';
// CommonJS: const util = require('node:util');
Inspecting objects with util.inspect
util.inspect() turns any JavaScript value into a human-readable string. It is the engine behind console.log — when you log an object, Node calls util.inspect under the hood. Calling it directly gives you control over depth, colors, and how much detail is shown, which is invaluable for debugging nested data.
import util from 'node:util';
const config = {
name: 'devcraftly',
port: 8080,
routes: { api: { v1: { users: ['get', 'post'] } } },
secret: Buffer.from('hunter2'),
};
console.log(util.inspect(config, { depth: null, colors: true }));
Output:
{
name: 'devcraftly',
port: 8080,
routes: { api: { v1: { users: [ 'get', 'post' ] } } },
secret: <Buffer 68 75 6e 74 65 72 32>
}
The most useful options are summarized below.
| Option | Default | Purpose |
|---|---|---|
depth | 2 | How many nested levels to expand; null means unlimited |
colors | false | ANSI color the output for terminals |
maxArrayLength | 100 | Cap how many array/typed-array elements are shown |
maxStringLength | 10000 | Truncate long strings |
breakLength | 128 | Width before output wraps onto multiple lines |
compact | 3 | Combine short inner items onto one line |
getters | false | Invoke getters and display their values |
You can also customize how your own class renders by defining a util.inspect.custom method:
import util from 'node:util';
class Money {
constructor(cents) {
this.cents = cents;
}
[util.inspect.custom]() {
return `Money($${(this.cents / 100).toFixed(2)})`;
}
}
console.log([new Money(1050), new Money(99)]);
Output:
[ Money($10.50), Money($0.99) ]
Tip: Use
depth: nullsparingly on objects that may contain circular references or huge trees.util.inspecthandles cycles safely (printing[Circular *1]), but unbounded depth on a large structure can produce overwhelming output.
Formatting strings with util.format
util.format() builds a string from a printf-like template. It is what console.log uses to interpret format specifiers such as %s and %d. Any extra arguments beyond the placeholders are appended, separated by spaces.
import util from 'node:util';
const line = util.format('%s listening on :%d (%j)', 'api', 8080, { tls: true });
console.log(line);
console.log(util.format('extra', 'args', 'get', 'joined', 42));
Output:
api listening on :8080 ({"tls":true})
extra args get joined 42
| Specifier | Meaning |
|---|---|
%s | String (objects are passed through util.inspect with depth 0) |
%d / %i | Number / integer |
%f | Floating point |
%j | JSON ([Circular] on cycles) |
%o / %O | Object via util.inspect (with / without options) |
%c | CSS — ignored in Node, consumed silently |
%% | A literal percent sign |
Runtime type checks with util.types
typeof and instanceof cannot reliably distinguish many built-in objects, especially across realms (e.g. values from a different vm context or worker). util.types provides battle-tested predicate functions that inspect an object’s internal slot, so they work even when instanceof would fail.
import util from 'node:util';
console.log(util.types.isPromise(Promise.resolve())); // true
console.log(util.types.isDate(new Date())); // true
console.log(util.types.isAsyncFunction(async () => {})); // true
console.log(util.types.isNativeError(new TypeError())); // true
console.log(util.types.isTypedArray(new Uint8Array(4))); // true
console.log(util.types.isRegExp(/devcraftly/)); // true
Output:
true
true
true
true
true
true
There are dozens of predicates (isMap, isSet, isArrayBuffer, isProxy, isGeneratorFunction, and more). Prefer them over hand-rolled checks when correctness across contexts matters.
Marking APIs deprecated with util.deprecate
util.deprecate() wraps a function so that the first time it is called, Node prints a deprecation warning to stderr. Repeated calls stay quiet, keeping logs clean while still nudging callers to migrate.
import util from 'node:util';
const oldFetchUser = util.deprecate(
(id) => ({ id, name: 'legacy' }),
'fetchUser() is deprecated; use getUser() instead',
'DEP0001',
);
oldFetchUser(7);
oldFetchUser(8); // no second warning
Output:
(node:48211) [DEP0001] DeprecationWarning: fetchUser() is deprecated; use getUser() instead
Warning: Running Node with
--throw-deprecationturns these warnings into thrown errors, and--no-deprecationsilences them entirely. This is handy in CI to catch deprecated usage before it ships.
Legacy: util.inherits
util.inherits() predates ES classes. It sets up prototypal inheritance between two constructor functions, which was the standard pattern for extending EventEmitter in older code. In modern Node you should use the native class ... extends ... syntax instead.
import util from 'node:util';
import { EventEmitter } from 'node:events';
// Legacy style — avoid in new code:
function Bus() {
EventEmitter.call(this);
}
util.inherits(Bus, EventEmitter);
// Modern equivalent — prefer this:
class ModernBus extends EventEmitter {}
The native form is clearer, preserves the constructor chain, and supports super(). util.inherits remains only for backward compatibility.
Best Practices
- Reach for
util.inspectwith explicitdepthandcolorsoptions when debugging instead of stringifying withJSON.stringify, which dropsBuffers,Maps, and circular references. - Use
util.types.isX()predicates for cross-realm or low-level type checks; fall back totypeof/Array.isArrayfor plain primitive checks. - Add a
[util.inspect.custom]method to domain objects so logs stay readable without leaking sensitive fields. - Wrap soon-to-be-removed functions in
util.deprecatewith a stable code (likeDEP0001) so consumers get one clear, deduplicated warning. - Prefer native
class extendsoverutil.inheritsin all new code. - Run deprecation-sensitive test suites with
--throw-deprecationto surface legacy usage early.