npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

custom-error-creator

v1.4.0

Published

Create typed, structured error classes from simple definitions.

Downloads

2,656

Readme

custom-error-creator

npm version GitHub CI bundle size

Create typed, structured error classes from simple definitions.

Why?

Lets you define errors declaratively and get back proper classes with:

  • Typed error codes — catch and handle errors by code, not by sniffing message strings
  • HTTP status codes — errors carry their status, so your error handler doesn't need a mapping table
  • Message templates — parameterised messages with compile-time checking of required params
  • PascalCase namesNOT_FOUND becomes NotFound in stack traces automatically
  • Clean stack traces — the error points at your throw site, not the class internals
  • Standard cause chaining — wrap underlying errors using the ES2022 cause option

I found myself using similar patterns for error handling in multiple projects, and this module encapsulates the common boilerplate into a simple, reusable API.

Install

npm install custom-error-creator

Quick start

import { createErrorClass } from "custom-error-creator";

const NotFound = createErrorClass({
  code: "NOT_FOUND",
  message: "Resource {resource} not found",
  status: 404,
});

throw new NotFound({ resource: "User" });
// NotFound: Resource User not found
//     at handler (/app/routes.ts:12:9)

API

createErrorClass(definition)

Creates a single error class from a definition.

const ValidationError = createErrorClass({
  code: "VALIDATION_ERROR",
  message: "{field} is invalid: {reason}",
  status: 400,
});

status is optional. Omit it for errors that don't map to an HTTP status — the instance then has no status property at all (reading .status is a compile-time error, and "status" in err is false at runtime):

const ConfigError = createErrorClass({
  code: "CONFIG_ERROR",
  message: "Invalid config",
});

const err = new ConfigError();
err.status; // ❌ compile error — no such property

To recognise errors regardless of status, use the looser isErrorWithCode guard, which checks only code.

createErrorClassesByCode(definitions)

Creates multiple error classes at once, returned as an object keyed by code.

import { createErrorClassesByCode } from "custom-error-creator";

const errors = createErrorClassesByCode([
  { code: "NOT_FOUND", message: "Resource {resource} not found", status: 404 },
  { code: "UNAUTHORIZED", message: "Access denied", status: 401 },
  {
    code: "VALIDATION_ERROR",
    message: "{field} is invalid: {reason}",
    status: 400,
  },
]);

throw new errors.NOT_FOUND({ resource: "User" });
throw new errors.UNAUTHORIZED();
throw new errors.VALIDATION_ERROR({ field: "email", reason: "too short" });

createErrorClassesByName(definitions)

Creates multiple error classes at once, returned as an object keyed by PascalCase name.

import { createErrorClassesByName } from "custom-error-creator";

const errors = createErrorClassesByName([
  { code: "NOT_FOUND", message: "Resource {resource} not found", status: 404 },
  { code: "UNAUTHORIZED", message: "Access denied", status: 401 },
]);

throw new errors.NotFound({ resource: "User" });
throw new errors.Unauthorized();

isCustomError(error)

Type guard for errors created by this module that carry a numeric status. Narrows to the exported CustomError type. Duck-typed on code + status, so it also matches manually augmented errors with those properties.

isErrorWithCode(error)

A looser alternative to isCustomError when you only want to check against error.code. Narrows to the exported ErrorWithCode type. It checks code alone and ignores status, so it matches every error this module creates (with or without a status) as well as unrelated errors that carry a string code, such as Node's system errors (ENOENT, etc.). Narrow further on error.code when you need to be specific.

Constructor signatures

The constructor is flexible depending on whether the message template has parameters.

Errors with template parameters

When the default message contains {param} placeholders, the params object is required:

const NotFound = createErrorClass({
  code: "NOT_FOUND",
  message: "Resource {resource} not found",
  status: 404,
});

// Params required — typed and checked at compile time
new NotFound({ resource: "User" });
new NotFound({ resource: "User" }, { cause: underlyingError });

// Custom message — params become optional and untyped
new NotFound("Thing not found");
new NotFound("{thing} is gone", { thing: "Widget" });
new NotFound("Gone", { cause: underlyingError });
new NotFound("{x} missing", { x: "y" }, { cause: underlyingError });

Errors without template parameters

const Unauthorized = createErrorClass({
  code: "UNAUTHORIZED",
  message: "Access denied",
  status: 401,
});

new Unauthorized();
new Unauthorized("Custom message");
new Unauthorized("Custom message", { cause: underlyingError });

