Suggest an editImprove this articleRefine the answer for “Extending Error with custom properties”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**To extend `Error`, you create your own subclass, call `super(message)` in the constructor and add the fields you need: `code`, `status`, `details`, `cause`, `context`.** Such an object keeps the standard `message` and `stack`, works correctly with `instanceof` and lets you tell error types apart in a `catch` block or in a central middleware. ```javascript class AppError extends Error { constructor(message, code) { super(message); this.name = "AppError"; this.code = code; Error.captureStackTrace?.(this, this.constructor); } } ``` **Key point:** `super(message)` gives you the standard part of the error, your own fields give it structure, and `Error.captureStackTrace` hides the constructor from the stack trace.Shown above the full answer for quick recall.Answer (EN)Image**Extending `Error` means declaring your own class that inherits from `Error`, passing the message to the parent constructor via `super(message)` and adding your own properties to it.** You then throw such errors like any others, but in `catch` you get a structure instead of a bare string: a code, an HTTP status, details and a cause. ## Theory ### TL;DR - A custom error class is `class AppError extends Error`, and the constructor must call `super(message)`. - After `super` you can add any fields you like: `code`, `status`, `details`, `context`. - `this.name = this.constructor.name` makes the error name readable in logs and in the stack trace. - `Error.captureStackTrace(this, this.constructor)` (V8) removes your own constructor from the stack trace. - Since ES2022 there is a standard `cause` property that stores the original error: `new Error(msg, { cause: err })`. - A class hierarchy (`AppError` -> `ValidationError`, `AuthError`) gives convenient `instanceof` checks. ### Quick example ```javascript class AppError extends Error { constructor(message, code) { super(message); // pass the message to the parent Error this.name = "AppError"; // set the error name this.code = code; // add our own property } } try { throw new AppError("Something went wrong", "SERVER_ERROR"); } catch (e) { console.log(e.name); // AppError console.log(e.message); // Something went wrong console.log(e.code); // SERVER_ERROR console.log(e.stack); // call stack } ``` Your error now has both the standard properties (`message`, `stack`) and the custom one (`code`). ### The base class and the stack trace The minimal contract of a custom error is three things: a `super(message)` call, a correct `name` and a clean stack trace. ```javascript class AppError extends Error { constructor(message, code) { super(message); this.name = this.constructor.name; // the name comes from the class, not a literal this.code = code; Error.captureStackTrace?.(this, this.constructor); } } ``` - `super(message)` is mandatory: without it `this` is unavailable, and `message` and `stack` stay unset. - `this.name = this.constructor.name` yields `"AppError"`, `"ValidationError"` and so on automatically, so you do not repeat a string literal in every subclass. - `Error.captureStackTrace` exists in V8 (Node.js, Chrome). The second argument excludes the constructor frame, so the first line points at the place where the error was actually thrown. Calling it with `?.()` keeps the code safe in engines that lack the method. ### Status, details and an error hierarchy For the HTTP layer it is convenient to carry a status and structured details on the error itself: ```javascript class HttpError extends Error { constructor(message, status = 500, details = {}) { super(message); this.name = this.constructor.name; this.status = status; this.details = details; if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } } } try { throw new HttpError("User not found", 404, { userId: 123 }); } catch (err) { console.log(err.name); // HttpError console.log(err.status); // 404 console.log(err.details); // { userId: 123 } } ``` Several such classes build a whole tree of errors: ```javascript class AppError extends Error { constructor(message, code) { super(message); this.name = this.constructor.name; this.code = code; Error.captureStackTrace?.(this, this.constructor); } } class ValidationError extends AppError { constructor(message, field) { super(message, "VALIDATION_ERROR"); this.field = field; } } class AuthError extends AppError { constructor(message = "Unauthorized") { super(message, "AUTH_ERROR"); this.status = 401; } } ``` Usage: ```javascript try { throw new ValidationError("Invalid email", "email"); } catch (err) { if (err instanceof ValidationError) { console.log("Error in field:", err.field); } console.log(err.code); // VALIDATION_ERROR } ``` Because `ValidationError` inherits from `AppError`, the check `err instanceof AppError` is true as well. That gives you two levels of handling: a generic one for all of your own errors and a targeted one for a specific type. ### The cause property (ES2022) The modern ECMAScript standard supports a `cause` property that stores the original error which led to the current one. This way you do not lose the original stack trace when you wrap a low level error into your own domain error. ```javascript try { JSON.parse("invalid JSON"); } catch (parseErr) { throw new AppError("Failed to parse JSON", "PARSE_ERROR", { cause: parseErr }); } ``` For that to work, the third argument has to be forwarded to `super`: ```javascript class AppError extends Error { constructor(message, code, options = {}) { super(message, options); // the engine sets this.cause itself this.name = this.constructor.name; this.code = code; this.cause = options.cause; // compatibility with older engines } } ``` ### Central handling in Express or NestJS The main benefit of custom classes is that a single handler can answer correctly for any of your errors and hide internal details for everything else: ```javascript app.use((err, req, res, next) => { if (err instanceof AppError) { res.status(err.status || 500).json({ error: err.name, message: err.message, code: err.code, details: err.details, }); } else { res.status(500).json({ error: "InternalError", message: "Unknown error" }); } }); ``` ### Why extend Error at all | Reason | Example | | --- | --- | | Clear, understandable messages | `"Email is required"` instead of `"Unexpected token"` | | Structure and typing | `ValidationError`, `AuthError`, `HttpError` | | Easier logging | `code`, `status` and `details` land in the log | | Keeping the original cause | through `cause` | | Easier debugging | different error types are easy to tell apart in `catch` | The summary template: ```javascript class MyError extends Error { constructor(message, customProp) { super(message); this.name = "MyError"; this.customProp = customProp; Error.captureStackTrace?.(this, this.constructor); } } throw new MyError("Database error", { query: "SELECT * FROM users" }); ``` ### Common mistakes - **Forgetting `super(message)`.** The constructor throws a `ReferenceError` on the first use of `this`, and calling `super()` with no argument leaves `message` empty. - **Not setting `name`.** Logs and `stack` then show plain `Error`, and your custom type becomes invisible. - **Relying on `this.constructor.name` after minification.** A bundler may rename the class, so public error codes belong in a separate `code` field rather than in `name`. - **Checking the type with `err.name === "AppError"` instead of `instanceof`.** A string is easy to break, and `instanceof` also sees the whole hierarchy. - **Transpiling to ES5 without care.** With an ES5 target, `class X extends Error` breaks the prototype chain and `instanceof` stops working. The fix is `Object.setPrototypeOf(this, new.target.prototype)` in the constructor, or an ES2015+ target. - **Putting sensitive data on the error.** `details` often ends up in logs and in the HTTP response, so passwords, tokens and personal data must not go there. - **Assuming `Error.captureStackTrace` exists everywhere.** It is a non standard V8 extension, so make the call optional.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.