Ch. 1 · JavaScript

JavaScript Custom Error Classes

Extend Error to carry a stable name and instanceof, preserve the underlying cause, and avoid the string-throwing mistakes interviewers look for.

~3 min readintermediateupdated Oct 4, 2026

Throwing plain strings or objects loses a stack trace and any reliable way to branch in a catch. A small custom error class keeps a stable name, supports instanceof, and can carry a cause that records the original failure without hiding it.

Before you start

You should be comfortable with class, extends, super and try...catch. This article focuses on the error contract; it does not cover logging pipelines or framework error middleware.

Step-by-step walkthrough

Step 1: Extend the built-in Error

Subclass Error so instances still carry message and stack. Set this.name to the class name: the default Error name would otherwise appear in logs and stack traces, making the subclass indistinguishable from a generic failure.

Step 2: Preserve the cause

Pass { cause } through to super. The cause property keeps the underlying error available for diagnostics while the outer error describes the layer that failed, for example a ValidationError caused by a schema mismatch. Callers can branch on the outer type and still log the inner one.

Step 3: Keep the hierarchy meaningful

Model error categories that callers actually branch on, not one class per message. A catch that tests instanceof AppError should be able to handle every expected application failure, while transport or programming errors stay outside that hierarchy.

Worked scenario

Run this with Node.js. The subclass keeps instanceof for both itself and Error.

class ValidationError extends Error {
  constructor(message, options) {
    super(message, options);
    this.name = 'ValidationError';
  }
}
try {
  throw new ValidationError('Invalid email', { cause: new Error('schema mismatch') });
} catch (error) {
  console.log(error instanceof ValidationError); // true
  console.log(error instanceof Error); // true
  console.log(error.name); // ValidationError
  console.log(error.cause.message); // schema mismatch
}
JavaScript

Walk through the example

super(message, options) sets message, stack and cause. Setting name makes logs read ValidationError: Invalid email instead of Error: Invalid email. In the catch, instanceof distinguishes this expected failure from an unexpected one, and cause retains the deeper reason for the log without flattening it into a string.

Common mistake

Throwing a string (throw 'failed') or a bare object, which produces no stack and forces string comparison in catch. A subtler mistake is calling Error.captureStackTrace directly: it is a V8 extension, so portable code should rely on the standard stack that super already provides.

Verify the behavior

Assert instanceof for both the subclass and Error. Confirm name shows up in a serialized log line. Throw a subclass with a cause and check both error.message and error.cause.message. Finally, verify that a catch for the base class still catches the subclass, so broad handling and specific handling can coexist.

Interview exercise

Should an HTTP layer throw Error or a custom HttpError, and where should the status code live?

Answer and reasoning

Use a custom HttpError that carries a status field and a short, safe message. The status is data the transport layer needs, so it belongs on the error rather than being parsed out of the message string. Callers can then branch on instanceof HttpError and read status directly, while a top-level handler maps any unknown error to a generic 500 without leaking internal detail.

Follow-up discussion

Does extending Error break instanceof across realms? It can: an error created in an iframe has a different Error constructor, so prefer structural checks such as a name or a brand field when errors cross realms or are serialized. Are aggregate errors useful? AggregateError collects several failures, such as Promise.any rejections, and pairs well with a custom subclass when callers must inspect every cause.

Continue learning

See how promises surface these errors in JavaScript promise finally and read the MDN Error reference. Test yourself with the JavaScript MCQs.

More in JavaScript

read ✓JavaScript · mid

JavaScript Date and Timezone Pitfalls

Parse and format dates without timezone surprises: zero-based months, date-only versus date-time parsing, and why UTC storage avoids drift.

~3 min readread →
read ✓JavaScript · mid

JavaScript Number Precision and BigInt

Why 0.1 + 0.2 is not 0.3, where safe integer range ends, and when BigInt is the right tool for large identifiers and money.

~2 min readread →
esc