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

unified-error-handling

v2.1.1

Published

One error-capture API for your app, with the tracking SDK loaded only when you use it.

Readme

npm version downloads license types bundle size node

Docs · npm · GitHub · Changelog · AI Guide · Support

[!IMPORTANT] Errors only reach a service once you activate an adapter. initialize() on its own captures nothing — call await useAdapter('<name>', config) too, or every captured error is discarded. See Quick Start.

unified-error-handling gives your application one small API — captureError, setUser, addBreadcrumb — and forwards it to whichever error-tracking service you point it at. The vendor SDK is loaded through a dynamic import() at the moment you activate its adapter, so a project that only uses Sentry never pays for the other eight. The library itself has no runtime dependencies, and the React layer needs no context provider, so hooks work in any component.

| | | |---|---| | Version | 2.1.1 | | License | MIT | | Node | >=24.13.0 | | Platforms | Browser · Node.js | | Install size | ~7 kB min+brotli (core) · ~10 kB (React layer) | | Types | Bundled .d.ts (ESM + CJS) | | Status | Stable · actively maintained |

🧭 Table of Contents #

💡 Why unified-error-handling #

Error-tracking vendors each ship their own SDK, their own initialisation ritual, and their own idea of what a "user" or a "breadcrumb" is. Wiring one in spreads vendor-specific calls through your codebase, and swapping vendors later means touching every one of those call sites.

This library puts one thin layer in front of them. Your code calls captureError; the adapter translates. Changing vendor is a one-line change at startup.

| | unified-error-handling | Importing a vendor SDK directly | |---|---|---| | Call sites | vendor-neutral | vendor-specific throughout | | Switching vendor | one line at startup | edit every call site | | Runtime dependencies | none | the vendor SDK, always | | SDK cost when unused | none — loaded on activation | bundled whether used or not | | Vendor-specific features | only what the adapter maps | everything the vendor offers |

Not the right tool when you need a vendor's advanced features — Sentry performance tracing, LogRocket session search, Bugsnag release pipelines. This layer maps errors, messages, user context and breadcrumbs; anything past that is easier reached by using the vendor SDK directly. It is also the wrong choice if you want errors delivered to several services at once — see Limitations.

✨ Features #

  • Nine adapters — console, Sentry, Firebase, DataDog, Bugsnag, Rollbar, LogRocket, Raygun and AppCenter.
  • Dynamic SDK loading — a vendor SDK is imported only when its adapter is activated.
  • Zero runtime dependencies — nothing is added to your dependency tree.
  • No React provider — hooks read a module singleton, so they work in any component at any depth.
  • Automatic capture — unhandled errors, promise rejections, console.error and failed network requests.
  • Context and breadcrumbs — user, tags, device and a capped breadcrumb trail travel with every error.
  • Offline queueing — errors captured while the browser is offline are held and flushed on reconnect.
  • beforeSend hook — rewrite an error or drop it by returning null.
  • Custom adapters — send errors to your own endpoint with one send function.
  • Typed throughout — bundled declarations for both ESM and CJS.

📱 Platform Support #

| Platform | Supported | Notes | |---|---|---| | Browser | ✅ | Full support, including global handlers, offline queue and interceptors. | | Node.js | ⚠️ | Manual captureError works. Global handlers are not installed — they attach to window, so process exceptions are not captured. | | React Native | ⚠️ | Not verified by the maintainer. The core has no browser-only imports, but the offline queue and global handlers depend on window. |

📋 Requirements #

| Requirement | Version | Why | |---|---|---| | Node | >=24.13.0 | the version the package is built and tested on | | react | >=19.0.0 | optional peer — only for unified-error-handling/react | | react-dom | >=19.0.0 | optional peer — only for unified-error-handling/react |

Vendor SDKs are not dependencies. Install only the one your adapter needs — see Usage for the per-adapter list.

📦 Installation #

yarn add unified-error-handling

No build step, no native sync, no configuration file. Add the vendor SDK for whichever adapter you plan to activate, for example:

yarn add @sentry/browser

