# Event Capture

Event capture allows you to track user actions and behaviors. You can capture custom events with properties, register super properties that apply to all events, and manage event properties dynamically.

## Capabilities

### Capture Custom Events

Captures a custom event with optional properties and configuration options.

```typescript { .api }
/**
 * Captures a custom event
 * @param event - Event name
 * @param properties - Event properties (optional)
 * @param options - Capture options (optional)
 * @returns CaptureResult if successful, void otherwise
 */
function capture(
    event: EventName,
    properties?: Properties,
    options?: CaptureOptions
): CaptureResult | void;
```

**Usage Examples:**

```typescript
// Simple event
posthog.capture('button_clicked');

// Event with properties
posthog.capture('purchase_completed', {
    product_id: '12345',
    price: 99.99,
    currency: 'USD'
});

// Event with options
posthog.capture('important_action',
    { action_type: 'critical' },
    { send_instantly: true } // Don't batch this event
);

// Set user properties with event
posthog.capture('user_action', {}, {
    $set: { plan: 'premium' },
    $set_once: { first_purchase_date: '2024-01-15' }
});
```

### Register Super Properties

Registers properties that will be sent with every subsequent event.

```typescript { .api }
/**
 * Registers super properties that apply to all events
 * @param properties - Properties to register
 * @param days - Number of days to persist (optional)
 */
function register(properties: Properties, days?: number): void;
```

**Usage Example:**

```typescript
// Register properties for all events
posthog.register({
    app_version: '2.1.0',
    environment: 'production'
});

// Register with expiration
posthog.register({
    promotion_code: 'SUMMER2024'
}, 30); // Expires in 30 days
```

### Register Super Properties Once

Registers properties only if they don't already exist.

```typescript { .api }
/**
 * Registers super properties once if not already set
 * @param properties - Properties to register
 * @param default_value - Default value if property exists
 * @param days - Number of days to persist (optional)
 */
function register_once(
    properties: Properties,
    default_value?: Property,
    days?: number
): void;
```

**Usage Example:**

```typescript
// Register only on first visit
posthog.register_once({
    initial_referrer: document.referrer,
    initial_landing_page: window.location.pathname
});
```

### Register Session Properties

Registers properties for the current session only (cleared on new session).

```typescript { .api }
/**
 * Registers properties for the current session only
 * @param properties - Properties to register for this session
 */
function register_for_session(properties: Properties): void;
```

**Usage Example:**

```typescript
// Properties only for this session
posthog.register_for_session({
    session_campaign: 'email_newsletter',
    session_variant: 'A'
});
```

### Unregister Super Property

Removes a registered super property.

```typescript { .api }
/**
 * Removes a registered super property
 * @param property - Property name to remove
 */
function unregister(property: string): void;
```

**Usage Example:**

```typescript
posthog.unregister('promotion_code');
```

### Unregister Session Property

Removes a session-specific property.

```typescript { .api }
/**
 * Removes a session-specific property
 * @param property - Property name to remove
 */
function unregister_for_session(property: string): void;
```

**Usage Example:**

```typescript
posthog.unregister_for_session('session_campaign');
```

### Get Registered Property

Retrieves the value of a registered super property.

```typescript { .api }
/**
 * Gets the value of a registered super property
 * @param property_name - Property name
 * @returns Property value or undefined if not found
 */
function get_property(property_name: string): Property | undefined;
```

**Usage Example:**

```typescript
const appVersion = posthog.get_property('app_version');
console.log('Current version:', appVersion);
```

### Get Session Property

Retrieves the value of a session-specific property.

```typescript { .api }
/**
 * Gets the value of a session-specific property
 * @param property_name - Property name
 * @returns Property value or undefined if not found
 */
function getSessionProperty(property_name: string): Property | undefined;
```

**Usage Example:**

```typescript
const campaign = posthog.getSessionProperty('session_campaign');
```

### Calculate Event Properties

Calculates the combined properties for an event, including super properties.

```typescript { .api }
/**
 * Calculates combined event properties including super properties
 * @param properties - Base event properties
 * @param eventName - Event name for context
 * @param readOnly - If true, don't modify internal state
 * @returns Combined properties object
 */
function calculateEventProperties(
    properties?: Properties,
    eventName?: string,
    readOnly?: boolean
): Properties;
```

**Usage Example:**

```typescript
// Preview what properties will be sent
const finalProps = posthog.calculateEventProperties({
    custom_prop: 'value'
}, 'my_event', true);

console.log('Event will include:', finalProps);
```

### Listen to Captured Events

Registers a callback that fires whenever an event is captured.

