Quiz

How can you create custom error objects?

Topics
JavaScript

TL;DR

Extend Error when callers need to distinguish a domain failure from other exceptions or inspect structured context. Call super(message, options), give the class a useful name, and add stable properties such as a machine-readable code. Preserve an underlying error with the standard cause option instead of replacing its diagnostic context.

class ValidationError extends Error {
constructor(message, { field, cause } = {}) {
super(message, { cause });
this.name = 'ValidationError';
this.code = 'INVALID_INPUT';
this.field = field;
}
}
try {
throw new ValidationError('Email is invalid', { field: 'email' });
} catch (error) {
if (error instanceof ValidationError) {
console.log(error.field); // email
} else {
throw error;
}
}

How can you create custom error objects?

Extending the Error class

To create a custom error object, you can extend the built-in Error class. This allows you to inherit the properties and methods of the Error class while adding your own custom properties and methods.

class CustomError extends Error {
constructor(message) {
super(message);
this.name = 'CustomError';
}
}

Adding custom properties

You can add custom properties to your custom error class to provide more context about the error.

class CustomError extends Error {
constructor(message, errorCode) {
super(message);
this.name = 'CustomError';
this.errorCode = errorCode;
}
}
try {
throw new CustomError('This is a custom error message', 404);
} catch (error) {
console.log(error.name); // CustomError
console.log(error.message); // This is a custom error message
console.log(error.errorCode); // 404
}

Custom methods

You can also add custom methods to your custom error class to handle specific error-related logic.

class CustomError extends Error {
constructor(message, errorCode) {
super(message);
this.name = 'CustomError';
this.errorCode = errorCode;
}
logError() {
console.error(`${this.name} [${this.errorCode}]: ${this.message}`);
}
}
try {
throw new CustomError('This is a custom error message', 404);
} catch (error) {
error.logError(); // CustomError [404]: This is a custom error message
}

Using instanceof to check for custom errors

You can use the instanceof operator to check if an error is an instance of your custom error class.

class CustomError extends Error {
constructor(message, errorCode) {
super(message);
this.name = 'CustomError';
this.errorCode = errorCode;
}
}
try {
throw new CustomError('This is a custom error message', 404);
} catch (error) {
if (error instanceof CustomError) {
console.log('Caught a CustomError');
} else {
console.log('Caught a different type of error');
}
}

Catch a custom error only where you can recover, translate it into an expected API result, or add useful context. Re-throw unexpected errors so programming defects are not silently treated as validation failures.

Preserving the original cause

Wrapping a low-level error can give callers a domain-specific error without losing the original failure:

class UserLookupError extends Error {
constructor(userId, options) {
super(`Could not load user ${userId}`, options);
this.name = 'UserLookupError';
this.userId = userId;
}
}
async function loadUser(userId) {
try {
return await database.findUser(userId);
} catch (cause) {
throw new UserLookupError(userId, { cause });
}
}

Error stacks, causes, and custom properties may contain secrets or personal data. Log them only in trusted systems with suitable redaction, and send a deliberately shaped public error response rather than serializing the error object directly.

Further reading

Exercises

Check your understanding
Beta
Check your understanding Exercise
Check your understanding Exercise

Which choices make a custom JavaScript error useful to callers and diagnostics? Select all that apply.