🚀 Quick Start #

Two calls at startup: initialise the store, then activate an adapter.

import { initialize, useAdapter, captureError } from 'unified-error-handling';

initialize({ enableGlobalHandlers: true });

await useAdapter('console');

captureError(new Error('Something went wrong'));

Swap 'console' for a real service when you are ready — nothing else in your code changes:

await useAdapter('sentry', { dsn: process.env.SENTRY_DSN });

🛠️ Usage #

Choosing an adapter

Each adapter loads its SDK on activation. Install the SDK yourself; the library never bundles it.

| Adapter | SDK to install | Required config | |---|---|---| | console | none — built in | none | | sentry | @sentry/browser | dsn | | datadog | @datadog/browser-rum + @datadog/browser-logs | applicationId, clientToken | | bugsnag | @bugsnag/js | apiKey | | rollbar | rollbar | accessToken | | logrocket | logrocket | appId | | raygun | raygun4js | apiKey | | appcenter | appcenter-crashes + appcenter-analytics | appSecret | | firebase | firebase | firebaseConfig — ⚠️ see Limitations |

Per-adapter options are documented at Adapters.

Adding context

import { setUser, addBreadcrumb, captureError } from 'unified-error-handling';

setUser({ id: '12345', email: '[email protected]' });

addBreadcrumb({ message: 'Opened checkout', category: 'ui', level: 'info' });

captureError(new Error('Payment declined'), {
  tags: { feature: 'checkout' },
  extra: { orderId: 'A-4471' },
});

React

Initialise once at your entry point, then use the hooks anywhere — no provider to mount.

import { initialize, useAdapter } from 'unified-error-handling';
import { ErrorBoundary, useErrorHandler } from 'unified-error-handling/react';

initialize({ enableGlobalHandlers: true });
await useAdapter('sentry', { dsn: import.meta.env.VITE_SENTRY_DSN });

function App() {
  return (
    <ErrorBoundary>
      <Checkout />
    </ErrorBoundary>
  );
}

function Checkout() {
  const handleError = useErrorHandler();

  return <button onClick={() => handleError(new Error('Boom'))}>Pay</button>;
}

useErrorHandler() returns a function. For the store's other actions use useErrorStore(), which returns { captureError, setUser, addBreadcrumb, ... }.

A custom adapter

import { createAdapter, useAdapter } from 'unified-error-handling';

createAdapter('my-backend', {
  async send(error, context) {
    await fetch('/api/errors', {
      method: 'POST',
      body: JSON.stringify({ error, context }),
    });
  },
});

await useAdapter('my-backend');

⚙️ Configuration #

Every field of initialize(config) is optional.

| Option | Type | Default | What it does | |---|---|---|---| | maxBreadcrumbs | number | 100 | Trail length; oldest are dropped first. | | enableGlobalHandlers | boolean | true | Capture window errors and unhandled rejections. Browser only. | | enableOfflineQueue | boolean | true | Hold errors while offline, flush on reconnect. | | enableConsoleCapture | boolean | true | Capture console.error calls. | | enableNetworkCapture | boolean | false | Capture failed fetch and XHR requests. | | beforeSend | (error) => NormalizedError \| null | — | Rewrite an error, or return null to drop it. | | environment | string | — | Passed through to adapters that accept it. | | release | string | — | Passed through to adapters that accept it. | | debug | boolean | false | Log to the console when an error is captured with no active adapter. |

Full reference: Configuration.

🔧 API Reference #

A signature index. Full documentation on the docs site.

Core — unified-error-handling

| Export | Signature | Docs | |---|---|---| | initialize | (config?: ErrorStoreConfig) => void | | | captureError | (error: Error \| string, context?: Partial<ErrorContext>) => void | | | captureMessage | (message: string, level?: string) => void | | | setUser | (user: UserContext \| null) => void | | | setContext | (context: Partial<ErrorContext>) => void | | | addBreadcrumb | (crumb: Omit<Breadcrumb, 'timestamp'>) => void | | | clearBreadcrumbs | () => void | | | useAdapter | (name: string, config?: unknown) => Promise<void> | | | removeAdapter | (name: string) => void | | | registerAdapter | (name: string, adapter: ErrorAdapter) => void | | | createAdapter | (name: string, config: CustomAdapterConfig) => void | | | createCustomAdapter | (config: CustomAdapterConfig) => ErrorAdapter | | | subscribe | (listener: ErrorListener) => () => void | | | flush | () => Promise<void> | | | reset | () => void | | | errorStore | the singleton behind every function above | |

