tessl install tessl/npm-posthog-js@1.335.0PostHog Browser JS Library is a comprehensive browser analytics and feature management SDK that enables developers to capture user events, track product analytics, manage feature flags, record session replays, and implement feedback mechanisms like surveys and conversations in web applications.
Error tracking allows you to capture and monitor exceptions, errors, and crashes in your application automatically or manually.
Manually captures an exception with optional additional properties.
/**
* Manually captures an exception
* @param error - Error object or any throwable value
* @param additionalProperties - Additional properties to attach to the exception event
* @returns CaptureResult if successful, undefined otherwise
*/
function captureException(
error: unknown,
additionalProperties?: Properties
): CaptureResult | undefined;Usage Examples:
// Capture basic error
try {
riskyOperation();
} catch (error) {
posthog.captureException(error);
}
// Capture with additional context
try {
await processPayment(amount);
} catch (error) {
posthog.captureException(error, {
payment_amount: amount,
user_id: userId,
payment_method: 'credit_card'
});
}
// Capture custom error
const customError = new Error('Custom error message');
customError.code = 'CUSTOM_ERROR_CODE';
posthog.captureException(customError, {
context: 'user_action',
severity: 'high'
});
// Capture non-Error objects
posthog.captureException('String error message', {
source: 'api_response'
});
posthog.captureException({
message: 'Custom error object',
details: { foo: 'bar' }
});Starts automatic capture of uncaught errors and unhandled promise rejections.
/**
* Starts automatic exception capture
* @param config - Configuration for autocapture behavior
*/
function startExceptionAutocapture(config?: ExceptionAutoCaptureConfig): void;Usage Examples:
// Start with default settings (all error types)
posthog.startExceptionAutocapture();
// Customize what to capture
posthog.startExceptionAutocapture({
capture_unhandled_errors: true, // window.onerror
capture_unhandled_rejections: true, // unhandledrejection
capture_console_errors: false // console.error
});
// Capture only promise rejections
posthog.startExceptionAutocapture({
capture_unhandled_errors: false,
capture_unhandled_rejections: true,
capture_console_errors: false
});
// Start after user consent
function onErrorTrackingConsent() {
posthog.startExceptionAutocapture();
}Stops automatic exception capture.
/**
* Stops automatic exception capture
*/
function stopExceptionAutocapture(): void;Usage Examples:
// Stop autocapture
posthog.stopExceptionAutocapture();
// Stop on user opt-out
function onUserOptOut() {
posthog.stopExceptionAutocapture();
localStorage.setItem('error_tracking_disabled', 'true');
}
// Stop temporarily
function pauseErrorTracking() {
posthog.stopExceptionAutocapture();
}
function resumeErrorTracking() {
posthog.startExceptionAutocapture();
}/**
* Configuration for automatic exception capture
*/
interface ExceptionAutoCaptureConfig {
/**
* Capture uncaught errors (window.onerror)
* @default true
*/
capture_unhandled_errors?: boolean;
/**
* Capture unhandled promise rejections
* @default true
*/
capture_unhandled_rejections?: boolean;
/**
* Capture console.error calls
* @default false
*/
capture_console_errors?: boolean;
}/**
* Result of capturing an exception
*/
interface CaptureResult {
/**
* Event name that was captured
*/
event: string;
/**
* Properties that were sent with the event
*/
properties: Record<string, any>;
}/**
* Object containing event properties
*/
type Properties = Record<string, Property | Property[]>;/**
* A single property value
*/
type Property = string | number | boolean | null | undefined;Configure exception capture during initialization:
// Enable with default settings
posthog.init('token', {
capture_exceptions: true
});
// Enable with custom configuration
posthog.init('token', {
capture_exceptions: {
capture_unhandled_errors: true,
capture_unhandled_rejections: true,
capture_console_errors: true
}
});
// Disable exception capture
posthog.init('token', {
capture_exceptions: false
});Always include relevant context when capturing errors:
// ✅ Good: Rich context
try {
await fetchUserData(userId);
} catch (error) {
posthog.captureException(error, {
user_id: userId,
operation: 'fetch_user_data',
api_endpoint: '/api/users',
timestamp: Date.now(),
retry_count: retryCount,
network_status: navigator.onLine ? 'online' : 'offline'
});
}
// ❌ Bad: No context
try {
await fetchUserData(userId);
} catch (error) {
posthog.captureException(error);
}Categorize errors by severity:
function captureErrorWithSeverity(error, severity, context = {}) {
posthog.captureException(error, {
severity: severity,
...context
});
}
// Usage
try {
criticalOperation();
} catch (error) {
captureErrorWithSeverity(error, 'critical', {
operation: 'payment_processing'
});
}
try {
nonCriticalOperation();
} catch (error) {
captureErrorWithSeverity(error, 'warning', {
operation: 'load_recommendations'
});
}Filter out noise and expected errors:
// Filter known/expected errors
function shouldCaptureError(error) {
// Don't capture user cancellations
if (error.name === 'AbortError') {
return false;
}
// Don't capture network timeouts below threshold
if (error.code === 'ETIMEDOUT' && error.timeout < 5000) {
return false;
}
// Don't capture 3rd party script errors
if (error.filename?.includes('third-party-script')) {
return false;
}
return true;
}
// Wrap captureException
function captureFilteredError(error, properties = {}) {
if (shouldCaptureError(error)) {
posthog.captureException(error, properties);
}
}
// Usage
try {
await operation();
} catch (error) {
captureFilteredError(error, { operation: 'user_action' });
}Include session replay URLs with exceptions:
// Always include replay URL
try {
dangerousOperation();
} catch (error) {
posthog.captureException(error, {
session_replay_url: posthog.get_session_replay_url({
withTimestamp: true,
timestampLookBack: 30
}),
session_id: posthog.get_session_id()
});
}
// Automatic integration
function captureExceptionWithReplay(error, properties = {}) {
posthog.captureException(error, {
...properties,
session_replay_url: posthog.get_session_replay_url({
withTimestamp: true
}),
session_id: posthog.get_session_id(),
user_id: posthog.get_distinct_id()
});
}Integrate with React error boundaries:
class ErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
posthog.captureException(error, {
error_boundary: true,
component_stack: errorInfo.componentStack,
location: window.location.pathname,
session_replay_url: posthog.get_session_replay_url({
withTimestamp: true
})
});
}
render() {
if (this.state.hasError) {
return <ErrorFallback />;
}
return this.props.children;
}
}Set up comprehensive error tracking:
// Initialize error tracking
function initializeErrorTracking() {
// Start autocapture
posthog.startExceptionAutocapture({
capture_unhandled_errors: true,
capture_unhandled_rejections: true,
capture_console_errors: false // Handle manually for filtering
});
// Manual console.error capture with filtering
const originalConsoleError = console.error;
console.error = function(...args) {
// Filter out known warnings
const message = args[0]?.toString() || '';
if (!message.includes('React DevTools') &&
!message.includes('Download the React DevTools')) {
posthog.captureException(new Error(message), {
source: 'console.error',
args: args
});
}
originalConsoleError.apply(console, args);
};
}
// Call on app initialization
initializeErrorTracking();Handle async errors consistently:
// Async wrapper with error tracking
async function withErrorTracking(fn, context = {}) {
try {
return await fn();
} catch (error) {
posthog.captureException(error, {
...context,
function_name: fn.name
});
throw error;
}
}
// Usage
const userData = await withErrorTracking(
() => fetchUserData(userId),
{ operation: 'fetch_user', user_id: userId }
);
// Promise wrapper
function trackPromiseErrors(promise, context = {}) {
return promise.catch(error => {
posthog.captureException(error, context);
throw error;
});
}
// Usage
trackPromiseErrors(
fetch('/api/data'),
{ endpoint: '/api/data' }
);Track API errors with rich context:
class APIClient {
async request(url, options = {}) {
try {
const response = await fetch(url, options);
if (!response.ok) {
const error = new Error(`API Error: ${response.status}`);
error.status = response.status;
error.url = url;
error.method = options.method || 'GET';
posthog.captureException(error, {
error_type: 'api_error',
status_code: response.status,
endpoint: url,
method: options.method || 'GET',
response_headers: Object.fromEntries(response.headers)
});
throw error;
}
return response.json();
} catch (error) {
if (!error.status) {
// Network error
posthog.captureException(error, {
error_type: 'network_error',
endpoint: url,
method: options.method || 'GET'
});
}
throw error;
}
}
}Group similar errors together:
function getErrorFingerprint(error) {
// Create unique fingerprint for error grouping
const message = error.message || '';
const name = error.name || 'Error';
const stackLine = error.stack?.split('\n')[1] || '';
return `${name}:${message}:${stackLine}`;
}
function captureExceptionWithFingerprint(error, properties = {}) {
const fingerprint = getErrorFingerprint(error);
posthog.captureException(error, {
...properties,
error_fingerprint: fingerprint,
error_name: error.name,
error_message: error.message
});
}
// Usage
try {
operation();
} catch (error) {
captureExceptionWithFingerprint(error, {
context: 'user_action'
});
}Track performance-related errors:
// Track slow operations as errors
async function trackSlowOperation(fn, threshold = 5000, context = {}) {
const startTime = Date.now();
try {
const result = await fn();
const duration = Date.now() - startTime;
if (duration > threshold) {
const error = new Error('Operation exceeded time threshold');
error.duration = duration;
error.threshold = threshold;
posthog.captureException(error, {
error_type: 'performance',
duration_ms: duration,
threshold_ms: threshold,
...context
});
}
return result;
} catch (error) {
const duration = Date.now() - startTime;
posthog.captureException(error, {
...context,
duration_ms: duration
});
throw error;
}
}
// Usage
await trackSlowOperation(
() => loadLargeDataset(),
3000,
{ operation: 'load_dataset' }
);Prevent error spam:
class ErrorRateLimiter {
constructor(maxErrors = 10, windowMs = 60000) {
this.maxErrors = maxErrors;
this.windowMs = windowMs;
this.errors = [];
}
shouldCapture(error) {
const now = Date.now();
const fingerprint = this.getFingerprint(error);
// Remove old errors outside window
this.errors = this.errors.filter(e =>
now - e.timestamp < this.windowMs
);
// Count errors with same fingerprint
const similarErrors = this.errors.filter(e =>
e.fingerprint === fingerprint
);
if (similarErrors.length >= this.maxErrors) {
console.log(`Error rate limit exceeded for: ${fingerprint}`);
return false;
}
this.errors.push({
fingerprint: fingerprint,
timestamp: now
});
return true;
}
getFingerprint(error) {
return `${error.name}:${error.message}`;
}
}
const rateLimiter = new ErrorRateLimiter();
function captureRateLimitedError(error, properties = {}) {
if (rateLimiter.shouldCapture(error)) {
posthog.captureException(error, properties);
}
}Integrate with error tracking services:
// Sentry integration
import * as Sentry from '@sentry/browser';
function captureToAllServices(error, properties = {}) {
// Capture to PostHog
posthog.captureException(error, properties);
// Capture to Sentry
Sentry.captureException(error, {
extra: properties,
contexts: {
posthog: {
session_id: posthog.get_session_id(),
distinct_id: posthog.get_distinct_id()
}
}
});
}
// Bugsnag integration
import Bugsnag from '@bugsnag/js';
function captureWithBugsnag(error, properties = {}) {
posthog.captureException(error, properties);
Bugsnag.notify(error, (event) => {
event.addMetadata('posthog', {
session_id: posthog.get_session_id(),
distinct_id: posthog.get_distinct_id(),
...properties
});
});
}Handle errors differently by environment:
function captureEnvironmentAwareError(error, properties = {}) {
const isDevelopment = process.env.NODE_ENV === 'development';
if (isDevelopment) {
// In development, log to console with full details
console.error('Error captured:', error);
console.log('Context:', properties);
// Optionally still capture to PostHog
// posthog.captureException(error, properties);
} else {
// In production, always capture
posthog.captureException(error, {
...properties,
environment: 'production'
});
}
}Track how errors affect users:
function captureErrorWithImpact(error, impact, properties = {}) {
posthog.captureException(error, {
...properties,
user_impact: impact,
user_id: posthog.get_distinct_id(),
session_id: posthog.get_session_id()
});
}
// Usage examples
try {
await submitPayment();
} catch (error) {
captureErrorWithImpact(error, 'high', {
operation: 'payment',
amount: paymentAmount
});
showErrorToUser('Payment failed. Please try again.');
}
try {
await loadRecommendations();
} catch (error) {
captureErrorWithImpact(error, 'low', {
operation: 'recommendations'
});
// Silently fail, show default recommendations
}// Categorize errors for better analysis
enum ErrorCategory {
NETWORK = 'network',
VALIDATION = 'validation',
BUSINESS_LOGIC = 'business_logic',
THIRD_PARTY = 'third_party',
SECURITY = 'security',
PERFORMANCE = 'performance'
}
function captureErrorWithCategory(
error: Error,
category: ErrorCategory,
properties: Properties = {}
) {
posthog.captureException(error, {
...properties,
error_category: category,
error_severity: getSeverity(category),
environment: process.env.NODE_ENV
});
}
function getSeverity(category: ErrorCategory): string {
switch (category) {
case ErrorCategory.SECURITY:
return 'critical';
case ErrorCategory.BUSINESS_LOGIC:
case ErrorCategory.NETWORK:
return 'high';
case ErrorCategory.VALIDATION:
case ErrorCategory.THIRD_PARTY:
return 'medium';
case ErrorCategory.PERFORMANCE:
return 'low';
}
}// Automatically enrich all errors with context
class ErrorContextEnricher {
captureException(error: Error, additionalProperties: Properties = {}) {
const enrichedProperties = {
...additionalProperties,
// Session context
session_id: posthog.get_session_id(),
session_replay_url: posthog.get_session_replay_url({
withTimestamp: true,
timestampLookBack: 30
}),
// User context
user_id: posthog.get_distinct_id(),
user_groups: posthog.getGroups(),
// Page context
page_url: window.location.href,
page_path: window.location.pathname,
page_title: document.title,
referrer: document.referrer,
// Device context
user_agent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
screen_resolution: `${window.screen.width}x${window.screen.height}`,
viewport_size: `${window.innerWidth}x${window.innerHeight}`,
// Network context
online: navigator.onLine,
connection_type: (navigator as any).connection?.effectiveType,
// Error context
error_name: error.name,
error_message: error.message,
error_stack: error.stack,
timestamp: new Date().toISOString()
};
return posthog.captureException(error, enrichedProperties);
}
}
const enricher = new ErrorContextEnricher();
// Usage
try {
riskyOperation();
} catch (error) {
enricher.captureException(error, {
operation: 'risky_operation',
input_params: params
});
}// Track error recovery attempts
class ErrorRecoveryTracker {
async withRecovery<T>(
operation: () => Promise<T>,
operationName: string,
maxRetries: number = 3
): Promise<T> {
let lastError: Error | null = null;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const result = await operation();
// Track successful recovery
if (attempt > 0) {
posthog.capture('error_recovered', {
operation: operationName,
attempts: attempt + 1,
last_error: lastError?.message
});
}
return result;
} catch (error) {
lastError = error as Error;
// Track retry attempt
posthog.capture('error_retry_attempt', {
operation: operationName,
attempt: attempt + 1,
max_retries: maxRetries,
error: lastError.message
});
// Final attempt failed
if (attempt === maxRetries) {
posthog.captureException(lastError, {
operation: operationName,
attempts: attempt + 1,
recovery_failed: true
});
throw lastError;
}
// Wait before retry (exponential backoff)
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
}
}
throw lastError!;
}
}
// Usage
const recovery = new ErrorRecoveryTracker();
const data = await recovery.withRecovery(
() => fetchUserData(userId),
'fetch_user_data',
3
);// Enhance errors with source maps
interface EnhancedError extends Error {
sourceFile?: string;
sourceLine?: number;
sourceColumn?: number;
originalStack?: string;
}
function captureErrorWithSourceMap(error: EnhancedError, properties: Properties = {}) {
// Source maps are typically handled by browser dev tools
// But you can add source location if available
const enhancedProperties = {
...properties,
source_file: error.sourceFile,
source_line: error.sourceLine,
source_column: error.sourceColumn,
stack_trace: error.stack,
original_stack: error.originalStack
};
posthog.captureException(error, enhancedProperties);
}class ErrorDashboard {
private errors: Array<{
error: Error;
timestamp: number;
properties: Properties;
}> = [];
private maxErrors = 100;
initialize() {
// Listen for error events
posthog.on('eventCaptured', (data) => {
if (data.event === '$exception') {
this.addError({
error: new Error(data.properties.$exception_message),
timestamp: Date.now(),
properties: data.properties
});
}
});
}
addError(errorData: any) {
this.errors.unshift(errorData);
if (this.errors.length > this.maxErrors) {
this.errors.pop();
}
this.updateDashboard();
}
updateDashboard() {
const errorCounts = this.errors.reduce((acc, e) => {
const name = e.error.name;
acc[name] = (acc[name] || 0) + 1;
return acc;
}, {} as Record<string, number>);
console.log('Error dashboard:', {
total: this.errors.length,
by_type: errorCounts,
recent: this.errors.slice(0, 5).map(e => ({
message: e.error.message,
time: new Date(e.timestamp).toISOString()
}))
});
}
getErrors() {
return [...this.errors];
}
clearErrors() {
this.errors = [];
this.updateDashboard();
}
}// Debug exception capture
function debugExceptionCapture() {
console.log('Exception autocapture:', {
enabled: posthog.config.capture_exceptions,
config: posthog.config.capture_exceptions
});
// Test manual capture
try {
throw new Error('Test error');
} catch (error) {
const result = posthog.captureException(error, { test: true });
console.log('Capture result:', result);
}
// Check if events are being sent
posthog.on('eventCaptured', (data) => {
if (data.event === '$exception') {
console.log('Exception event captured:', data);
}
});
}// Implement smart filtering
class SmartErrorFilter {
private errorCounts = new Map<string, number>();
private readonly MAX_SAME_ERROR = 10;
private readonly WINDOW_MS = 60000;
shouldCapture(error: Error): boolean {
const fingerprint = this.getFingerprint(error);
const count = this.errorCounts.get(fingerprint) || 0;
if (count >= this.MAX_SAME_ERROR) {
console.log('Error threshold reached:', fingerprint);
return false;
}
this.errorCounts.set(fingerprint, count + 1);
// Reset count after window
setTimeout(() => {
this.errorCounts.delete(fingerprint);
}, this.WINDOW_MS);
return true;
}
private getFingerprint(error: Error): string {
return `${error.name}:${error.message}`;
}
}
const filter = new SmartErrorFilter();
// Use in global error handler
window.addEventListener('error', (event) => {
if (filter.shouldCapture(event.error)) {
posthog.captureException(event.error);
}
});// Monitor error tracking performance
class ErrorPerformanceMonitor {
private captureTimings: number[] = [];
trackCapturePerformance(error: Error, properties: Properties = {}) {
const start = performance.now();
const result = posthog.captureException(error, properties);
const duration = performance.now() - start;
this.captureTimings.push(duration);
if (this.captureTimings.length > 100) {
this.captureTimings.shift();
}
return result;
}
getAverageCaptureTime(): number {
if (this.captureTimings.length === 0) return 0;
const sum = this.captureTimings.reduce((a, b) => a + b, 0);
return sum / this.captureTimings.length;
}
logPerformanceStats() {
console.log('Error capture performance:', {
avg_ms: this.getAverageCaptureTime().toFixed(2),
count: this.captureTimings.length
});
}
}