JavaScript Custom Errors
JavaScript allows developers to create their own custom error types by extending the built-in Error class. This capability enables more detailed error handling
Custom Errors in JavaScript
JavaScript lets you create your own error types by extending the built-in Error class. A custom error is just a class that inherits from Error (or another error type) and carries a meaningful name plus any extra data the situation calls for.
This page covers why you would want custom errors, how to define them correctly, how to distinguish error types with instanceof, how to attach context (status codes, field names, the original cause), and how to organize a hierarchy of errors for a real application.
Why create custom errors?
Throwing a plain new Error("...") works, but it gives the caller only a string to inspect. Custom errors solve three problems:
- Type-based handling. With
instanceof ValidationErroryou can react to one kind of failure without parsing message text, which is fragile and locale-dependent. - Extra context. A custom class can carry structured fields — an HTTP
statusCode, the offendingfield, a retry hint — instead of cramming everything into the message. - Clear hierarchies. A shared base class (for example
AppError) lets a top-level handler catch all of your application's errors with a singleinstanceof, while still letting inner code throw specific subtypes.
If you only need to learn the throwing/catching mechanics first, read Error handling: try...catch. Custom errors build directly on class inheritance and extending built-in classes.
Basics of Extending the Error Class
To create a custom error, you extend the Error class. The new class inherits Error's message, stack, and toString(), and you add whatever else you need.
Creating a Custom Error Class
Here’s the minimal pattern:
class ValidationError extends Error {
constructor(message) {
super(message); // Pass the message to the Error constructor (sets this.message)
this.name = "ValidationError"; // Override the default name "Error"
}
}Two details matter:
super(message)must come first. Inside a subclass constructor you cannot usethisbefore callingsuper().Error's constructor setsthis.messageand captures the stack trace.- Set
this.name. Without it the error reports as"Error"in messages andtoString(). Settingnamemakes logs and theswitch (error.name)pattern readable.
You may also see Object.setPrototypeOf(this, new.target.prototype) in some examples. With modern transpilers and native classes it is usually unnecessary, but it does not hurt and guarantees instanceof works even when code is compiled down to ES5 or shared across realms (iframes/worker boundaries). The examples below keep it for safety.
Once created, the error behaves like any other:
const e = new ValidationError("bad input");
e.name; // "ValidationError"
e instanceof Error; // true — it is still a real Error
e.toString(); // "ValidationError: bad input"Using Custom Errors
Once you have defined a custom error, you can throw it in your application like any standard error:
In this example, an email is validated against a regular expression. If the validation fails, a ValidationError is thrown. This error is then caught in the try...catch block, and if it's an instance of ValidationError, a specific message is logged.
Handling Multiple Types of Custom Errors
You might want to create various types of errors for different parts of your application. Here’s how you can handle multiple custom errors:
This setup allows for distinct handling of different error types, making the application more robust and easier to debug.
Prefer instanceof over comparing error.name strings when you can: instanceof also matches subclasses, so it survives refactoring better.
Building an Error Hierarchy with a Base Class
In real applications it pays to give all your errors a common ancestor. A top-level handler can then catch every "expected" application error in one place while still letting deeper code throw precise subtypes. The cause option (ES2022) lets a higher-level error wrap the lower-level one that triggered it, preserving the original for debugging.
Here this.name = this.constructor.name removes the need to repeat the name in every subclass, and the single instanceof AppError check captures NotFoundError, ConflictError, and any future subtype.
Advanced Custom Error Handling in JavaScript
Expanding on the basics, we can apply custom errors to more complex scenarios like asynchronous operations and specific business-logic cases.
Example 1: Custom API Error Handling
This example demonstrates how to create and use a custom error for handling API request issues, such as when a requested resource is not found or the server returns an error. The async/await flow here pairs with error handling with promises.
Explanation
- ApiError Class: This custom class captures API-specific errors, storing the HTTP status code along with a custom message.
- fetchData Function: Attempts to fetch data from a provided URL. If the response is not successful, it throws an
ApiErrorwith a detailed message and status code. - Error Handling: Errors are caught and handled appropriately. API-related errors are logged with detailed information, while unexpected errors are also caught and logged.
Example 2: Custom Validation Error Handling
Use this example to handle errors related to data validation, such as checking user input.
Explanation
- ValidationError Class: A custom error class that helps in identifying which specific field of the input data failed the validation check.
- validateUser Function: Checks the validity of user data. If the data does not meet certain criteria, it throws a
ValidationError. - Error Handling: Catches validation errors and logs them with detailed information. Other types of errors are also handled separately.
Best Practices and Gotchas
- Always call
super(message)first, before touchingthis. - Set
nameso logs andtoString()are readable;this.name = this.constructor.namedoes it once for a whole hierarchy. - Prefer
instanceofovererror.namecomparisons — it matches subclasses too. - Rethrow what you do not recognize. A
catchthat swallows everything (catch (e) {}) hides real bugs. Handle your known types andthrow errorfor the rest, as the examples do. - Use
causeto wrap, not replace. When you catch a low-level error and throw a higher-level one, pass{ cause: original }so the stack trace and root cause survive. - Do not subclass
Errorto control flow. Errors are for exceptional conditions, not for normal branching.
Conclusion
Creating custom error classes in JavaScript by extending the Error class is a powerful technique for handling specific types of errors in a more granular way. It enhances the clarity and maintainability of error handling in your code, allowing you to provide more specific feedback and actions based on different error conditions. This method not only helps in debugging but also improves the reliability of your applications by ensuring that each error type is caught and handled appropriately.