or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

client-configuration.mderror-tracking.mdevent-tracking.mdexperimental.mdexpress-integration.mdfeature-flags.mdidentification.mdindex.mdsentry-integration.md
tile.json

event-tracking.mddocs/

Event Tracking

Capture user events, pageviews, and custom analytics with properties, groups, and feature flag context.

Capabilities

Capture Event

Capture an event and queue it for batch sending.

/**
 * Capture an event manually
 * @param props - The event properties
 */
capture(props: EventMessage): void;

Usage Examples:

import { PostHog } from 'posthog-node';

const client = new PostHog('phc_your_api_key');

// Basic event capture
client.capture({
  distinctId: 'user_123',
  event: 'button_clicked',
  properties: {
    button_name: 'signup',
    button_color: 'red'
  }
});

// Event with timestamp
client.capture({
  distinctId: 'user_123',
  event: 'purchase_completed',
  properties: {
    amount: 99.99,
    currency: 'USD',
    product_id: 'prod_456'
  },
  timestamp: new Date()
});

// Event with groups
client.capture({
  distinctId: 'user_123',
  event: 'feature_used',
  properties: {
    feature_name: 'export_data'
  },
  groups: {
    company: 'acme_corp',
    organization: 'org_789'
  }
});

// Event with feature flags
client.capture({
  distinctId: 'user_123',
  event: 'user_action',
  sendFeatureFlags: true
});

// Event with custom UUID
client.capture({
  distinctId: 'user_123',
  event: 'custom_event',
  uuid: 'custom-uuid-123'
});

// Disable GeoIP lookup
client.capture({
  distinctId: 'user_123',
  event: 'privacy_event',
  disableGeoip: true
});

Capture Event Immediately

Capture an event and send it immediately without queuing. Use for serverless environments or when you need guaranteed delivery before function termination.

/**
 * Capture an event immediately (synchronously)
 * @param props - The event properties
 * @returns Promise that resolves when the event is captured
 */
captureImmediate(props: EventMessage): Promise<void>;

Usage Examples:

// Basic immediate capture
await client.captureImmediate({
  distinctId: 'user_123',
  event: 'button_clicked',
  properties: { button_name: 'signup' }
});

// In serverless function
export async function handler(event) {
  await client.captureImmediate({
    distinctId: event.userId,
    event: 'function_invoked',
    properties: {
      function_name: 'my-function',
      execution_time: Date.now()
    }
  });

  // Ensure event is sent before function exits
  return { statusCode: 200 };
}

// With feature flags
await client.captureImmediate({
  distinctId: 'user_123',
  event: 'user_action',
  sendFeatureFlags: true
});

// With selective feature flag evaluation
await client.captureImmediate({
  distinctId: 'user_123',
  event: 'user_action',
  sendFeatureFlags: {
    onlyEvaluateLocally: true,
    personProperties: { plan: 'premium' },
    groupProperties: { organization: { tier: 'enterprise' } },
    flagKeys: ['flag1', 'flag2']
  }
});

Types

EventMessage

interface EventMessage {
  /** Unique identifier for the user */
  distinctId: string;

  /** Name of the event (e.g., 'button_clicked', 'purchase_completed') */
  event: string;

  /** Custom properties for the event */
  properties?: Record<string | number, any>;

  /** Group associations (e.g., { company: 'acme_corp' }) */
  groups?: Record<string, string | number>;

  /** Whether to evaluate and send feature flags with the event */
  sendFeatureFlags?: boolean | SendFeatureFlagsOptions;

  /** Custom timestamp for the event (defaults to now) */
  timestamp?: Date;

  /** Custom UUID for the event (auto-generated if not provided) */
  uuid?: string;

  /** Disable GeoIP lookup for this event */
  disableGeoip?: boolean;
}

SendFeatureFlagsOptions

interface SendFeatureFlagsOptions {
  /** Only use local evaluation, don't fall back to remote */
  onlyEvaluateLocally?: boolean;

  /** Person properties for flag evaluation */
  personProperties?: Record<string, any>;

  /** Group properties for flag evaluation */
  groupProperties?: Record<string, Record<string, any>>;

  /** Specific flag keys to evaluate */
  flagKeys?: string[];
}

Usage Patterns

Pageview Tracking

// Track pageview
client.capture({
  distinctId: 'user_123',
  event: '$pageview',
  properties: {
    $current_url: 'https://example.com/pricing',
    $referrer: 'https://google.com',
    $pathname: '/pricing'
  }
});

E-commerce Events

// Product viewed
client.capture({
  distinctId: 'user_123',
  event: 'product_viewed',
  properties: {
    product_id: 'prod_456',
    product_name: 'Premium Widget',
    price: 99.99,
    currency: 'USD',
    category: 'widgets'
  }
});

// Added to cart
client.capture({
  distinctId: 'user_123',
  event: 'product_added_to_cart',
  properties: {
    product_id: 'prod_456',
    quantity: 2,
    price: 99.99,
    cart_total: 199.98
  }
});

// Purchase completed
client.capture({
  distinctId: 'user_123',
  event: 'purchase_completed',
  properties: {
    order_id: 'order_789',
    total: 199.98,
    currency: 'USD',
    items: [
      { product_id: 'prod_456', quantity: 2, price: 99.99 }
    ]
  }
});

Feature Usage Tracking

