JavaScript Custom Errors
Custom errors are your own error classes that extend JavaScript's built-in Error class. Instead of throwing a plain "Error: something went wrong", you create meaningful, named error types — like ValidationError, NotFoundError, or NetworkError — that make your code easier to debug and test.
Why Create Custom Errors?
When you catch an error in a large application, a plain Error tells you that something broke but not what kind of problem it was. A custom error like DatabaseConnectionError or AuthenticationError tells you exactly what went wrong at a glance — and lets you handle each type differently in catch blocks.
Diagram: Plain Error vs Custom Error
Plain Error: error.name = "Error" error.message = "something failed" → Unclear — what failed? Why? Custom Error: error.name = "ValidationError" error.message = "Email format is invalid" error.field = "email" → Crystal clear — exactly what and where
Creating a Custom Error Class
Extend the built-in Error class using class and extends. Call super(message) inside the constructor to set the error message.
class ValidationError extends Error {
constructor(message) {
super(message); // sets error.message
this.name = "ValidationError"; // overrides error.name
}
}
// Using it
try {
throw new ValidationError("Email is required");
} catch (error) {
console.log(error.name); // "ValidationError"
console.log(error.message); // "Email is required"
console.log(error instanceof ValidationError); // true
console.log(error instanceof Error); // true (inherits!)
}
Diagram: Custom Error Inheritance
Error (built-in) │ ├─ name ├─ message └─ stack ValidationError extends Error │ ├─ name = "ValidationError" ├─ message = (whatever you pass) └─ stack (inherited automatically)
Adding Extra Properties
Custom errors can carry additional context — like which field failed, an error code, or an HTTP status.
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError";
this.field = field; // extra context
}
}
try {
throw new ValidationError("Must be a valid email", "email");
} catch (error) {
console.log(error.name); // "ValidationError"
console.log(error.message); // "Must be a valid email"
console.log(error.field); // "email"
}
Multiple Custom Error Types
Create one error type per category of problem in your application.
class NotFoundError extends Error {
constructor(resource) {
super(resource + " not found");
this.name = "NotFoundError";
this.resource = resource;
}
}
class NetworkError extends Error {
constructor(statusCode, message) {
super(message);
this.name = "NetworkError";
this.statusCode = statusCode;
}
}
class AuthenticationError extends Error {
constructor(message = "Authentication failed") {
super(message);
this.name = "AuthenticationError";
}
}
Handling Different Error Types in catch
Use instanceof to detect the error type and respond appropriately.
function loadUserProfile(userId) {
if (!userId) {
throw new ValidationError("User ID is required", "userId");
}
if (userId === 999) {
throw new NotFoundError("User");
}
if (userId === 0) {
throw new AuthenticationError("You must be logged in");
}
return { id: userId, name: "Priya" };
}
function handleProfileLoad(userId) {
try {
let profile = loadUserProfile(userId);
console.log("Profile loaded:", profile.name);
} catch (error) {
if (error instanceof ValidationError) {
console.log("Fix your input:", error.message, "| Field:", error.field);
} else if (error instanceof NotFoundError) {
console.log("Not found:", error.message);
} else if (error instanceof AuthenticationError) {
console.log("Login required:", error.message);
} else {
console.log("Unexpected error:", error.message);
throw error; // re-throw unknown errors
}
}
}
handleProfileLoad(null); // Fix your input: User ID is required | Field: userId
handleProfileLoad(999); // Not found: User not found
handleProfileLoad(0); // Login required: Authentication failed
handleProfileLoad(5); // Profile loaded: Priya
Diagram: Error Routing in catch
catch (error) │ ├─ instanceof ValidationError → show field validation message │ ├─ instanceof NotFoundError → show "not found" UI │ ├─ instanceof AuthenticationError → redirect to login │ └─ else (unknown error) → log and re-throw
Building an Error Hierarchy
You can create a base custom error and extend it further for more specific types.
// Base custom error
class AppError extends Error {
constructor(message, code) {
super(message);
this.name = "AppError";
this.code = code;
}
}
// Specific errors extend AppError
class DatabaseError extends AppError {
constructor(message) {
super(message, "DB_ERROR");
this.name = "DatabaseError";
}
}
class TimeoutError extends AppError {
constructor() {
super("Request timed out", "TIMEOUT");
this.name = "TimeoutError";
}
}
// All are AppError and Error at the same time
let err = new DatabaseError("Connection refused");
console.log(err instanceof DatabaseError); // true
console.log(err instanceof AppError); // true
console.log(err instanceof Error); // true
console.log(err.code); // "DB_ERROR"
Diagram: Error Class Hierarchy
Error (built-in)
│
└─ AppError
│
├─ DatabaseError
├─ TimeoutError
└─ NetworkError
│
└─ AuthenticationError
Custom Error with Stack Trace Fix
In some older environments, the stack trace may not point to where the error was thrown. Fix this in the constructor:
class CustomError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name; // auto-sets name from class name
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
class PaymentError extends CustomError {}
class ShippingError extends CustomError {}
let err = new PaymentError("Card declined");
console.log(err.name); // "PaymentError"
console.log(err.message); // "Card declined"
When to Use Custom Errors
| Situation | Custom Error |
|---|---|
| Form input is invalid | ValidationError |
| API returns 404 | NotFoundError |
| User not logged in | AuthenticationError |
| Database query fails | DatabaseError |
| Network request times out | TimeoutError |
| Payment processing fails | PaymentError |
Summary
Custom errors extend the built-in Error class to create named, meaningful error types. Add extra properties like field names, status codes, or error codes to carry debugging context. Handle each error type differently with instanceof checks in catch blocks. Build a class hierarchy to group related errors under a shared base class. Custom errors make large codebases far easier to debug, test, and maintain because you always know exactly what went wrong and why.
