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

@vercel/error

v0.0.2

Published

A lightweight toolkit for structured, actionable errors for humans and agents

Readme

@vercel/error

Structured error primitives for humans and agents.

Table of contents

Install

pnpm add @vercel/error

Entry points

| Import | Purpose | | ---------------------- | -------------------------------------------------------------- | | @vercel/error | Core: VercelError, createErrors, guards, extractors, types | | @vercel/error/server | Server: errorResponse, wantsAnsi | | @vercel/error/client | Client: parseErrorResponse, fromErrorResponse | | @vercel/error/format | Format primitives: frame, hint, fix, link |

Philosophy

A good error answers up to five questions:

  1. What happened? → message
  2. Why did it happen? → reason
  3. What could help? → hint
  4. How to fix it? → fix
  5. Where to learn more? → link

The more questions you answer, the faster the person (or agent) on the other end can resolve the issue.

The rest follows from that:

  • Errors are a unit of communication between services, not crash artifacts. They carry context for humans, agents, and observability tools.
  • Errors should be useful wherever they surface — terminal, log aggregator, HTTP response. The format adapts to the context.
  • Errors are too fundamental to be locked to a framework or runtime.

Quick start

import { VercelError } from '@vercel/error';

throw new VercelError('Database connection pool exhausted', {
  code: 'pool_exhausted',
  scope: 'database',
  statusCode: 503,
  reason: 'All 20 connections are in use and none have been released.',
  hint: 'Consider using pgBouncer for connection pooling.',
  fix: 'Increase max_connections or add pgBouncer.',
  link: 'https://vercel.com/docs/storage/neon#connection-pooling',
});

toString() auto-detects your environment and renders structured output. In a color-capable terminal (TTY or FORCE_COLOR), you get ANSI colors and Unicode tree connectors:

error: VercelError [database:pool_exhausted] Database connection pool exhausted
│
├── All 20 connections are in use and none have been released.
├─▸ hint: Consider using pgBouncer for connection pooling.
├─▸ fix: Increase max_connections or add pgBouncer.
╰─▸ read more: https://vercel.com/docs/storage/neon#connection-pooling

In piped or browser environments, the output falls back to plain indented text. See Terminal formatting for the full detection logic.

For services that create many errors with shared context, see Error factories.

Error anatomy

Every VercelError field falls into one of three groups.

Identity

| Field | Type | Description | | ------------ | ------------------- | ------------------------------------------------------------------------------------------------------- | | code | string (readonly) | Machine-readable error code, e.g. "pool_exhausted" | | scope | string (readonly) | Namespace or service that produced the error, e.g. "database" | | statusCode | number | HTTP status code. errorResponse uses this to set the response status (falls back to 500 when unset) |

Context

| Field | Type | Description | | ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | message | string | What happened. The first constructor argument | | reason | string | Why it happened. Explains the root cause | | hint | string | What could help. Advisory information for the developer | | fix | string | How to fix it. An actionable remediation step | | link | string | Where to learn more. A URL to relevant documentation | | userMessage | string | Client-safe message. Set this when message contains internal details you don't want clients to see. errorResponse uses it instead of message when set |

Observability

| Field | Type | Description | | ------------ | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | metadata | Record<string, SerializableValue> | Structured context for debugging and logging. Supports arbitrary nesting. Server-side only — excluded from HTTP error responses | | attributes | Record<string, string \| number \| boolean \| ...> | Flat key-value pairs for OpenTelemetry spans, Sentry tags, and metrics dashboards | | requestId | string | Correlation ID for tracing an error across services | | cause | unknown | Standard Error cause for chaining. Passed through to super() and walkable via getRootCause |

Error factories

createErrors defines a scoped error namespace for a service or module. Every error created through the factory gets the scope injected automatically. The report callback defaults to console.error when not provided.

import { createErrors } from '@vercel/error';

const errors = createErrors({
  scope: 'database',
  report: (error) => sentry.captureException(error),
});

The factory returns three methods:

| Method | Behavior | | --------------------------- | ------------------------------------------- | | create(message, options?) | Create and return a VercelError | | raise(message, options?) | Create and throw (return type: never) | | report(message, options?) | Create, report via the callback, and return |

errors.create('Connection failed', { code: 'conn_failed' });
errors.raise('Timeout', { code: 'timeout' }); // throws
errors.report('Pool exhausted', { code: 'pool_exhausted' }); // reports + returns

