Skip to content

JavaScript Tutorial Error Handling

Custom Errors in JavaScript

Lesson 162 of 258 2 min read By Deepak Paswan Updated Oct 4, 2026

Sometimes the built-in errors are too general. A custom error has its own name and can carry extra information, so callers can react to specific problems.

script.js
JS
class ValidationError extends Error {
  constructor(message, field) {
    super(message);               // sets the message
    this.name = "ValidationError";
    this.field = field;
  }
}

function validateUser(user) {
  if (!user.email) {
    throw new ValidationError("Email is required", "email");
  }
  return true;
}

try {
  validateUser({});
} catch (error) {
  console.log(error.name);      // → ValidationError
  console.log(error.message);   // → Email is required
  console.log(error.field);     // → email
}

class ... extends Error builds on classes. Calling super(message) is required before you use this.

Telling errors apart

instanceof lets you handle each kind differently.

script.js
JS
class NotFoundError extends Error {
  constructor(resource) {
    super(`${resource} was not found`);
    this.name = "NotFoundError";
  }
}

class PermissionError extends Error {
  constructor() {
    super("You do not have permission");
    this.name = "PermissionError";
  }
}

function load(kind) {
  if (kind === "missing") throw new NotFoundError("User");
  if (kind === "private") throw new PermissionError();
  return "data";
}

for (const kind of ["ok", "missing", "private"]) {
  try {
    console.log(load(kind));
  } catch (error) {
    if (error instanceof NotFoundError) {
      console.log("404 page:", error.message);
    } else if (error instanceof PermissionError) {
      console.log("show login:", error.message);
    } else {
      throw error;       // unknown error: do not hide it
    }
  }
}

The final else { throw error; } is important: only handle errors you recognise, and rethrow the rest.

A practical example: HTTP errors

script.js
JS
class HttpError extends Error {
  constructor(status, message = `HTTP ${status}`) {
    super(message);
    this.name = "HttpError";
    this.status = status;
  }
}

// later
if (!response.ok) throw new HttpError(response.status);

You saw this idea in A Practical fetch() Helper.

A small base class

When you have several, share the boilerplate:

script.js
JS
class AppError extends Error {
  constructor(message, options) {
    super(message, options);
    this.name = this.constructor.name;    // uses the class name
  }
}

class DatabaseError extends AppError {}

const error = new DatabaseError("Connection lost");
console.log(error.name);                  // → DatabaseError
console.log(error instanceof AppError);   // → true
console.log(error instanceof Error);     // → true

When to bother

  • You need to distinguish between failures in catch.
  • You want to attach data (field, status code, id).
  • A library or shared module should expose clear failure types.

For small scripts, plain new Error("message") is enough. Custom errors are a tool for code that grows.