Skip to content
Node.js nd core 5 min read

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.

OptionDefaultPurpose
depth2How many nested levels to expand; null means unlimited
colorsfalseANSI color the output for terminals
maxArrayLength100Cap how many array/typed-array elements are shown
maxStringLength10000Truncate long strings
breakLength128Width before output wraps onto multiple lines
compact3Combine short inner items onto one line
gettersfalseInvoke 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: null sparingly on objects that may contain circular references or huge trees. util.inspect handles 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
SpecifierMeaning
%sString (objects are passed through util.inspect with depth 0)
%d / %iNumber / integer
%fFloating point
%jJSON ([Circular] on cycles)
%o / %OObject via util.inspect (with / without options)
%cCSS — 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-deprecation turns these warnings into thrown errors, and --no-deprecation silences 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.inspect with explicit depth and colors options when debugging instead of stringifying with JSON.stringify, which drops Buffers, Maps, and circular references.
  • Use util.types.isX() predicates for cross-realm or low-level type checks; fall back to typeof/Array.isArray for 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.deprecate with a stable code (like DEP0001) so consumers get one clear, deduplicated warning.
  • Prefer native class extends over util.inherits in all new code.
  • Run deprecation-sensitive test suites with --throw-deprecation to surface legacy usage early.
Last updated June 14, 2026
Was this helpful?