```typescript { .api }
/**
 * Registers an event listener
 * @param event - Event type (currently only 'eventCaptured')
 * @param cb - Callback function
 * @returns Function to unsubscribe
 */
function on(event: 'eventCaptured', cb: (data: CaptureResult) => void): () => void;
```

**Usage Example:**

```typescript
// Listen to all captured events
const unsubscribe = posthog.on('eventCaptured', (data) => {
    console.log('Event captured:', data.event, data.properties);
});

// Later, stop listening
unsubscribe();
```

### Get Page View ID

Gets the unique ID for the current page view.

```typescript { .api }
/**
 * Gets the unique ID for the current page view
 * @returns Page view ID or undefined
 */
function getPageViewId(): string | undefined;
```

**Usage Example:**

```typescript
const pageViewId = posthog.getPageViewId();
console.log('Current page view:', pageViewId);
```

### Push to Queue

Adds an item to the execution queue for snippet compatibility.

```typescript { .api }
/**
 * Pushes an item to the execution queue
 * Used for snippet-style initialization and command queuing
 * @param item - Command array to execute
 */
function push(item: any[]): void;
```

**Usage Example:**

```typescript
// Snippet-style command queuing
posthog.push(['capture', 'event_name', { property: 'value' }]);
posthog.push(['identify', 'user-123']);

// Typically used with the PostHog snippet for async loading
window.posthog = window.posthog || [];
window.posthog.push(['init', 'api-key']);
```

### String Representation

Returns a string representation of the PostHog instance.

```typescript { .api }
/**
 * Returns a string representation of the PostHog instance
 * @returns String representation
 */
function toString(): string;
```

**Usage Example:**

```typescript
console.log(posthog.toString());
// Output: "posthog" or instance name
```

## Types

### EventName

```typescript { .api }
/**
 * Event name - any string value
 */
type EventName = string;
```

### Property

```typescript { .api }
/**
 * A single property value
 */
type Property = string | number | boolean | null | undefined;
```

### Properties

```typescript { .api }
/**
 * Object containing event or user properties
 */
type Properties = Record<string, Property | Property[]>;
```

### CaptureResult

```typescript { .api }
/**
 * Result of capturing an event
 */
interface CaptureResult {
    /**
     * Event name that was captured
     */
    event: string;

    /**
     * Properties that were sent with the event
     */
    properties: Record<string, any>;
}
```

### CaptureOptions

```typescript { .api }
/**
 * Options for capturing events
 */
interface CaptureOptions {
    /**
     * Send this event immediately without batching
     * @default false
     */
    send_instantly?: boolean;

    /**
     * Custom timestamp for the event
     * @default Current time
     */
    timestamp?: Date;

    /**
     * User properties to set with this event
     */
    $set?: Properties;

    /**
     * User properties to set once with this event
     */
    $set_once?: Properties;

    /**
     * Internal batch key for grouping events
     * @internal
     */
    _batchKey?: string;
}
```

## Reserved Event Names

PostHog reserves certain event names for built-in functionality. **Do not use these names for custom events:**

```typescript { .api }
// Reserved event names (automatically captured by PostHog)
type ReservedEventName =
    | '$pageview'           // Automatic page view tracking
    | '$pageleave'          // Page leave events
    | '$autocapture'        // Automatically captured interactions
    | '$identify'           // User identification
    | '$create_alias'       // User aliasing
    | '$set'                // Set user properties
    | '$set_once'           // Set user properties once
    | '$groupidentify'      // Group identification
    | '$feature_flag_called' // Feature flag evaluation
    | '$feature_flag_call'  // Alternative name for flag evaluation
    | '$survey_shown'       // Survey shown to user
    | '$survey_sent'        // Survey response submitted
    | '$survey_dismissed'   // Survey dismissed by user
    | '$exception'          // Exception captured
    | '$web_vitals'         // Web performance metrics
    | '$ai_feedback'        // LLM trace feedback
    | '$ai_metric'          // LLM trace metric
    | '$session_start'      // Session started
    | '$session_end'        // Session ended
    | '$opt_in'             // User opted in
    | '$opt_out';           // User opted out
```

**Note**: Avoid event names starting with `$` to prevent conflicts with PostHog's internal events.

## Automatic Event Capture

PostHog automatically captures certain events based on configuration:

### Page Views

Automatically captured when `capture_pageview` is enabled:

```typescript
// Captured automatically
{
    event: '$pageview',
    properties: {
        $current_url: 'https://example.com/page',
        $host: 'example.com',
        $pathname: '/page',
        // ... other properties
    }
}
```

### Page Leave

Automatically captured when `capture_pageleave` is enabled:

