Suggest an editImprove this articleRefine the answer for “How to create a custom error type”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**You create a custom error type with a class that extends `Error`: in the constructor you call `super(message, { cause })`, set `this.name = this.constructor.name` and add your own fields, such as `code`, `status` or `details`.** Calling `Error.captureStackTrace(this, this.constructor)` (on V8) strips the constructor itself out of the stack, so the trace starts at the place where the error was thrown. From a base `AppError` it is then convenient to derive concrete types, `ValidationError`, `AuthError`, and tell them apart in `catch` with `instanceof`. ```javascript class AppError extends Error { constructor(message, { code, cause } = {}) { super(message, { cause }); this.name = this.constructor.name; this.code = code ?? "APP_ERROR"; } } ``` **Key point:** `extends Error` plus `super(message, { cause })`, your own `name` and a `code` field give you an error that is reliably recognised with `instanceof` and easy to serialise into a log or an API response.Shown above the full answer for quick recall.Answer (EN)Image**A custom error type is an ordinary class that extends `Error`: in the constructor you call `super(message, { cause })`, set `this.name` and add your own fields such as `code`, `status` or `details`.** Such a class behaves like a real error, keeps its stack and `cause`, and can be recognised precisely with `instanceof`. ## Theory ### TL;DR - `class MyError extends Error` plus `super(message)` is the minimum that already works. - `this.name = this.constructor.name` makes the error name meaningful in logs. - The second argument of `super(message, { cause })` keeps the original error in the chain. - `Error.captureStackTrace(this, this.constructor)` (V8) strips the constructor out of the stack. - Custom fields (`code`, `status`, `details`) make the error fit for APIs and monitoring. - An `AppError` -> `ValidationError` / `AuthError` hierarchy gives precise handling in `catch`. ### Quick example ```javascript class ValidationError extends Error { constructor(message, details) { super(message); this.name = "ValidationError"; this.details = details; } } try { throw new ValidationError("Email is required", { field: "email" }); } catch (e) { console.log(e instanceof ValidationError, e.name, e.details); // true ValidationError { field: "email" } } ``` ### The base template (ES2022+) ```javascript class AppError extends Error { constructor(message, { code, cause } = {}) { super(message, { cause }); // keeps stack and cause this.name = this.constructor.name; // "AppError" this.code = code ?? "APP_ERROR"; if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } } } // Specific cases class ValidationError extends AppError { constructor(message, details, opts = {}) { super(message, { ...opts, code: "VALIDATION_ERROR" }); this.details = details; // any fields of your own } } class AuthError extends AppError { constructor(message = "Unauthorized", opts = {}) { super(message, { ...opts, code: "AUTH_ERROR" }); this.status = 401; } } ``` Usage: ```javascript function parseUser(input) { if (!input.email) { throw new ValidationError("Email is required", { field: "email" }); } return input; } try { parseUser({}); } catch (e) { if (e instanceof ValidationError) { console.log(e.code, e.details); // VALIDATION_ERROR { field: "email" } } else { console.error("Unexpected error", e); } } ``` ### Wrapping the original error with cause This is useful when you intercept a low level exception and want to add context without losing the original: ```javascript try { JSON.parse("not valid JSON"); } catch (e) { throw new AppError("Failed to parse the config", { cause: e, code: "CONFIG_PARSE" }); } ``` Later you can inspect the whole chain: ```javascript console.error(err.cause); // the original error ``` ### The TypeScript version ```typescript type AppErrorCode = "APP_ERROR" | "VALIDATION_ERROR" | "AUTH_ERROR"; class AppError extends Error { code: AppErrorCode; declare cause?: unknown; // so that TS knows about cause constructor(message: string, opts: { code?: AppErrorCode; cause?: unknown } = {}) { super(message, { cause: opts.cause }); this.name = new.target.name; this.code = opts.code ?? "APP_ERROR"; if ((Error as any).captureStackTrace) { (Error as any).captureStackTrace(this, new.target); } } } class ValidationError extends AppError { details?: Record<string, unknown>; constructor(message: string, details?: Record<string, unknown>, opts: { cause?: unknown } = {}) { super(message, { ...opts, code: "VALIDATION_ERROR" }); this.details = details; } } ``` Here `new.target.name` yields the name of the class actually being constructed, so subclasses get the right `name` with no extra code. ### A couple of practical techniques - **Serialisation** (logging or an API response): ```javascript function errorToJson(err) { return { name: err.name, message: err.message, code: err.code, stack: err.stack, details: err.details, cause: err.cause instanceof Error ? err.cause.message : err.cause, }; } ``` - **HTTP binding.** Add a `status` (400, 401, 404, 500) to your errors and map them to responses at the middleware level. - **Guarantee `instanceof`.** Modern engines are fine with it, but the `extends Error` template together with `captureStackTrace` (where available) is a dependable base. If you compile down to ES5, you also need `Object.setPrototypeOf(this, new.target.prototype)`. ### Common mistakes - **Forgetting `super(message)`.** Without it `message` comes out empty and the stack is truncated. - **Not setting `name`.** In logs and monitoring the error shows up as a plain `Error`, leaving you nothing to filter on. - **Losing the original error.** You catch a low level exception and throw a new one without `cause`, so the cause of the failure can no longer be recovered. - **Relying on `e.constructor.name` instead of `instanceof`.** Minification renames classes, while `instanceof` keeps working. - **Counting on `Error.captureStackTrace` everywhere.** It is a V8 specific API, so the call must always be guarded by a presence check. - **Putting sensitive data into the error.** The error object often lands in logs in full, so passwords and tokens do not belong in `details`.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.