In the course of developing something, it is necessary to have own error classes for reflecting particular things that can go wrong. For errors in network operations, HttpError is necessary, for searching operations-NotFoundError, for database operations- DbError, and more. They should support basic error properties such as name, message, and stack. Also, they can include other properties. For example, HttpError objects can contain a statusCode such as 403, 404, or 500.
JavaScript allows using throw with any argument. So, technically, custom errors don’t need inheritance from Error.
Extending Error
Now, let’s discover what extending error is. To be more precise, we can start from a function readUser(json), which reads JSON with user data.
So, a valid json looks like this:
let json = `{ "name": "John", "age": 25 }`;
Internally, JSON.parse is used.
In case it gets malformed json then it throws SyntaxError. Even if json is syntactically correct it doesn’t consider that the user is valid. It can miss essential data such as name, age, or other properties.
The readUser(json) function both reads JSON and checks the data. In case the format is wrong, then an error occurs. As the data is syntactically correct, it’s not a SyntaxError. It is a ValidationError.
For ValidationError it is necessary to inherit from the built-in Error class.
Here is the code for extending:
// "Pseudocode" for the built-in Error class
class Error {
constructor(message) {
this.message = message;
this.errorName = "Error"; // different names for different built-in error classes
this.stack = < call stack > ; // non-standard, but most environments support it
}
}
In the example below, ValidationError is inherited from it:

Now, let’s try using it in createUser(json), like this:

So, in the code above the try..catch block handles ValidationError and SyntaxError from JSON.parse.
It is especially interesting how instanceof is used for checking a particular error in the (*) line.
The err.name can also look like here:
// ...
// instead of, err instanceof SyntaxError
} else if (err.name == "SyntaxError") { // (*)
}
However, the version with instanceof is better, as the next step is to extend ValidationError, making subtypes of it. The instanceof check will keep working for new inheriting classes.
Further Inheritance
The ValidationError class is very universal. Hence, various things can go wrong. For example, a property can be in a wrong format or it can be absent. Let’s consider the PropertyRequiredError for absent properties. It carries extra information about the missing property. Here is how it looks like:

The new class PropertyRequiredError is easy in usage. All you need is to do is passing the property name: new PropertyRequiredError(property). The constructor creates a human-readable message.
Please, consider that this.name in PropertyRequiredError constructor is assigned manually. So, assigning this.name = <class name> in each custom error class. For avoiding that the basic error class, which assigns this.name = this.constructor.name is made. It can inherit all the custom errors. It can be called ErrorName.
Here it is and other custom error classes:

So, now custom errors are shorter.
Wrapping Exceptions
The purpose of the createUser function is reading the user data. In the process, various errors may occur.
The code calling createUser must handle the errors. In the example below, it applies multiple ifs in the catch block, checking the class and handling known errors and rethrowing the unknown ones.
The scheme will look as follows:
In the code above, there are two kinds of errors. Of course, they can be more. The technique, represented in this part is known as “wrapping extensions”. Let’s start at making a new class ReadError to present a generic data reading error. The readUser function catches the data reading errors, occurring inside it (for example, ValidationError and SyntaxError) and create ReadError uthuinstead.
The ReadError object keeps the reference to the original error inside the cause property.
try {
//...
createUser() // the potential error source
//...
}
catch (err) {
if (err instanceof ValidationError) {
// handle validation error
} else if (err instanceof SyntaxError) {
// handle syntax error
} else {
throw err; // unknown error, rethrow it
}
}
Let’s take a look at the code, which specifies ReadError demonstrating its usage in readUser and try..catch:

The approach, described above is named “wrapping exceptions” as “low-level” exceptions are taken and wrapped into ReadError, which is more abstract.
Summary
In brief, we can state that, normally, it is possible to inherit from Error and other built-in classes. All you need is to take care of the name property, not forgetting to call super.
The instanceof class can also be applied to check for specific errors. It also operates with inheritance. At times when an error object comes from a third-party library, the name property can be used.
A widespread and useful technique is wrapping exceptions. With it, a function can handle low-level exceptions, creating high-level errors, instead of different low-level ones.