12k
All articles

JavaScript's Error.isError() Explained

Error.isError() checks real JavaScript errors across realms, explains why it beats instanceof Error, and shows safe fallback usage.

OpenReplay Team
OpenReplay Team
JavaScript's Error.isError() Explained

Error.isError(value) is a static method that returns true only when value is a genuine Error object, and it stays reliable even across realms because it checks an internal brand ([[ErrorData]]) rather than walking the prototype chain.

If you have ever opened your error tracker and found an empty {} where a real exception should have been, you have already met the problem this solves. Somewhere in the middle, an instanceof check quietly decided your error was not an error. It was standardized in ECMAScript 2026, which means the long-standing gaps in instanceof Error (errors from iframes that read as non-errors, and fake objects that read as errors) now have a first-class fix. This article explains what the method does, why it beats instanceof, the exact mechanism behind it, its edge cases, and how to adopt it with a safe fallback.

The behavior is exactly what you’d expect: Error.isError(new Error()) is true, and Error.isError({ message: 'x' }) is false, because the method verifies how the object was constructed, not merely what it inherits from.

Key Takeaways

  • Error.isError() performs a branded check for the internal [[ErrorData]] slot, the same category of unspoofable check Array.isArray() uses, so userland code can’t fake its way past it.
  • instanceof Error fails in two opposite ways: false for a real error created in another realm, and true for a fake object whose prototype was set to Error.prototype.
  • The method returns true for built-in subclasses like TypeError, for classes that correctly extend Error, and for DOMException in browsers, though Safari currently returns false for DOMException.
  • Error.isError() is part of ECMAScript 2026 and ships in Chrome/Edge 134+, Firefox 138+, Node.js 24.0.0+, and Safari 18.4 (partial).
  • Use it at boundaries (global handlers, logging glue, workers, iframes, SSR/edge) where a silent instanceof miss turns a real error into an empty object in your logs.

Why does instanceof Error fall short?

instanceof Error fails in two opposite ways, and both are silent. The TC39 proposal spells out the first one: a genuine error that has crossed a realm boundary, whether from an iframe or from Node’s vm module, comes back as a false negative. Each realm has its own Error constructor, so an error created in an iframe isn’t an instance of your Error.

The second failure is the inverse: any object with Error.prototype in its chain passes the check without being a real error. Here is each failure in code:

// Failure 1 — cross-realm error reads as NOT an error
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const crossRealmError = new iframe.contentWindow.Error('from iframe');

crossRealmError instanceof Error;   // → false  (wrong)
Error.isError(crossRealmError);     // → true   (correct)

// Failure 2 — fake object reads as an error
const fake = { message: "I'm not real" };
Object.setPrototypeOf(fake, Error.prototype);

fake instanceof Error;              // → true   (wrong)
Error.isError(fake);                // → false  (correct)

Both results are the documented contract rather than an accident. MDN’s reference for the method presents it as the robust alternative to instanceof Error precisely because it avoids each failure mode: a borrowed prototype is not enough to pass the check, and an error built in another realm still passes it. instanceof compares constructor identity along the prototype chain, so it gets both cases wrong.

Inputinstanceof Errorduck-typing ('message' in x)Error.isError()
Cross-realm Error (iframe/worker/vm)false⚠️ dependstrue
Object.setPrototypeOf(obj, Error.prototype)true⚠️ truefalse
class MyError extends Error instancetruetruetrue

How does Error.isError() work under the hood?

Under the hood, Error.isError() performs a branded check for an internal slot rather than inspecting the prototype chain. MDN describes the mechanism directly: the method looks for a private field that the Error() constructor installs on every error it builds. That is the same trick behind Array.isArray(), and a close cousin of how the in operator tests for a property.

That Array.isArray() analogy is the mental model to keep. Array.isArray() also accepts arrays built in a different realm, where instanceof Array reports false because each realm holds a separate Array constructor. Error.isError() brings that same realm-safe branding to errors.

The Stage 4 specification text names the slot [[ErrorData]] and keeps the IsError operation to three steps: anything that isn’t an object fails immediately, anything carrying the slot passes, and everything else fails. The slot is set at construction and can’t be forged from JavaScript.

Why a slot instead of Object.prototype.toString? Because tag-spoofing broke the old trick. The proposal author took the problem to committee: once Symbol.toStringTag existed, a check that had been both dependable and impossible to fake stopped being either. And since nothing outside Object#toString ever consulted the error slot, user code was left with no dependable test at all. Error.isError() fills exactly that gap.

Behavior details worth knowing

