Skip to content

Plugins let you hook into huh's error and action lifecycle. Use them to send errors to monitoring services (Sentry, Datadog), track analytics events, or add custom logging — without modifying your Provider setup.

How It Works

Pass an array of plugins to your framework's HuhProvider via the plugins prop. Each plugin can implement two hooks:

  • onError — Called when huh resolves an error
  • onAction — Called when a user triggers an action (before the action is executed)
tsx
<HuhProvider source={errorConfig} renderers={renderers} plugins={[myPlugin()]}>
  <App />
</HuhProvider>

HuhPlugin Interface

ts
interface HuhPlugin {
  name: string;
  onError?: (error: ResolvedError, context: HuhErrorContext) => void;
  onAction?: (error: ResolvedError, action: ErrorAction) => void;
}

interface HuhErrorContext {
  trackId: string;
  variables?: Record<string, string>;
  locale?: string;
  severity?: Severity;
}

Both hooks are optional. If a plugin throws, the error is caught and logged as a warning — it won't break your app.

Writing a Custom Plugin

A plugin is a factory function that returns a HuhPlugin object:

ts

export function analyticsPlugin(): HuhPlugin {
  return {
    name: 'my-analytics',
    onError(error, context) {
      analytics.track('error_shown', {
        trackId: context.trackId,
        errorType: error.type,
        locale: context.locale,
      });
    },
    onAction(error, action) {
      analytics.track('error_action', {
        trackId: error.trackId,
        actionType: action.type,
      });
    },
  };
}

Using @sanghyuk-2i/huh-plugin-sentry

The official Sentry plugin automatically reports errors and records action breadcrumbs.

Installation

bash
pnpm add @sanghyuk-2i/huh-plugin-sentry @sentry/browser
bash
npm install @sanghyuk-2i/huh-plugin-sentry @sentry/browser
bash
yarn add @sanghyuk-2i/huh-plugin-sentry @sentry/browser

Setup

tsx
import { sentryPlugin } from '@sanghyuk-2i/huh-plugin-sentry';

<HuhProvider source={errorConfig} renderers={renderers} plugins={[sentryPlugin()]}>
  <App />
</HuhProvider>;

When an error is triggered:

  • Sentry.captureMessage is called with the trackId
  • Tags are set for huh.trackId, huh.errorType, and huh.locale
  • Variables are attached as Sentry context

When an action is triggered:

  • Sentry.addBreadcrumb records the action type and trackId

Options

ts
  level: 'warning', // Sentry severity (default: 'error')
  tags: { env: 'production' }, // Additional Sentry tags
  filter: (error) => error.type !== 'TOAST', // Skip certain errors
  breadcrumbs: false, // Disable action breadcrumbs (default: true)
});
OptionTypeDefaultDescription
level'fatal' | 'error' | 'warning' | 'info''error'Sentry severity level
tagsRecord<string, string>{}Additional tags to set on the Sentry scope
filter(error: ResolvedError) => booleanReturn false to skip reporting
breadcrumbsbooleantrueWhether to record action breadcrumbs
ignoreTypesstring[][]Error types to ignore (noise filtering)
ignoreTrackIdsstring[][]trackIds to ignore (noise filtering)
urlPatternsArray<[RegExp, string]>[]URL pattern grouping (dynamic URL normalization)
enrichContext(error, context) => Record<string, unknown>-Additional context enrichment callback
sensitiveKeysArray<string | RegExp>[]Sensitive data masking key patterns

Severity Auto-Mapping

When an error has a severity set, the Sentry report level is automatically mapped:

SeveritySentry Level
CRITICALfatal
ERRORerror
WARNINGwarning
INFOinfo

For unrecognized severity values or when severity is absent, the level option is used as fallback (default: 'error').

Noise Filtering

ts
  ignoreTypes: ['TOAST'], // Don't send TOAST errors to Sentry
  ignoreTrackIds: ['ERR_DISMISS'], // Ignore specific trackIds
});

Sensitive Data Masking

ts
  sensitiveKeys: ['password', 'token', /^secret/],
  // variables: { password: '1234', token: 'abc', secretKey: 'xyz' }
  // → Sent to Sentry as { password: '[REDACTED]', token: '[REDACTED]', secretKey: '[REDACTED]' }
});

URL Grouping

Normalizes dynamic URL segments for Sentry fingerprint grouping.

ts
  urlPatterns: [
    [/\/users\/\d+/, '/users/:id'],
    [/\/orders\/[a-f0-9-]+/, '/orders/:orderId'],
  ],
});

Context Enrichment

ts
  enrichContext: (error, context) => ({
    requestId: getRequestId(),
    userId: getCurrentUser()?.id,
    route: window.location.pathname,
  }),
});

Using @sanghyuk-2i/huh-plugin-datadog

The official Datadog plugin sends error logs via @datadog/browser-logs and tracks actions as info-level log entries.

Installation

bash
pnpm add @sanghyuk-2i/huh-plugin-datadog @datadog/browser-logs
bash
npm install @sanghyuk-2i/huh-plugin-datadog @datadog/browser-logs
bash
yarn add @sanghyuk-2i/huh-plugin-datadog @datadog/browser-logs

Setup

tsx
import { datadogLogs } from '@datadog/browser-logs';
import { datadogPlugin } from '@sanghyuk-2i/huh-plugin-datadog';

// Initialize Datadog Logs first
datadogLogs.init({
  clientToken: '<CLIENT_TOKEN>',
  site: 'datadoghq.com',
  forwardErrorsToLogs: true,
  sessionSampleRate: 100,
});

<HuhProvider source={errorConfig} renderers={renderers} plugins={[datadogPlugin()]}>
  <App />
</HuhProvider>;

When an error is triggered:

  • datadogLogs.logger.error() is called with [huh] <trackId> as the message
  • huh.trackId, huh.errorType, huh.locale, and huh.variables are attached as structured context

When an action is triggered:

  • datadogLogs.logger.info() logs the action type and trackId

Options

ts
  level: 'warn', // Log level (default: 'error')
  service: 'my-app', // Service name added to log context
  filter: (error) => error.type !== 'TOAST', // Skip certain errors
  actionTracking: false, // Disable action logging (default: true)
});
OptionTypeDefaultDescription
level'error' | 'warn' | 'info' | 'debug''error'Datadog log level for error events
servicestringService name added to all log entries
filter(error: ResolvedError) => booleanReturn false to skip logging
actionTrackingbooleantrueWhether to log user actions

Multiple Plugins

You can combine multiple plugins. They execute in order:

tsx
<HuhProvider
  source={errorConfig}
  renderers={renderers}
  plugins={[sentryPlugin(), analyticsPlugin(), loggingPlugin()]}
>
  <App />
</HuhProvider>

TIP

In React, define your plugins array outside the component or wrap it in useMemo to avoid unnecessary re-renders:

tsx
// Outside component (recommended)
const plugins = [sentryPlugin()];

function App() {
  return (
    <HuhProvider plugins={plugins} ...>
      ...
    </HuhProvider>
  );
}

Released under the MIT License.