Custom Errors in JavaScript: Extending Error the Right Way

Custom Errors in JavaScript: Extending Error the Right Way

Learn how to extend JavaScript’s Error class correctly, build error hierarchies, and wrap exceptions for clean, scalable error handling.

Martin Ferret

Martin Ferret

March 17, 2026

Why Custom Errors Matter

JavaScript allows throwing any value, but that flexibility often leads to unstructured and fragile error handling. Strings and generic Error instances don’t scale well in real applications.

Custom errors give failures meaning. They allow you to model domain-specific problems, handle them predictably, and keep error handling readable as your codebase grows.

Extending Error Correctly

The built-in Error class already provides everything you need: a message, a name, and a stack trace. Extending it properly is simple but must be done correctly.

class ValidationError extends Error { constructor(message) { super(message); [this.name](http://this.name/) = "ValidationError"; } }

Calling super(message) is mandatory. Without it, the error is not initialized correctly. The name property must also be set explicitly, otherwise it remains "Error".

Using Custom Errors in Practice

function parseUser(json) { const user = JSON.parse(json);

if (![user.name](http://user.name/)) { throw new ValidationError("Missing field: name"); }

return user; }

try { parseUser('{ "age": 30 }'); } catch (err) { if (err instanceof ValidationError) { console.error(err.message); } else { throw err; } }

Using instanceof is reliable, expressive, and compatible with inheritance.

Building an Error Hierarchy

As applications grow, errors naturally form hierarchies.

class PropertyRequiredError extends ValidationError { constructor(property) { super(Missing property: ${property}); [this.name](http://this.name/) = "PropertyRequiredError"; this.property = property; } }

This allows you to catch:

  • all validation errors at once
  • or specific cases when needed

Reducing Boilerplate with a Base Error

Manually assigning name in every class becomes repetitive. A common pattern is to centralize it.

class AppError extends Error { constructor(message) { super(message); [this.name](http://this.name/) = [this.constructor.name](http://this.constructor.name/); } }

class ValidationError extends AppError {} class PropertyRequiredError extends ValidationError {}

This keeps error classes short and consistent.

Wrapping Exceptions: A Scalable Pattern

Low-level errors should not leak across abstraction boundaries. Wrapping exceptions solves this.

class ReadError extends Error { constructor(message, cause) { super(message); [this.name](http://this.name/) = "ReadError"; this.cause = cause; } }

function readUser(json) { try { const user = JSON.parse(json); if (![user.name](http://user.name/)) throw new ValidationError("Missing name"); } catch (err) { throw new ReadError("Failed to read user", err); } }

try { readUser("{ bad json }"); } catch (err) { if (err instanceof ReadError) { console.error(err.message); console.error("Cause:", err.cause); } }

The caller now handles a single, meaningful error type, while still having access to the original failure.

When to Wrap Errors

Wrap errors when:

  • crossing a responsibility boundary
  • exposing a domain-level failure
  • hiding low-level implementation details

Do not wrap unknown or programming errors blindly. Rethrow them.

Conclusion

Clean error handling is not about more code.

It’s about expressing intent and maintaining control.

Extend Error properly, build clear hierarchies, and wrap exceptions when abstractions change.

Your future self, and your teammates will thank you.

More certificates.dev articles

Get the latest news and updates on developer certifications. Content is updated regularly, so please make sure to bookmark this page or sign up to get the latest content directly in your inbox.

Looking for Certified Developers?

We can help you recruit Certified Developers for your organization or project. The team has helped many customers employ suitable resources from a pool of 100s of qualified Developers.

Let us help you get the resources you need.

Contact Us
Customer Testimonial for Hiring
like a breath of fresh air
Everett Owyoung
Everett Owyoung
Head of Talent for ThousandEyes
(a Cisco company)