Factory-level attributes and metadata

Set attributes and metadata at the factory level. These merge with per-error values automatically, so every error gets baseline context without repetition.

const errors = createErrors({
  scope: 'billing',
  attributes: { 'service.name': 'billing-api' },
  metadata: { region: 'us-east-1' },
});

errors.create('Charge failed', {
  code: 'charge_failed',
  attributes: { 'stripe.error': 'card_declined' },
  metadata: { customerId: 'cus_123' },
});
// attributes: { 'service.name': 'billing-api', 'stripe.error': 'card_declined' }
// metadata: { region: 'us-east-1', customerId: 'cus_123' }

Custom error classes

Pass ErrorClass to create instances of a custom subclass. Type inference flows through, so create, raise, and report all return your subclass type.

import { VercelError, createErrors } from '@vercel/error';
import type { VercelErrorOptions } from '@vercel/error';

class DatabaseError extends VercelError {
  readonly retryable = true;

  constructor(message: string, options?: VercelErrorOptions) {
    super(message, options);
    this.name = 'DatabaseError';
  }
}

const errors = createErrors({
  scope: 'database',
  ErrorClass: DatabaseError,
});

const err = errors.create('Pool exhausted');
err.retryable; // true, fully typed as DatabaseError

Terminal formatting

VercelError.toString() auto-detects your environment and renders structured output with zero configuration.

Auto-detection

The formatter checks the environment in order and uses the first match:

| Environment | Tree structure | ANSI color | Example | | ------------------------- | -------------- | ---------- | ------------------------------ | | NO_COLOR env var set | Yes | No | NO_COLOR=1 node app.js | | FORCE_COLOR env var set | Yes | Yes | FORCE_COLOR=1 node app.js | | TTY terminal | Yes | Yes | Running directly in a terminal | | Piped, CI, non-TTY | No | No | node app.js \| cat | | Browser | No | No | window is defined |

NO_COLOR respects the no-color.org convention. You still get Unicode tree characters for structure, but no escape codes. FORCE_COLOR forces full ANSI output even without a TTY, which is useful in Docker containers or CI systems that support color.

Color hierarchy

When ANSI color is active, each element has a distinct visual treatment:

| Element | Color | Weight | | --------------------------------------- | --------- | -------------------- | | error: label | Red | Bold | | Error name and [scope:code] qualifier | Red | Normal | | Error message | Red | Bold | | hint: prefix | Yellow | Bold | | fix: prefix | Green | Bold | | read more: prefix | Inherited | Bold, URL underlined | | reason text | Inherited | Normal | | Connectors (├──, ) | Inherited | Dim |

Connector types

The tree uses arrow connectors (├─▸) for actionable items (hint, fix, link) and line connectors (├──) for informational items (reason).

Plain text fallback

When tree structure is disabled (piped output, browser), sections are indented with two spaces:

error: VercelError [database:pool_exhausted] Database connection pool exhausted
  All 20 connections are in use and none have been released.
  hint: Consider using pgBouncer for connection pooling.
  fix: Increase max_connections or add pgBouncer.
  read more: https://vercel.com/docs/storage/neon#connection-pooling

Format primitives

The @vercel/error/format entry point exports functions for building custom formatted output outside of VercelError. Use these when formatting errors you don't own or building custom CLI output.

import { frame, hint, fix, link } from '@vercel/error/format';

const output = frame('Build failed: missing entry point', [
  'No index.ts or index.js found in the src/ directory.',
  hint('Check your tsconfig.json paths configuration.'),
  fix('Create src/index.ts or update the "main" field in package.json.'),
  link('https://vercel.com/docs/builds#entry-points'),
]);

console.error(output);

All format functions are nil-safe: pass undefined or null and they return undefined, which frame filters out. frame auto-detects formatting the same way VercelError.toString() does.

HTTP error responses

errorResponse builds a complete HTTP error response with content negotiation. It returns { status, body, headers } for use with any framework.

import { errorResponse } from '@vercel/error/server';

const { status, body, headers } = errorResponse(error);
return new Response(body, { status, headers });

Content negotiation

Pass a Request or HeadersLike object as the second argument to enable content negotiation. The response body switches from JSON to structured ANSI text when the client signals a preference:

const { status, body, headers } = errorResponse(error, request);
return new Response(body, { status, headers });

