util.types

Provides reliable type predicates for built-in objects that survive proxies and cross-realm values.

Signature
util.types
  • Added in Node.js v10.0.0

Parameters

ParameterTypeDescription
$valueanyThe value passed to any individual predicate, e.g. util.types.isDate(value). Every predicate takes exactly one argument.

Return value

ObjectAn object of type-checking predicates, each taking any value and returning a boolean.

Errors

The predicates do not throw; anything that is not the queried type simply returns false.

Changelog

  • PHP v15.3.0Also exposed directly as require('node:util/types').
  • PHP v10.0.0Introduced, replacing the individual util.isArray(), util.isDate() and similar functions deprecated since v4.0.0.

util.types is a namespace of one-argument predicates — isDate(), isPromise(), isTypedArray(), isRegExp(), isMap(), isSet(), isProxy(), isNativeError() and around fifty more. Each interrogates the value's internal slots in C++ rather than reading anything reachable from JavaScript.

Why that matters

The usual checks are all forgeable or realm-bound. instanceof Date consults the prototype chain, so it says true for a Proxy with a lying getPrototypeOf trap and false for a genuine Date from a vm context, a worker_thread or an iframe. Object.prototype.toString.call(v) is defeated by Symbol.toStringTag. Duck typing (typeof v.then === 'function') cannot distinguish a real promise from any thenable. util.types is immune to all three.

When to reach for it

At trust boundaries: values crossing a worker_thread or vm boundary, arguments to a serialiser or a native addon, plugin input you did not create. For ordinary same-realm application code the plain checks are clearer and faster — reach for util.types when a wrong answer would be a correctness bug, not out of habit.

Note on isPromise

It is true only for native promises. A thenable that await would happily unwrap returns false.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Classify values inside a custom serialiser that receives objects from worker threads or vm contexts.
  • Validate arguments at a plugin or native addon boundary where a forged prototype must not pass.
  • Distinguish a real Date, Map or TypedArray from a look-alike object in library input validation.

Frequently asked questions

Why use util.types.isDate instead of instanceof Date?

Because `instanceof` walks the prototype chain, so it returns `false` for a real `Date` created in another realm — a worker thread, a `vm` context, an iframe — and `true` for a Proxy with a forged `getPrototypeOf` trap. `util.types.isDate()` inspects V8's internal slot and is correct in both cases.

Does util.types.isPromise return true for a thenable?

No. It returns `true` only for native `Promise` instances; an object with a `then` method returns `false` even though `await` would unwrap it. If your question is "can I await this", duck-type with `typeof value.then === 'function'` instead.

Is util.types slower than typeof or instanceof?

Yes, marginally — the predicates call into C++, so they cost more than a prototype-chain walk. The difference is irrelevant outside hot loops, but it is a reason to keep `util.types` for trust boundaries and untrusted input rather than using it for every check in ordinary same-realm code.

More examples

Where instanceof gives the wrong answer

The `vm` case is the one that reaches production: a `Date` created in a worker thread or a sandboxed context has a different `Date.prototype`, so `instanceof` reports `false` for a perfectly valid date and a serialiser silently falls through to its object branch. `util.types` reads V8's internal slot, which is the same regardless of realm. The third block is a reminder that `Array.isArray` already solved this for arrays — `util.types` extends the same guarantee to every other built-in.

Example · javascript
const vm = require('node:vm');
const { types } = require('node:util');

// 1. Cross-realm values: a real Date built in another context.
const foreignDate = vm.runInNewContext('new Date(0)');
console.log(foreignDate instanceof Date); // false  <- wrong
console.log(types.isDate(foreignDate));   // true

// The same problem appears with values from a worker_thread,
// an iframe, or any module loaded into a separate realm.

// 2. Symbol.toStringTag lies to the toString trick.
const fake = { [Symbol.toStringTag]: 'Date' };
console.log(Object.prototype.toString.call(fake)); // [object Date]  <- wrong
console.log(types.isDate(fake));                   // false

// 3. Array.isArray is the one built-in check that is already cross-realm safe.
const foreignArray = vm.runInNewContext('[1, 2]');
console.log(foreignArray instanceof Array); // false
console.log(Array.isArray(foreignArray));   // true

A serialiser that must classify untrusted values

A serialiser is exactly the code that must not be fooled: it receives values from callers it does not control, and a misclassification produces silently corrupt output rather than an exception. Using `util.types` means a `Date` from a worker thread still serialises as a date, and an object with a forged `Symbol.toStringTag` does not. `Array.isArray` is used for the array branch because it is already cross-realm safe and faster.

Example · javascript
const { types } = require('node:util');

function encode(value) {
  if (value === null || typeof value !== 'object') {
    return { t: typeof value, v: value };
  }
  // Order matters: check the specific types before the generic ones.
  if (types.isDate(value)) return { t: 'date', v: value.toISOString() };
  if (types.isRegExp(value)) return { t: 'regexp', v: value.source, f: value.flags };
  if (types.isMap(value)) return { t: 'map', v: [...value].map(([k, x]) => [encode(k), encode(x)]) };
  if (types.isSet(value)) return { t: 'set', v: [...value].map(encode) };
  if (types.isTypedArray(value)) {
    return { t: value.constructor.name, v: Array.from(value) };
  }
  if (types.isNativeError(value)) return { t: 'error', v: value.message };
  if (Array.isArray(value)) return { t: 'array', v: value.map(encode) };
  return { t: 'object', v: Object.fromEntries(
    Object.entries(value).map(([k, x]) => [k, encode(x)]),
  ) };
}

console.log(JSON.stringify(encode({
  when: new Date(0),
  pattern: /ab+/gi,
  seen: new Set([1, 2]),
  bytes: new Uint8Array([1, 2]),
})));
// {"t":"object","v":{"when":{"t":"date","v":"1970-01-01T00:00:00.000Z"},
//  "pattern":{"t":"regexp","v":"ab+","f":"gi"},
//  "seen":{"t":"set","v":[{"t":"number","v":1},{"t":"number","v":2}]},
//  "bytes":{"t":"Uint8Array","v":[{...}]}}}

isPromise, thenables, and picking the right predicate

`isPromise` answers "is this a native Promise", which is not the same question as "can I await this" — a thenable returns `false` here yet works fine with `await`. Choose the predicate that matches the question you are actually asking. The `Buffer` lines show the inverse point: a `Buffer` genuinely is a `Uint8Array`, so `isUint8Array` and `isTypedArray` both return `true`, and `Buffer.isBuffer` is the check that distinguishes it.

Example · javascript
const { types } = require('node:util');

const native = Promise.resolve(1);
const thenable = { then(resolve) { resolve(1); } };

console.log(types.isPromise(native));   // true
console.log(types.isPromise(thenable)); // false — but await unwraps it anyway

// If you care about "can I await this?", duck-type instead:
const isAwaitable = (v) =>
  v !== null && (typeof v === 'object' || typeof v === 'function') &&
  typeof v.then === 'function';
console.log(isAwaitable(thenable)); // true

// Boxed primitives are the other common surprise.
console.log(typeof new String('x'));           // 'object'
console.log(types.isStringObject(new String('x'))); // true
console.log(types.isBoxedPrimitive(new Number(1))); // true

// Buffer is a Uint8Array, so it satisfies both checks.
const buf = Buffer.from('hi');
console.log(types.isUint8Array(buf), types.isTypedArray(buf)); // true true
console.log(Buffer.isBuffer(buf));                             // true

// The namespace is also importable on its own.
const types2 = require('node:util/types');
console.log(types2.isDate === types.isDate); // true

Discussion

  • Be the first to comment on this lesson.