```typescript
// Captured automatically when user leaves page
{
    event: '$pageleave',
    properties: {
        $current_url: 'https://example.com/page',
        // ... other properties
    }
}
```

### Autocapture Events

When `autocapture` is enabled, PostHog automatically captures:
- Click events on buttons, links, and other interactive elements
- Form submissions
- Input changes

```typescript
// Captured automatically on click
{
    event: '$autocapture',
    properties: {
        $event_type: 'click',
        $el_text: 'Sign Up',
        $el_tag: 'button',
        // ... other element properties
    }
}
```

## Advanced Event Patterns

### Event Batching and Deduplication

```typescript { .api }
// Deduplicate rapid events
class EventDeduplicator {
    private recentEvents = new Map<string, number>();
    private dedupWindow = 1000; // 1 second
    
    capture(event: string, properties: Properties = {}) {
        const key = this.getEventKey(event, properties);
        const now = Date.now();
        const lastTime = this.recentEvents.get(key);
        
        if (lastTime && (now - lastTime) < this.dedupWindow) {
            console.log('Skipping duplicate event:', event);
            return;
        }
        
        this.recentEvents.set(key, now);
        posthog.capture(event, properties);
        
        // Cleanup old entries
        this.cleanup();
    }
    
    private getEventKey(event: string, properties: Properties): string {
        return `${event}:${JSON.stringify(properties)}`;
    }
    
    private cleanup() {
        const now = Date.now();
        for (const [key, time] of this.recentEvents.entries()) {
            if (now - time > this.dedupWindow * 2) {
                this.recentEvents.delete(key);
            }
        }
    }
}

const deduplicator = new EventDeduplicator();

// Usage
button.addEventListener('click', () => {
    deduplicator.capture('button_clicked', { button_id: 'signup' });
});
```

### Event Validation

```typescript { .api }
// Validate events before sending
interface EventSchema {
    required: string[];
    optional?: string[];
    types?: Record<string, string>;
}

const eventSchemas: Record<string, EventSchema> = {
    'purchase_completed': {
        required: ['order_id', 'total', 'currency'],
        optional: ['discount', 'items'],
        types: {
            order_id: 'string',
            total: 'number',
            currency: 'string'
        }
    }
};

function captureValidatedEvent(
    event: string,
    properties: Properties
): CaptureResult | void {
    const schema = eventSchemas[event];
    
    if (schema) {
        // Check required properties
        const missing = schema.required.filter(key => !(key in properties));
        if (missing.length > 0) {
            console.error(`Missing required properties for ${event}:`, missing);
            return;
        }
        
        // Validate types
        if (schema.types) {
            for (const [key, expectedType] of Object.entries(schema.types)) {
                if (key in properties && typeof properties[key] !== expectedType) {
                    console.error(`Wrong type for ${key}: expected ${expectedType}`);
                    return;
                }
            }
        }
    }
    
    return posthog.capture(event, properties);
}

// Usage
captureValidatedEvent('purchase_completed', {
    order_id: '12345',
    total: 99.99,
    currency: 'USD'
});
```

### Event Enrichment Pipeline

```typescript { .api }
// Automatically enrich all events
const enrichmentRules = [
    // Add user context
    (properties: Properties) => ({
        ...properties,
        user_plan: posthog.get_property('plan'),
        user_cohort: posthog.get_property('cohort')
    }),
    
    // Add session context
    (properties: Properties) => ({
        ...properties,
        session_id: posthog.get_session_id(),
        page_url: window.location.href
    }),
    
    // Add device context
    (properties: Properties) => ({
        ...properties,
        screen_resolution: `${window.screen.width}x${window.screen.height}`,
        viewport_size: `${window.innerWidth}x${window.innerHeight}`,
        device_memory: (navigator as any).deviceMemory,
        connection_type: (navigator as any).connection?.effectiveType
    })
];

function captureEnriched(event: string, properties: Properties = {}) {
    let enriched = properties;
    
    for (const rule of enrichmentRules) {
        enriched = rule(enriched);
    }
    
    return posthog.capture(event, enriched);
}
```

### Async Event Tracking

```typescript { .api }
// Track async operations with timing
async function trackAsyncOperation<T>(
    operationName: string,
    operation: () => Promise<T>,
    context: Properties = {}
): Promise<T> {
    const startTime = Date.now();
    const eventId = `${operationName}-${Date.now()}`;
    
    // Track start
    posthog.capture(`${operationName}_started`, {
        ...context,
        event_id: eventId
    });
    
    try {
        const result = await operation();
        const duration = Date.now() - startTime;
        
        // Track success
        posthog.capture(`${operationName}_completed`, {
            ...context,
            event_id: eventId,
            duration_ms: duration,
            success: true
        });
        
        return result;
    } catch (error) {
        const duration = Date.now() - startTime;
        
        // Track failure
        posthog.capture(`${operationName}_failed`, {
            ...context,
            event_id: eventId,
            duration_ms: duration,
            error: error instanceof Error ? error.message : String(error)
        });
        
        throw error;
    }
}

// Usage
const userData = await trackAsyncOperation(
    'fetch_user_data',
    () => fetchUserData(userId),
    { user_id: userId }
);
```