Function messages

When a {param} template isn't expressive enough, pass a function as the message. Its parameter must be an object type, which defines the typed params required by the constructor, and you control the formatting:

const TooMany = createErrorClass({
  code: "TOO_MANY_ITEMS",
  message: (params: { items: number[] }) =>
    `Found ${params.items.length} items, expected fewer`,
  status: 400,
});

// Params required — typed as { items: number[] } from the function signature
new TooMany({ items: [1, 2, 3] });
new TooMany({ items: [1, 2, 3] }, { cause: underlyingError });
new TooMany({ items: "nope" }); // ❌ wrong param type, caught at compile time

The parameter must be an object — (n: number) => ... is a compile-time error, since the constructor passes params as an object.

Declaring the parameter as optional (or giving it a default) makes params optional in the constructor too. The function is always called with an object ({} when no params are passed), so a default like = {} works as expected:

const DocError = createErrorClass({
  code: "DOC_ERROR",
  message: ({ docType }: { docType?: string } = {}) =>
    docType ? `${docType} error` : "other error message",
  status: 400,
});

new DocError(); // "other error message"
new DocError({ docType: "observation" }); // "observation error"
new DocError({ cause: underlyingError }); // cause without params

A zero-argument function behaves like an error without template parameters:

const Unauthorized = createErrorClass({
  code: "UNAUTHORIZED",
  message: () => "Access denied",
  status: 401,
});

new Unauthorized();

The optional custom message passed to the constructor is always a plain string (new TooMany("Custom message")) — the function only produces the default message.

As with {param} templates, cause is a reserved parameter name: a function message whose params include a cause key is a compile-time error. This keeps cause meaning only "the error's cause" (passed as the second constructor argument), with no shadowing. Because a function's parameter types are erased at runtime, this is enforced at compile time only — unlike the {cause} template check, which also throws at runtime.

Error instance properties

Every error instance has the standard Error properties plus:

const err = new NotFound({ resource: "User" });

err.message; // "Resource User not found"
err.name; // "NotFound"
err.code; // "NOT_FOUND"
err.status; // 404 (the property is absent if the definition omits `status`)
err.stack; // stack trace pointing at the throw site
err.cause; // underlying error, if provided

Static class properties

Error classes also expose code and name as static properties, useful for comparisons without instantiating:

const NotFound = createErrorClass({
  code: "NOT_FOUND",
  message: "Resource {resource} not found",
  status: 404,
});

NotFound.code; // "NOT_FOUND"
NotFound.name; // "NotFound"

Error handling patterns

By code

try {
  await getUser(id);
} catch (err) {
  if (err.code === "NOT_FOUND") {
    // handle missing resource
  }
}

By instanceof

try {
  await getUser(id);
} catch (err) {
  if (err instanceof NotFound) {
    // handle missing resource
  }
}

Wrapping errors with cause

Use the standard cause option to chain underlying errors:

try {
  await db.query("SELECT ...");
} catch (err) {
  throw new NotFound({ resource: "User" }, { cause: err });
}

The cause is set using the native Error constructor option (ES2022), so it behaves identically to new Error("msg", { cause }) — non-enumerable and compatible with all standard tooling.

Reserved parameter names

The parameter name cause is reserved and cannot be used as a message parameter, so it only ever refers to the error's cause (the second constructor argument). For string templates this is enforced at both compile time and runtime; for function messages, whose param types are erased at runtime, it is enforced at compile time only.

// ❌ Compile error at definition time + runtime throw
const Bad = createErrorClass({
  code: "BAD",
  message: "Failed because {cause}",
  status: 500,
});

// ❌ Compile error at definition time (function params)
const AlsoBad = createErrorClass({
  code: "BAD",
  message: (params: { cause: string }) => `Failed because ${params.cause}`,
  status: 500,
});

Type safety

Template parameters are fully type-checked when using the default message:

const NotFound = createErrorClass({
  code: "NOT_FOUND",
  message: "Resource {resource} not found",
  status: 404,
});

new NotFound({ resource: "User" }); // ✅
new NotFound({ resorce: "User" }); // ❌ typo caught at compile time
new NotFound({}); // ❌ missing required param
new NotFound(); // ❌ params required

Instance properties are also typed:

const err = new NotFound({ resource: "User" });
err.code; // type: "NOT_FOUND"
err.status; // type: 404
err.name; // type: "NotFound"

License

MIT