W3docs

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 ValidationError you 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 offending field, 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 single instanceof, 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 use this before calling super(). Error's constructor sets this.message and captures the stack trace.
  • Set this.name. Without it the error reports as "Error" in messages and toString(). Setting name makes logs and the switch (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:

javascript— editable

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:

javascript— editable

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.

javascript— editable

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.

javascript— editable

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 ApiError with 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.

javascript— editable

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 touching this.
  • Set name so logs and toString() are readable; this.name = this.constructor.name does it once for a whole hierarchy.
  • Prefer instanceof over error.name comparisons — it matches subclasses too.
  • Rethrow what you do not recognize. A catch that swallows everything (catch (e) {}) hides real bugs. Handle your known types and throw error for the rest, as the examples do.
  • Use cause to 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 Error to 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.

Practice

Practice
What is the purpose of using extended error classes in JavaScript?
What is the purpose of using extended error classes in JavaScript?
Practice
In a custom error subclass constructor, why must super(message) be called before using this?
In a custom error subclass constructor, why must super(message) be called before using this?
Practice
What does the ES2022 cause option let you do when throwing a custom error?
What does the ES2022 cause option let you do when throwing a custom error?
Was this page helpful?