## Event Troubleshooting

### Events Not Appearing in PostHog

```typescript { .api }
// Debug event capture
posthog.on('eventCaptured', (data) => {
    console.log('Event captured locally:', data.event);
    console.log('Properties:', data.properties);
});

// Check if capturing is enabled
console.log('Is capturing:', posthog.is_capturing());

// Check opt-out status
console.log('Has opted out:', posthog.has_opted_out_capturing());

// Enable debug mode
posthog.debug(true);

// Try capturing test event
const result = posthog.capture('test_event', { test: true });
console.log('Capture result:', result);
```

### Event Properties Missing

```typescript { .api }
// Check super properties
console.log('Registered properties:', {
    app_version: posthog.get_property('app_version'),
    environment: posthog.get_property('environment')
});

// Check session properties
console.log('Session property:', posthog.getSessionProperty('campaign'));

// Debug property calculation
const properties = posthog.calculateEventProperties(
    { custom: 'value' },
    'test_event',
    true  // readOnly mode
);
console.log('Final properties:', properties);
```

### Events Being Filtered

```typescript { .api }
// Check before_send hook
posthog.init('token', {
    before_send: (event) => {
        console.log('Before send:', event);
        
        // Filter sensitive events
        if (event.properties.sensitive) {
            console.log('Blocked sensitive event');
            return null;
        }
        
        return event;
    }
});
```

## Performance Optimization

### Batch Critical Events

```typescript { .api }
// Send critical events immediately
posthog.capture('payment_completed', {
    order_id: '12345',
    amount: 99.99
}, {
    send_instantly: true  // Don't wait for batch
});

// Regular events use batching
posthog.capture('page_viewed', {
    path: window.location.pathname
});
```

### Throttle High-Frequency Events

```typescript { .api }
// Throttle scroll events
let scrollTimeout: NodeJS.Timeout;

window.addEventListener('scroll', () => {
    clearTimeout(scrollTimeout);
    
    scrollTimeout = setTimeout(() => {
        posthog.capture('scroll_depth', {
            depth: window.scrollY,
            max_depth: document.body.scrollHeight - window.innerHeight
        });
    }, 500);  // Only capture after 500ms of no scrolling
});
```

### Event Sampling

```typescript { .api }
// Sample high-frequency events
function captureSampled(
    event: string,
    properties: Properties,
    sampleRate: number = 0.1
): CaptureResult | void {
    if (Math.random() < sampleRate) {
        return posthog.capture(event, {
            ...properties,
            sampled: true,
            sample_rate: sampleRate
        });
    }
}

// Capture 10% of scroll events
window.addEventListener('scroll', () => {
    captureSampled('scrolled', {
        scroll_position: window.scrollY
    }, 0.1);
});
```

## Testing Event Capture

### Mock Event Tracking in Tests

```typescript { .api }
// Mock for unit tests
const mockCapture = jest.fn();
(window as any).posthog = {
    capture: mockCapture,
    identify: jest.fn(),
    // ... other methods
};

// Test event capture
it('captures event on button click', () => {
    render(<Button />);
    fireEvent.click(screen.getByRole('button'));
    
    expect(mockCapture).toHaveBeenCalledWith(
        'button_clicked',
        expect.objectContaining({
            button_id: 'test-button'
        })
    );
});
```

### Verify Event Properties

```typescript { .api }
// Test helper to verify event shape
function expectEventCaptured(
    eventName: string,
    expectedProperties: Partial<Properties>
) {
    const calls = (posthog.capture as jest.Mock).mock.calls;
    const matchingCall = calls.find(call => call[0] === eventName);
    
    expect(matchingCall).toBeDefined();
    expect(matchingCall[1]).toMatchObject(expectedProperties);
}

// Usage
fireEvent.click(button);
expectEventCaptured('button_clicked', {
    button_id: 'signup',
    location: 'header'
});
```

## See Also

- [Identification](./identification.md) - User identification and properties
- [Feature Flags](./feature-flags.md) - Feature flag integration with events
- [Session Recording](./session-recording.md) - Link events to session replays
- [Privacy](./privacy.md) - Event filtering and consent
- [Types](./types.md) - Type definitions for events
