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 whenhuhresolves an erroronAction— Called when a user triggers an action (before the action is executed)
<HuhProvider source={errorConfig} renderers={renderers} plugins={[myPlugin()]}>
<App />
</HuhProvider>HuhPlugin Interface
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:
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
pnpm add @sanghyuk-2i/huh-plugin-sentry @sentry/browsernpm install @sanghyuk-2i/huh-plugin-sentry @sentry/browseryarn add @sanghyuk-2i/huh-plugin-sentry @sentry/browserSetup
import { sentryPlugin } from '@sanghyuk-2i/huh-plugin-sentry';
<HuhProvider source={errorConfig} renderers={renderers} plugins={[sentryPlugin()]}>
<App />
</HuhProvider>;When an error is triggered:
Sentry.captureMessageis called with the trackId- Tags are set for
huh.trackId,huh.errorType, andhuh.locale - Variables are attached as Sentry context
When an action is triggered:
Sentry.addBreadcrumbrecords the action type and trackId
Options
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)
});| Option | Type | Default | Description |
|---|---|---|---|
level | 'fatal' | 'error' | 'warning' | 'info' | 'error' | Sentry severity level |
tags | Record<string, string> | {} | Additional tags to set on the Sentry scope |
filter | (error: ResolvedError) => boolean | — | Return false to skip reporting |
breadcrumbs | boolean | true | Whether to record action breadcrumbs |
ignoreTypes | string[] | [] | Error types to ignore (noise filtering) |
ignoreTrackIds | string[] | [] | trackIds to ignore (noise filtering) |
urlPatterns | Array<[RegExp, string]> | [] | URL pattern grouping (dynamic URL normalization) |
enrichContext | (error, context) => Record<string, unknown> | - | Additional context enrichment callback |
sensitiveKeys | Array<string | RegExp> | [] | Sensitive data masking key patterns |
Severity Auto-Mapping
When an error has a severity set, the Sentry report level is automatically mapped:
| Severity | Sentry Level |
|---|---|
CRITICAL | fatal |
ERROR | error |
WARNING | warning |
INFO | info |
For unrecognized severity values or when severity is absent, the level option is used as fallback (default: 'error').
Noise Filtering
ignoreTypes: ['TOAST'], // Don't send TOAST errors to Sentry
ignoreTrackIds: ['ERR_DISMISS'], // Ignore specific trackIds
});Sensitive Data Masking
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.
urlPatterns: [
[/\/users\/\d+/, '/users/:id'],
[/\/orders\/[a-f0-9-]+/, '/orders/:orderId'],
],
});Context Enrichment
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
pnpm add @sanghyuk-2i/huh-plugin-datadog @datadog/browser-logsnpm install @sanghyuk-2i/huh-plugin-datadog @datadog/browser-logsyarn add @sanghyuk-2i/huh-plugin-datadog @datadog/browser-logsSetup
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 messagehuh.trackId,huh.errorType,huh.locale, andhuh.variablesare attached as structured context
When an action is triggered:
datadogLogs.logger.info()logs the action type and trackId
Options
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)
});| Option | Type | Default | Description |
|---|---|---|---|
level | 'error' | 'warn' | 'info' | 'debug' | 'error' | Datadog log level for error events |
service | string | — | Service name added to all log entries |
filter | (error: ResolvedError) => boolean | — | Return false to skip logging |
actionTracking | boolean | true | Whether to log user actions |
Multiple Plugins
You can combine multiple plugins. They execute in order:
<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:
// Outside component (recommended)
const plugins = [sentryPlugin()];
function App() {
return (
<HuhProvider plugins={plugins} ...>
...
</HuhProvider>
);
}