Error Types
When things go wrong, here's what we capture
Error Types
The structure of error events. Knowing this helps you understand what you'll see in the dashboard and what you can filter on.
ErrorPayload
The full structure of an error event.
interface ErrorPayload {
message: string;
stack?: string;
type?: string;
level: 'warning' | 'error' | 'fatal';
fingerprint: string;
url?: string;
breadcrumbs?: Breadcrumb[];
tags?: Record<string, string>;
context?: Record<string, unknown>;
timestamp?: string;
anonymousId?: string;
userId?: string;
sessionId?: string;
}Prop
Type
What a real error looks like:
{
"message": "Cannot read property 'map' of undefined",
"stack": "TypeError: Cannot read property 'map' of undefined\n at ProductList (ProductList.tsx:42:15)\n at renderWithHooks (react-dom.js:1234)",
"type": "TypeError",
"level": "error",
"fingerprint": "a1b2c3d4",
"url": "https://app.example.com/products",
"breadcrumbs": [
{
"type": "navigation",
"message": "Navigated to /products",
"timestamp": "2025-01-15T10:29:55.000Z"
},
{
"type": "click",
"message": "Clicked on .filter-button",
"timestamp": "2025-01-15T10:29:58.000Z"
}
],
"context": {
"component": "ProductList",
"filters": { "category": "electronics" }
},
"timestamp": "2025-01-15T10:30:00.000Z",
"anonymousId": "anon_abc123",
"userId": "user_456"
}That's a lot of context. Way better than "Error: undefined".
Breadcrumb
A breadcrumb is something that happened before the error. It's the trail of events leading up to the crash.
interface Breadcrumb {
type: 'click' | 'navigation' | 'network' | 'console' | 'custom';
message: string;
timestamp?: string;
data?: Record<string, unknown>;
}Prop
Type
Breadcrumb Types
| Type | What It Captures | Auto-Captured? |
|---|---|---|
click | User clicks on elements | Yes |
navigation | Page changes, pushState, popstate | Yes |
network | Failed fetch/XHR requests | If enabled |
console | console.error() calls | If enabled |
custom | Whatever you add with addBreadcrumb() | No (manual) |
Auto-Captured Click Breadcrumb
{
"type": "click",
"message": "Clicked on button.submit-btn",
"timestamp": "2025-01-15T10:29:58.000Z",
"data": {
"selector": "button.submit-btn",
"text": "Submit"
}
}Auto-Captured Navigation Breadcrumb
{
"type": "navigation",
"message": "Navigated to /dashboard",
"timestamp": "2025-01-15T10:29:55.000Z",
"data": {
"from": "/login",
"to": "/dashboard"
}
}Manual Custom Breadcrumb
analytics.addBreadcrumb({
type: 'custom',
message: 'User started checkout',
data: {
cartItems: 3,
total: 149.97,
},
});Error Levels
Pick the right level for the situation:
| Level | When to Use |
|---|---|
warning | Something's off but the app still works |
error | Something broke |
fatal | The app crashed |
// Warning - degraded but working
analytics.captureMessage(
'API response slow (>2s)',
'warning',
{ endpoint: '/api/users', duration: 2500 }
);
// Error - something broke
analytics.captureMessage(
'Failed to load user preferences',
'error',
{ userId: 'user_123' }
);
// Fatal - everything's on fire
analytics.captureMessage(
'Database connection lost',
'fatal',
{ reconnectAttempts: 5 }
);Error Fingerprinting
Similar errors get grouped together using a fingerprint. The fingerprint is computed from:
- Error message (with dynamic values normalized)
- Top stack frame (file + function + line number)
This means you see "42 occurrences" instead of 42 separate errors.
Example grouping:
// Same fingerprint (same error, same location):
"Cannot read property 'name' of undefined" at ProductCard.tsx:42
"Cannot read property 'name' of undefined" at ProductCard.tsx:42
// Different fingerprints (different property):
"Cannot read property 'name' of undefined" at ProductCard.tsx:42
"Cannot read property 'price' of undefined" at ProductCard.tsx:42What Gets Captured Automatically
When errors.enabled is true:
| Source | Captured? |
|---|---|
throw new Error() | Yes |
| Unhandled promise rejections | Yes |
window.onerror | Yes |
console.error() | Only if captureConsoleErrors: true |
| Failed fetch/XHR | Only if captureNetworkErrors: true |
Filtering Errors
Use beforeSend to keep your error list clean:
{
errors: {
enabled: true,
beforeSend: (payload) => {
// Browser extension errors aren't your problem
if (payload.stack?.includes('chrome-extension://')) {
return null;
}
// ResizeObserver loop errors are noisy and usually harmless
if (payload.message.includes('ResizeObserver')) {
return null;
}
// Third-party script errors with no stack trace
if (payload.message === 'Script error.' && !payload.stack) {
return null;
}
return payload;
},
},
}Return null to drop the error. Return the payload (modified or not) to send it.