Adapter classes SentryAdapter, FirebaseAdapter and CustomAdapter are also exported for advanced use.

React — unified-error-handling/react

| Export | Signature | Docs | |---|---|---| | ErrorBoundary | React.Component<ErrorBoundaryProps> | | | withErrorBoundary | (Component, options?) => Component | | | useErrorHandler | () => (error, context?) => void | | | useErrorStore | () => ErrorStoreActions & { initialized, offline, activeAdapter } | | | useAsyncError | () => (error) => void | | | useAsyncOperation | <T>(op: () => Promise<T>, deps?) => { data, loading, error, execute } | | | useErrorTracking | (componentName: string) => void — logs mount/unmount breadcrumbs | | | useComponentError | (componentName: string) => { logComponentError } | | | usePerformanceMonitor | () => { measurePerformance } | | | useExtendedErrorHandler | () => { logError, logNavigation, logUserAction, setTags } | |

Higher-order components — withErrorHandler, withAsyncErrorHandler, withPageErrorHandling, withApiErrorHandling, withFormErrorHandling, withCriticalErrorHandling — are re-exported from the same entry point.

🧩 Types #

The types a consumer actually touches:

export interface ErrorStoreConfig {
  maxBreadcrumbs?: number;
  enableGlobalHandlers?: boolean;
  enableOfflineQueue?: boolean;
  enableConsoleCapture?: boolean;
  enableNetworkCapture?: boolean;
  beforeSend?: (error: NormalizedError) => NormalizedError | null;
  environment?: string;
  release?: string;
  debug?: boolean;
}

export interface ErrorContext {
  user?: UserContext;
  device?: DeviceContext;
  custom?: Record<string, any>;
  tags?: Record<string, string>;
  extra?: Record<string, any>;
}

export interface Breadcrumb {
  timestamp: number;
  message: string;
  category?: string;
  level?: 'debug' | 'info' | 'warning' | 'error';
  data?: Record<string, any>;
}

export interface CustomAdapterConfig {
  send: (error: NormalizedError, context: ErrorContext) => Promise<void>;
  initialize?: () => Promise<void>;
  setContext?: (context: ErrorContext) => Promise<void>;
  addBreadcrumb?: (breadcrumb: Breadcrumb) => Promise<void>;
  flush?: () => Promise<void>;
  close?: () => Promise<void>;
}

NormalizedError, UserContext, DeviceContext, ErrorAdapter, ErrorLevel and ErrorListener are exported alongside them. Full listing: Types.

🧪 Examples #

| Goal | Example | |---|---| | Capture in plain JavaScript | basic-usage.js | | Wire up React | react-usage.jsx | | Send to Sentry | sentry-adapter.js | | Send to Firebase | firebase-adapter.js | | Write your own adapter | custom-adapter.js |

🎛️ Advanced Features #

  • Offline queue — while navigator.onLine is false, errors are held in memory and flushed when the browser reconnects.
  • Interceptorsconsole.error and failed fetch/XHR calls become captured errors.
  • Enrichment — each error gains device, viewport, language and a grouping fingerprint before dispatch.
  • subscribe(listener) — observe every captured error in-process, for a debug overlay or your own sink.
  • Custom adapters — one send function is the whole contract.

🚑 Recovery & Troubleshooting #