Clients signal ANSI preference in three ways (checked in order):

| Signal | Example | | -------------------------------- | -------------------------------- | | X-Error-Format: ansi header | curl -H "X-Error-Format: ansi" | | Accept: text/plain+ansi header | Custom client header | | User-Agent containing curl/ | Auto-detected for curl users |

Without a signal (or without a request object), the response is always JSON.

Plain parameters

You can pass plain parameters instead of a VercelError instance.

import { errorResponse } from '@vercel/error/server';

const { status, body, headers } = errorResponse({
  status: 429,
  code: 'rate_limited',
  message: 'Too many requests',
  hint: 'Wait 60 seconds before retrying.',
});

return new Response(body, { status, headers });

Wire format

The JSON response body follows a canonical shape:

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests",
    "reason": "You exceeded 100 requests per minute.",
    "hint": "Wait 60 seconds before retrying.",
    "fix": "Implement exponential backoff in your client.",
    "link": "https://vercel.com/docs/limits#rate-limits"
  }
}

All fields except message are optional. When using a VercelError, userMessage is used for message in the response, falling back to error.message when userMessage isn't set.

Manual ANSI detection

Use wantsAnsi directly when you need to check ANSI preference outside of errorResponse:

import { wantsAnsi } from '@vercel/error/server';

if (wantsAnsi(request)) {
  // render ANSI-formatted output
}

Accepts a Request, any HeadersLike object, or null/undefined.

Parsing error responses

Validate unknown JSON

parseErrorResponse validates that unknown data matches the ErrorResponse shape. Returns the validated response or undefined if invalid.

import { parseErrorResponse } from '@vercel/error/client';

const res = await fetch('/api/deploy');
if (!res.ok) {
  const parsed = parseErrorResponse(await res.json());
  if (parsed) {
    console.log(parsed.error.code, parsed.error.message);
  }
}

Reconstruct a VercelError

fromErrorResponse reconstructs a VercelError from a validated ErrorResponse. Use this at service boundaries when you want to re-throw, enrich, or chain an upstream error.

import { parseErrorResponse, fromErrorResponse } from '@vercel/error/client';

const res = await fetch('https://api.vercel.com/v1/deployments');
if (!res.ok) {
  const parsed = parseErrorResponse(await res.json());
  if (parsed) {
    throw fromErrorResponse(parsed, {
      statusCode: res.status,
      scope: 'upstream',
      cause: new Error(`${res.url} returned ${res.status}`),
    });
  }
}

The response message becomes both the VercelError message and userMessage, since it was already client-safe on the wire.

Error utilities

Guards and extractors for working with errors from any source. All exported from @vercel/error.

| Function | Description | | ------------------------------ | ---------------------------------------------------------------------------------------------------------- | | isVercelError(value) | Type guard for VercelError. Uses instanceof with a Symbol.for fallback for cross-realm detection | | isError(value) | Type guard for Error. Handles cross-realm errors via prototype walking | | isErrorLike(value) | Type guard for objects with a message string property | | hasCode(error, code) | Check if an error has a specific code. Also accepts an array of codes | | getMessage(error, fallback?) | Extract a message from any value. Returns undefined when no message is found and no fallback is provided | | getRootCause(error) | Walk the cause chain to the root. Handles cycles via WeakSet |

Subclassing

VercelError is designed for subclassing. The Custom error classes section above shows how to use subclasses with createErrors — this section covers the general pattern.

Two things to keep in mind:

  1. Hardcode this.name in your constructor. Bundler minification turns class names into single letters, which breaks Sentry grouping and log readability. String literals are minification-safe.

  2. Accept VercelErrorOptions as the options type so your subclass stays compatible with createErrors.

import { VercelError } from '@vercel/error';
import type { VercelErrorOptions } from '@vercel/error';

class NetworkError extends VercelError {
  readonly retryable: boolean;

  constructor(
    message: string,
    options?: VercelErrorOptions & { retryable?: boolean },
  ) {
    super(message, options);
    this.name = 'NetworkError';
    this.retryable = options?.retryable ?? false;
  }
}

const error = new NetworkError('Connection refused', {
  code: 'conn_refused',
  retryable: true,
});

error.name; // 'NetworkError' (stable across minification)
error.retryable; // true
error.toString(); // uses NetworkError in the header