// Feature used
client.capture({
  distinctId: 'user_123',
  event: 'feature_used',
  properties: {
    feature_name: 'export_data',
    export_format: 'csv',
    row_count: 1000
  }
});

// API call made
client.capture({
  distinctId: 'user_123',
  event: 'api_call',
  properties: {
    endpoint: '/api/users',
    method: 'GET',
    status_code: 200,
    response_time_ms: 150
  }
});

Group Analytics

// Track company-level event
client.capture({
  distinctId: 'user_123',
  event: 'company_feature_used',
  properties: {
    feature: 'team_collaboration'
  },
  groups: {
    company: 'acme_corp'
  }
});

// Multi-level groups
client.capture({
  distinctId: 'user_123',
  event: 'resource_accessed',
  properties: {
    resource_type: 'document',
    resource_id: 'doc_123'
  },
  groups: {
    company: 'acme_corp',
    team: 'engineering',
    project: 'project_alpha'
  }
});

Experiments and A/B Testing

// Capture event with feature flags for experiments
client.capture({
  distinctId: 'user_123',
  event: 'experiment_viewed',
  properties: {
    experiment_name: 'checkout_flow_v2'
  },
  sendFeatureFlags: true
});

// Evaluate specific flags only
client.capture({
  distinctId: 'user_123',
  event: 'conversion',
  properties: {
    conversion_type: 'signup'
  },
  sendFeatureFlags: {
    flagKeys: ['checkout_flow_v2', 'pricing_test']
  }
});

Error and Performance Events

// API error
client.capture({
  distinctId: 'user_123',
  event: 'api_error',
  properties: {
    endpoint: '/api/users',
    method: 'POST',
    status_code: 500,
    error_message: 'Internal server error'
  }
});

// Performance metric
client.capture({
  distinctId: 'user_123',
  event: 'page_load_time',
  properties: {
    url: '/dashboard',
    load_time_ms: 2500,
    is_slow: true
  }
});

Express.js Integration

import express from 'express';
import { PostHog } from 'posthog-node';

const app = express();
const client = new PostHog('phc_your_api_key');

// Track request
app.use((req, res, next) => {
  client.capture({
    distinctId: req.user?.id || 'anonymous',
    event: 'api_request',
    properties: {
      path: req.path,
      method: req.method,
      user_agent: req.headers['user-agent']
    }
  });
  next();
});

// Shutdown on process exit
process.on('SIGTERM', async () => {
  await client.shutdown();
  process.exit(0);
});

Serverless Function Example

import { PostHog } from 'posthog-node';

const client = new PostHog('phc_your_api_key');

export async function handler(event, context) {
  try {
    // Track function invocation
    await client.captureImmediate({
      distinctId: event.userId || context.awsRequestId,
      event: 'lambda_invoked',
      properties: {
        function_name: context.functionName,
        request_id: context.awsRequestId
      }
    });

    // Your business logic
    const result = await processEvent(event);

    // Track success
    await client.captureImmediate({
      distinctId: event.userId || context.awsRequestId,
      event: 'lambda_success',
      properties: {
        function_name: context.functionName,
        duration_ms: Date.now() - event.startTime
      }
    });

    return { statusCode: 200, body: JSON.stringify(result) };
  } catch (error) {
    // Track error
    await client.captureExceptionImmediate(error, event.userId);

    return { statusCode: 500, body: 'Internal error' };
  }
}

Best Practices

Event Naming

Use descriptive, consistent event names in the format [object]_[action]:

// Good
client.capture({ distinctId: 'user_123', event: 'button_clicked' });
client.capture({ distinctId: 'user_123', event: 'form_submitted' });
client.capture({ distinctId: 'user_123', event: 'video_played' });

// Avoid
client.capture({ distinctId: 'user_123', event: 'click' });
client.capture({ distinctId: 'user_123', event: 'submit' });
client.capture({ distinctId: 'user_123', event: 'play' });

Property Naming

Use snake_case for property names and avoid special characters:

// Good
client.capture({
  distinctId: 'user_123',
  event: 'purchase_completed',
  properties: {
    order_id: 'order_789',
    total_amount: 99.99,
    item_count: 3
  }
});

// Avoid
client.capture({
  distinctId: 'user_123',
  event: 'purchase_completed',
  properties: {
    'Order ID': 'order_789',
    'Total-Amount': 99.99,
    'item.count': 3
  }
});

Queued vs Immediate

  • Use capture() for: Web servers, long-running processes
  • Use captureImmediate() for: Serverless functions, edge workers, critical events
// Long-running server: use queued capture
app.post('/api/event', (req, res) => {
  client.capture({
    distinctId: req.user.id,
    event: 'api_called'
  });
  res.json({ success: true });
});

// Serverless: use immediate capture
export async function handler(event) {
  await client.captureImmediate({
    distinctId: event.userId,
    event: 'function_called'
  });
  return { statusCode: 200 };
}

Don't Mix Capture Methods

Don't mix capture() and captureImmediate() in the same application, as they use different queueing strategies.

// Bad: mixing methods
client.capture({ distinctId: 'user_123', event: 'event1' });
await client.captureImmediate({ distinctId: 'user_123', event: 'event2' });

// Good: choose one approach
client.capture({ distinctId: 'user_123', event: 'event1' });
client.capture({ distinctId: 'user_123', event: 'event2' });
await client.shutdown(); // Flush queue