| Symptom | Cause | Fix | |---|---|---| | Nothing reaches the service | No adapter is active — the error was discarded | await useAdapter('<name>', config) after initialize() | | [ErrorStore] Not initialized warning | captureError ran before initialize() | Call initialize() at your entry point | | Failed to load <sdk> on activation | The vendor SDK is not installed | yarn add <sdk> — see the table in Usage | | [ErrorStore] Already initialized warning | initialize() called twice | Call it once; it is a module singleton | | Errors stop after switching adapter | useAdapter replaces the active adapter | Expected — only one is active at a time | | Nothing captured in Node | Global handlers bind to window | Call captureError yourself, or add your own process hooks |

🚧 Limitations #

Stated as plainly as the features:

  • One adapter is active at a time. useAdapter() makes that adapter the only destination; calling it again replaces the previous one. Errors are not fanned out to several services at once.
  • With no active adapter, captured errors are dropped. They are logged to the console only when debug: true.
  • The firebase adapter cannot load in a browser. It imports firebase/crashlytics, which the Firebase JS SDK does not provide — Crashlytics is a native-only product. Activation fails with a "Failed to load" error. Tracked as ISSUE-002.
  • Global handlers are browser-only. Node.js gets no uncaughtException or unhandledRejection hook.
  • The offline queue is in-memory. A page reload while offline loses whatever was queued.
  • reset() cannot remove global handlers. They are registered as anonymous listeners, so a captured error can still reach the store after reset() within the same page session.
  • Only the Sentry, Firebase and Custom adapter classes are re-exported from the package root. The other six are reachable through useAdapter('<name>'), which is the supported path.
  • The React entry needs React 19+. There is no React 18 build.
  • engines.node is >=24.13.0. Older Node versions produce an EBADENGINE warning on install.

❓ FAQ #

Can I send errors to two services at once? Not today. The store keeps a single active adapter. To mirror errors, write a custom adapter whose send forwards to both destinations, or call subscribe() and dispatch yourself.

Do I have to install all nine vendor SDKs? No. Install only the SDK for the adapter you activate. Nothing else is imported.

Does it work without React? Yes. The core entry has no React import. The unified-error-handling/react subpath is opt-in, and both React peers are marked optional.

Why is there no provider component? The store is a module-level singleton, so hooks read it directly. That is why a hook works in any component without wrapping your tree.

How do I stop an error from being sent? Return null from beforeSend — the error is discarded before it reaches the adapter.

📚 Documentation #

Which document answers which question:

| Document | Read it when | |---|---| | Introduction | deciding whether this fits your project | | Installation | adding it to an app | | Quick start | first time using it | | Configuration | tuning what is captured | | Adapters | wiring a specific vendor | | Custom adapters | sending errors to your own endpoint | | React guide | using the hooks and the boundary | | API reference | you need an exact signature | | Provider overview | comparing the supported services | | AI integration guide | a coding agent is implementing against it |

🔄 Changelog #

Latest release: 2.1.1 — six new adapters (DataDog, Bugsnag, Rollbar, LogRocket, Raygun, AppCenter), a fix that restores the bundled TypeScript declarations, and no source maps in the published tarball.

Full history: CHANGELOG.md.

🤝 Contributing #

Fork and open a pull request — see CONTRIBUTING.md for setup, standards, and how to request collaborator access. main is protected: every change lands through a reviewed PR.

💬 Support #

Questions and bugs: open an issue.

If this package saves you time, you can support its maintenance at aoneahsan.com/payment.

📄 License #

MIT © Ahsan Mahmood — see LICENSE.

👤 Author #

Ahsan Mahmoodaoneahsan.com · GitHub · LinkedIn · [email protected]

🔗 Links #

| | | |---|---| | Documentation | https://unified-error-handling-docs.aoneahsan.com | | npm | https://www.npmjs.com/package/unified-error-handling | | Repository | https://github.com/aoneahsan/unified-error-handling | | Issues | https://github.com/aoneahsan/unified-error-handling/issues | | Changelog | https://github.com/aoneahsan/unified-error-handling/blob/main/CHANGELOG.md | | Support the project | https://aoneahsan.com/payment |

🏷️ Keywords #

error-handling · crash-reporting · error-tracking · sentry · bugsnag · rollbar · datadog · logrocket · raygun · zero-dependency · react · typescript