util.types
Provides reliable type predicates for built-in objects that survive proxies and cross-realm values.
util.types- Added in Node.js v10.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$value | any | The 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
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.
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)); // trueA 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.
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.
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