Skip to main content

Adapters

An adapter is the bridge between the store and an error-tracking service. Each adapter knows how to load its SDK, initialize it, and forward a NormalizedError to it. The store keeps a registry of adapters and one active adapter at a time.

Activating an adapter

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

await useAdapter('sentry', {
dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0',
});

useAdapter(name, config?):

  1. Looks up an already-registered adapter by name, or loads the built-in one.
  2. Calls adapter.initialize(config) — which dynamically import()s the service SDK.
  3. Sets it as the active adapter.
  4. Replays the current context and existing breadcrumbs into the adapter.
  5. Flushes any queued offline errors through it.

Because step 2 does a dynamic import, useAdapter() is async. Await it so you know the adapter is ready.

Built-in adapter names

console, sentry, firebase, datadog, bugsnag, rollbar, logrocket, raygun, appcenter. The console adapter needs no SDK; the rest dynamically import their package (see Installation).

Switching adapters

There is exactly one active adapter. Calling useAdapter() again re-points it:

await useAdapter('console'); // development
await useAdapter('sentry', sentryCfg); // now Sentry is active; console is still registered but inactive

This is the core mental model: the store dispatches each captured error to the single active adapter, not to every registered adapter. A failing adapter captureError is caught and logged inside the store, so it never throws out into your code.

Removing an adapter

import { removeAdapter } from 'unified-error-handling';

removeAdapter('sentry'); // calls adapter.close() and unregisters it

If you remove the active adapter, there is no active adapter until you call useAdapter() again (captured errors are then just enriched + sent to listeners).

Registering an adapter instance directly

If you have an adapter instance (for example one of the exported adapter classes, or a custom one), register it under a name without going through dynamic loading:

import { registerAdapter, SentryAdapter } from 'unified-error-handling';

registerAdapter('sentry', new SentryAdapter());
await useAdapter('sentry', { dsn: '...' }); // now activates the instance you registered

The package also re-exports SentryAdapter, FirebaseAdapter, and CustomAdapter for advanced usage.

Creating a custom adapter

For your own backend, use createAdapter() or createCustomAdapter() — no SDK, just a send callback. See the custom adapters guide.

Adapter capabilities

Every adapter must implement name, initialize, and captureError. The rest are optional and the store calls them only if present:

MethodRequiredPurpose
captureError(error)yesForward a normalized error to the service.
captureMessage(message, level?)noForward a plain message.
setContext(context)noPush user/tags/extra context to the service.
addBreadcrumb(breadcrumb)noPush a breadcrumb to the service.
flush()noFlush buffered events.
close()noTear down the SDK / release resources.

See the providers overview for which optional methods each built-in adapter implements.