Error.isError() returns true for the whole error family and false for everything else, without throwing. MDN’s examples show new Error(), new TypeError(), and new DOMException() all returning true, while a call with no argument, or one passing {}, null, undefined, 17, or the string "Error", returns false. Because the spec predicate returns false for any non-object and for objects lacking the slot, primitives and null are handled cleanly rather than raising.

Correctly-extended custom classes are detected, since they inherit the brand:

class ValidationError extends Error {}
Error.isError(new ValidationError('bad input')); // → true

Only look-alikes that never call the Error constructor are rejected. The DOMException case has a nuance worth memorizing. MDN’s rule is that DOMException instances pass. DOMException is not formally a subclass of Error, because its constructor does not inherit from the Error constructor, but it carries the same brand, so branded checks treat it as an error anyway. Safari is the exception: Chrome’s round-up of the month Firefox 138 shipped records that Safari answers false for DOMException, which is why the method has not reached Baseline status even though every major engine now implements it. MDN still labels it limited availability for the same reason. Treat that one case as not-yet-uniform.

When to use Error.isError()

Use Error.isError() at boundaries (global error handlers, logging and error-reporting glue, test runners, libraries, SSR/edge, workers, iframes, and browser extensions) where a silent instanceof miss turns a real error into an empty {} in your logs. Plain instanceof is fine in tightly-scoped, same-realm code; the payoff is specifically at the edges where values cross execution contexts.

This maps to a real reporting failure mode: an instanceof check at a boundary reclassifies a genuine thrown error as a plain object, so it lands in your pipeline with no message or stack. Session replay is a useful technique here: replaying the session surfaces the console error that was actually thrown, exposing the gap between what the browser saw and what your glue code reported. The fix is to brand-check with Error.isError() at those boundaries before anything is serialized or logged.

Browser and runtime support, and a safe fallback

Error.isError() is part of ECMAScript 2026, the 17th edition, which Ecma International ratified on 30 June 2026; the proposal reached Stage 4 at the May 2025 TC39 meeting. In browsers it works from Chrome and Edge 134, Safari 18.4, and Firefox 138, which was released on 29 April 2025. On the server, Node.js 24.0.0 picked it up through the upgrade to V8 13.6, which landed it alongside Float16Array, explicit resource management, RegExp.escape, and WebAssembly Memory64.

For a drop-in upgrade that degrades gracefully on older targets, feature-detect:

function isError(value) {
  return typeof Error.isError === 'function'
    ? Error.isError(value)      // realm-safe on modern engines
    : value instanceof Error;   // fallback, not realm-safe
}

In TypeScript, Error.isError(e) also acts as a type guard, narrowing an unknown caught value to Error inside the if branch, so e.message is type-safe without a manual cast.

Conclusion

Error.isError() closes a gap that duck-typing and instanceof never could: it asks whether the engine actually branded a value as an error, so cross-realm errors and prototype-spoofed fakes both resolve correctly. Swap your boundary checks (the logging glue, the global handlers, the worker and iframe seams) over to the feature-detecting wrapper today, and keep instanceof only where the code never leaves its own realm.

FAQs

Is Error.isError() standardized or still an experimental proposal?

Error.isError() is fully standardized. It advanced to Stage 4 of the TC39 process at the 108th meeting in May 2025 and is included in ECMAScript 2026, the 17th edition of the language specification. It is no longer a proposal or experimental feature, so descriptions calling it 'not yet standardized' or 'Stage 3' are outdated. Treat it as a shipped language feature.

Does Error.isError() work with custom error classes?

Yes, as long as the class correctly extends Error. A class defined as 'class MyError extends Error {}' inherits the internal brand set by the Error constructor, so Error.isError(new MyError()) returns true. Only look-alike objects that never call the Error constructor, such as a plain object with Error.prototype forced into its chain, are rejected. Correct subclassing is the requirement, not the class name.

Does Error.isError() work in Safari?

Safari 18.4 and later support Error.isError() for regular Error objects, but support is partial. Safari currently returns false for DOMException instances, whereas the specification and other engines return true. Because of this gap, MDN does not classify the method as Baseline, and web.dev flags it as not yet uniformly available. Handle the DOMException case defensively if your code targets Safari.

Is Error.isError() faster than instanceof Error?

Both are effectively constant-time checks, so performance is not the reason to switch. instanceof walks the prototype chain while Error.isError() reads a single internal brand, but the practical difference is negligible. The real advantage is correctness: Error.isError() returns the right answer for cross-realm errors and prototype-spoofed fakes, cases where instanceof silently fails. Choose it for reliability at execution-context boundaries, not for speed.

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.