Subclassing the built-in Error class lets you create domain-specific error types — ValidationError, NetworkError, AuthenticationError — that carry structured, meaningful information beyond a generic error message string.
1Custom Errors | JavaScript Tutorial - In-Depth Guide Part 1
Extending the built-in Error class with 'class MyError extends Error' creates a custom error type with its own name and, critically, working instanceof checks.
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}Extending Error
2Custom Errors | JavaScript Tutorial - In-Depth Guide Part 2
Custom errors can carry additional structured properties beyond just a message — like which field failed validation, or an HTTP status code.
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
throw new ValidationError('Must be a valid email', 'email');Carrying Structured Data
3Custom Errors | JavaScript Tutorial - In-Depth Guide Part 3
instanceof checks let calling code branch on the specific type of error that occurred, handling each kind appropriately.
try {
validateForm(data);
} catch (err) {
if (err instanceof ValidationError) {
highlightField(err.field);
} else {
showGenericError();
}
}instanceof Branching
4Custom Errors | JavaScript Tutorial - In-Depth Guide Part 4
In older transpilation setups (targeting pre-ES2015 output), extending Error can break instanceof — a known workaround explicitly resets the prototype.
class MyError extends Error {
constructor(message) {
super(message);
Object.setPrototypeOf(this, MyError.prototype); // legacy transpile fix
}
}The Prototype Chain Gotcha
5Custom Errors | JavaScript Tutorial - In-Depth Guide Part 5
A well-designed application defines a small hierarchy of custom error classes — often a common base error, extended by more specific subtypes — mirroring the different failure modes the app actually needs to distinguish.
class AppError extends Error {}
class NotFoundError extends AppError {}
class AuthError extends AppError {}
if (err instanceof AppError) { /* handle known app errors */ }Designing an Error Hierarchy
6Step-by-Step Breakdown
Extending the built-in Error class with 'class MyError extends Error' creates a custom error type with its own name and, critically, working instanceof checks.
Checkpoint: When creating a custom error class, why is calling super(message) important?
- →It ensures the base Error's message and stack trace behavior still works
- →It's optional and has no real effect
Custom errors can carry additional structured properties beyond just a message — like which field failed validation, or an HTTP status code.
instanceof checks let calling code branch on the specific type of error that occurred, handling each kind appropriately.
Checkpoint: Can instanceof be used to distinguish which specific custom error type was thrown?
- →Yes, this is the main benefit of a proper class hierarchy
- →No, all Error subclasses look identical to instanceof
In older transpilation setups (targeting pre-ES2015 output), extending Error can break instanceof — a known workaround explicitly resets the prototype.
A well-designed application defines a small hierarchy of custom error classes — often a common base error, extended by more specific subtypes — mirroring the different failure modes the app actually needs to distinguish.
Next, we'll explore 'Built-in Error Types'.
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Map Custom Error Types to Clear, Actionable Accessible Messages
A ValidationError's structured `field` property lets you move focus directly to the specific invalid form field and announce a precise error message via aria-describedby, rather than a generic, unhelpful alert for screen reader users.
SEO Implications
- 1
No Direct SEO Effect
Custom error classes are an application-code-quality concern; SEO relevance is limited to more reliable error handling preventing broken page states.
Best Practices
Always Call super(message) and Set this.name in a Custom Error
Skipping super() loses the base Error's message/stack handling; setting `this.name` explicitly ensures the error's string representation and logging tools correctly identify its specific type.
Design a Small, Purposeful Error Hierarchy Instead of One-Off Classes Everywhere
Grouping errors by how calling code needs to react to them (not by every minor variation) keeps the hierarchy useful and easy to reason about.
Frequent Bugs
Creating a custom error class but forgetting to call super(message), resulting in `error.message` being empty and the stack trace being unreliable.
Always call `super(message)` as the first statement in a custom error's constructor before setting any additional properties.
In an older transpiled build target, `err instanceof CustomError` unexpectedly returning false even though the error actually is a CustomError instance.
Add `Object.setPrototypeOf(this, CustomError.prototype)` right after the super() call as a defensive fix for older compilation targets.
Real-World Examples
A Structured API Error Class
A frontend app needed to distinguish network failures, authentication failures, and validation failures returned from its backend API, each requiring different UI handling.
class ApiError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'ApiError';
this.statusCode = statusCode;
}
}
if (response.status === 401) {
throw new ApiError('Unauthorized', 401);
}