or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/posthog-js@1.335.x

docs

index.md
tile.json

tessl/npm-posthog-js

tessl install tessl/npm-posthog-js@1.335.0

PostHog 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.

advanced.mddocs/reference/

Advanced Features

Advanced features include group analytics, LLM analytics, product tours, conversations, toolbar, and event listeners for extended functionality.

Group Analytics

Group analytics allows you to track organizations, companies, teams, or any entity that groups users together.

Associate User with Group

Associates the user with a group and optionally sets group properties.

/**
 * Associates the user with a group
 * @param groupType - Type of group (e.g., 'company', 'team', 'organization')
 * @param groupKey - Unique identifier for the group
 * @param groupPropertiesToSet - Properties to set on the group
 */
function group(
    groupType: string,
    groupKey: string,
    groupPropertiesToSet?: Properties
): void;

Usage Examples:

// Associate user with company
posthog.group('company', 'acme-inc');

// Associate with properties
posthog.group('company', 'acme-inc', {
    name: 'Acme Inc',
    plan: 'enterprise',
    employees: 500,
    industry: 'technology'
});

// Multiple group types
posthog.group('company', 'acme-inc');
posthog.group('team', 'engineering');
posthog.group('project', 'project-alpha');

// Update group properties
posthog.group('company', 'acme-inc', {
    mrr: 50000,
    last_active: Date.now()
});

Get Current Groups

Gets all current group memberships for the user.

/**
 * Gets all current group memberships
 * @returns Object mapping group types to group keys
 */
function getGroups(): Record<string, any>;

Usage Examples:

// Get all groups
const groups = posthog.getGroups();
console.log('User groups:', groups);
// Output: { company: 'acme-inc', team: 'engineering' }

// Check specific group
const groups = posthog.getGroups();
if (groups.company === 'acme-inc') {
    showEnterpriseFeatures();
}

// Use in conditional logic
function getGroupPlan() {
    const groups = posthog.getGroups();
    return groups.company ? 'group' : 'individual';
}

Reset Groups

Clears all group associations.

/**
 * Clears all group associations
 */
function resetGroups(): void;

Usage Examples:

// Clear all groups
posthog.resetGroups();

// Clear on user action
function leaveOrganization() {
    posthog.resetGroups();
    posthog.reset(); // Also reset user
}

LLM Analytics

Track Large Language Model (LLM) interactions, traces, feedback, and metrics.

Capture Trace Feedback

Captures user feedback for an LLM trace.

/**
 * Captures user feedback for an LLM trace
 * @param traceId - Unique identifier for the LLM trace
 * @param userFeedback - Feedback text from the user
 */
function captureTraceFeedback(
    traceId: string | number,
    userFeedback: string
): void;

Usage Examples:

// Capture positive feedback
posthog.captureTraceFeedback('trace-123', 'This response was helpful!');

// Capture negative feedback
posthog.captureTraceFeedback('trace-456', 'Response was not accurate');

// Capture with structured feedback
const feedback = JSON.stringify({
    rating: 5,
    comment: 'Excellent response',
    helpful: true
});
posthog.captureTraceFeedback('trace-789', feedback);

// Usage in UI
function FeedbackButtons({ traceId }) {
    return (
        <>
            <button onClick={() => {
                posthog.captureTraceFeedback(traceId, 'positive');
            }}>👍</button>
            <button onClick={() => {
                posthog.captureTraceFeedback(traceId, 'negative');
            }}>👎</button>
        </>
    );
}

Capture Trace Metric

Captures a metric for an LLM trace.

/**
 * Captures a metric for an LLM trace
 * @param traceId - Unique identifier for the LLM trace
 * @param metricName - Name of the metric
 * @param metricValue - Value of the metric
 */
function captureTraceMetric(
    traceId: string | number,
    metricName: string,
    metricValue: string | number | boolean
): void;

Usage Examples:

// Capture response time
posthog.captureTraceMetric('trace-123', 'response_time_ms', 1250);

// Capture token usage
posthog.captureTraceMetric('trace-123', 'tokens_used', 450);

// Capture quality metrics
posthog.captureTraceMetric('trace-123', 'relevance_score', 0.95);
posthog.captureTraceMetric('trace-123', 'coherence_score', 0.88);

// Boolean metrics
posthog.captureTraceMetric('trace-123', 'used_context', true);
posthog.captureTraceMetric('trace-123', 'hallucination_detected', false);

// Track LLM interaction
async function generateAIResponse(prompt) {
    const traceId = `trace-${Date.now()}`;
    const startTime = Date.now();

    const response = await llm.generate(prompt);

    const duration = Date.now() - startTime;

    // Capture metrics
    posthog.captureTraceMetric(traceId, 'response_time_ms', duration);
    posthog.captureTraceMetric(traceId, 'tokens_used', response.tokens);
    posthog.captureTraceMetric(traceId, 'model', response.model);

    return { traceId, response };
}

Product Tours

Product tours are guided walkthroughs that help users discover features. Tours can be displayed automatically based on conditions or programmatically controlled through the API.

Note: Product tours require the tours feature to be enabled in your PostHog configuration.

// Enable product tours
posthog.init('token', {
    disable_product_tours: false // default is true
});

Get All Product Tours

Fetches all product tours from the server.

/**
 * Gets all product tours
 * @param callback - Callback receiving tours and load status
 * @param forceReload - Force reload from server
 */
function getProductTours(
    callback: ProductTourCallback,
    forceReload?: boolean
): void;

Usage Example:

posthog.productTours.getProductTours((tours, context) => {
    if (context.isLoaded) {
        console.log('All tours:', tours);
        tours.forEach(tour => {
            console.log(`Tour: ${tour.name}, Status: ${tour.display_frequency}`);
        });
    } else {
        console.error('Failed to load tours:', context.error);
    }
}, false); // Don't force reload

Get Active Product Tours

Fetches product tours that should be shown to the current user based on conditions.

/**
 * Gets active product tours that match current conditions
 * @param callback - Callback receiving tours and load status
 */
function getActiveProductTours(callback: ProductTourCallback): void;

Usage Example:

posthog.productTours.getActiveProductTours((tours, context) => {
    if (context.isLoaded) {
        console.log('Active tours:', tours);
        tours.forEach(tour => {
            console.log(`Tour: ${tour.name}`);
        });
    } else {
        console.error('Failed to load tours:', context.error);
    }
});

Show Product Tour

Displays a specific product tour by ID.

/**
 * Shows a product tour by its ID
 * @param tourId - Unique identifier for the tour
 */
function showProductTour(tourId: string): void;

Usage Example:

// Show a specific tour
posthog.productTours.showProductTour('onboarding-tour-123');

Preview Tour

Force loads and displays a tour, ignoring all display conditions. Useful for testing.

/**
 * Previews a tour, ignoring display conditions
 * @param tour - Tour configuration object
 */
function previewTour(tour: ProductTour): void;

Usage Example:

// Preview a tour configuration
const testTour = {
    id: 'test-tour',
    name: 'Test Tour',
    type: 'product_tour',
    steps: [...]
};

posthog.productTours.previewTour(testTour);

Dismiss Product Tour

Dismisses the currently displayed tour.

/**
 * Dismisses the current tour
 */
function dismissProductTour(): void;

Usage Example:

// User clicks close button
posthog.productTours.dismissProductTour();

Navigate Tour Steps

Controls navigation through tour steps.

/**
 * Moves to the next step in the current tour
 */
function nextStep(): void;

/**
 * Moves to the previous step in the current tour
 */
function previousStep(): void;

Usage Example:

// Custom navigation buttons
document.getElementById('tour-next')?.addEventListener('click', () => {
    posthog.productTours.nextStep();
});

document.getElementById('tour-prev')?.addEventListener('click', () => {
    posthog.productTours.previousStep();
});

Reset Tours

Resets tour completion state for users.

/**
 * Resets a specific tour so it can be shown again
 * @param tourId - Tour ID to reset
 */
function resetTour(tourId: string): void;

/**
 * Resets all tours for the current user
 */
function resetAllTours(): void;

Usage Example:

// Reset a specific tour
posthog.productTours.resetTour('onboarding-tour-123');

// Reset all tours (e.g., for testing)
posthog.productTours.resetAllTours();

Cancel Pending Tour

Cancels a tour that's waiting to be displayed.

/**
 * Cancels a pending tour
 * @param tourId - Tour ID to cancel
 */
function cancelPendingTour(tourId: string): void;

Usage Example:

// Cancel a tour before it's shown
posthog.productTours.cancelPendingTour('feature-announcement-tour');

Product Tour Types

/**
 * Product tour configuration
 */
interface ProductTour {
    /**
     * Unique tour identifier
     */
    id: string;

    /**
     * Tour name
     */
    name: string;

    /**
     * Tour description
     */
    description?: string;

    /**
     * Tour type
     */
    type: 'product_tour';

    /**
     * Auto-launch the tour
     */
    auto_launch?: boolean;

    /**
     * Tour steps
     */
    steps: ProductTourStep[];

    /**
     * Display conditions
     */
    conditions?: ProductTourConditions;

    /**
     * Visual appearance
     */
    appearance?: ProductTourAppearance;

    /**
     * Display frequency
     */
    display_frequency?: ProductTourDisplayFrequency;
}

/**
 * Product tour step
 */
interface ProductTourStep {
    /**
     * Step ID
     */
    id: string;

    /**
     * Step type
     */
    type: ProductTourStepType;

    /**
     * CSS selector for target element
     */
    selector?: string;

    /**
     * How user progresses to next step
     */
    progressionTrigger: 'button' | 'click';

    /**
     * Step content (markdown)
     */
    content: JSONContent | null;

    /**
     * Step content (HTML)
     */
    contentHtml?: string;

    /**
     * Survey question in step
     */
    survey?: ProductTourSurveyQuestion;

    /**
     * Step buttons
     */
    buttons?: ProductTourStepButtons;

    /**
     * Banner configuration
     */
    bannerConfig?: ProductTourBannerConfig;
}

/**
 * Product tour step type
 */
type ProductTourStepType = 'element' | 'modal' | 'survey' | 'banner';

Conversations

In-app conversations provide support and messaging capabilities.

Note: Conversations are managed through PostHog's conversations feature and require backend configuration.

// Enable/disable conversations
posthog.init('token', {
    disable_conversations: false
});

Conversation Types

/**
 * Conversations remote configuration
 */
interface ConversationsRemoteConfig {
    /**
     * Whether conversations are enabled
     */
    enabled: boolean;

    /**
     * Whether the widget is enabled
     */
    widgetEnabled?: boolean;

    /**
     * API token for conversations
     */
    token: string;

    /**
     * Greeting text
     */
    greetingText?: string;

    /**
     * Widget color
     */
    color?: string;

    /**
     * Placeholder text
     */
    placeholderText?: string;

    /**
     * Require email from users
     */
    requireEmail?: boolean;

    /**
     * Collect user name
     */
    collectName?: boolean;
}

/**
 * Message in conversation
 */
interface Message {
    /**
     * Message ID
     */
    id: string;

    /**
     * Message content
     */
    content: string;

    /**
     * Author type
     */
    author_type: MessageAuthorType;

    /**
     * Author name
     */
    author_name?: string;

    /**
     * Creation timestamp
     */
    created_at: string;

    /**
     * Whether message is private
     */
    is_private: boolean;
}

/**
 * Message author type
 */
type MessageAuthorType = 'customer' | 'AI' | 'human';

/**
 * Support ticket
 */
interface Ticket {
    /**
     * Ticket ID
     */
    id: string;

    /**
     * Ticket status
     */
    status: TicketStatus;

    /**
     * Last message preview
     */
    last_message?: string;

    /**
     * Last message timestamp
     */
    last_message_at?: string;

    /**
     * Number of messages
     */
    message_count: number;

    /**
     * Creation timestamp
     */
    created_at: string;

    /**
     * All messages
     */
    messages?: Message[];

    /**
     * Unread message count
     */
    unread_count?: number;
}

/**
 * Ticket status
 */
type TicketStatus = 'new' | 'open' | 'pending' | 'on_hold' | 'resolved';

Note: Conversations API methods are accessed through the posthog.conversations namespace. The methods below are available once conversations are enabled and loaded.

Show Widget

Shows the conversations widget (button and chat panel).

/**
 * Shows the conversations widget
 */
function show(): void;

Usage Example:

// Show conversations widget
posthog.conversations.show();

Hide Widget

Hides the conversations widget completely.

/**
 * Hides the conversations widget
 */
function hide(): void;

Usage Example:

// Hide conversations widget
posthog.conversations.hide();

Check Widget Availability

Checks if conversations are available and loaded.

/**
 * Checks if conversations are available
 * @returns True if conversations are loaded and available
 */
function isAvailable(): boolean;

Usage Example:

if (posthog.conversations.isAvailable()) {
    console.log('Conversations ready');
    posthog.conversations.show();
} else {
    console.log('Conversations not available');
}

Check Widget Visibility

Checks if the widget is currently visible.

/**
 * Checks if the widget is currently visible
 * @returns True if widget is visible
 */
function isVisible(): boolean;

Usage Example:

if (posthog.conversations.isVisible()) {
    console.log('Widget is shown');
}

Get Current Ticket ID

Gets the ID of the current active ticket.

/**
 * Gets the current active ticket ID
 * @returns Ticket ID or null if no active ticket
 */
function getCurrentTicketId(): string | null;

Usage Example:

const ticketId = posthog.conversations.getCurrentTicketId();
if (ticketId) {
    console.log('Active ticket:', ticketId);
}

Get Widget Session ID

Gets the widget session ID for tracking.

/**
 * Gets the widget session ID
 * @returns Widget session ID or null
 */
function getWidgetSessionId(): string | null;

Usage Example:

const sessionId = posthog.conversations.getWidgetSessionId();
console.log('Widget session:', sessionId);

Reset Conversations

Resets conversations state.

/**
 * Resets conversations state
 */
function reset(): void;

Usage Example:

// Reset conversations on logout
function logout() {
    posthog.conversations.reset();
    posthog.reset();
}

Send Message

Sends a message in a conversation.

/**
 * Sends a message in a conversation
 * @param message - Message content
 * @param traits - Optional user identification data
 * @returns Promise with send result
 */
function sendMessage(
    message: string,
    traits?: UserProvidedTraits
): Promise<SendMessageResponse>;

Usage Example:

// Send a message
await posthog.conversations.sendMessage('Hello, I need help!', {
    email: 'user@example.com',
    name: 'John Doe'
});

Get Messages

Retrieves messages for a conversation or ticket.

/**
 * Gets messages for a ticket
 * @param ticketId - Optional ticket ID (uses current conversation if not provided)
 * @param options - Optional query options
 * @returns Promise with messages
 */
function getMessages(
    ticketId?: string,
    options?: GetMessagesOptions
): Promise<GetMessagesResponse>;

Usage Example:

// Get messages for current conversation
const response = await posthog.conversations.getMessages();
console.log('Messages:', response.messages);

// Get messages for specific ticket
const ticketMessages = await posthog.conversations.getMessages('ticket-123');

Get Tickets

Retrieves all tickets for the current user.

/**
 * Gets all tickets for the current user
 * @param options - Optional query options
 * @returns Promise with tickets
 */
function getTickets(
    options?: GetTicketsOptions
): Promise<GetTicketsResponse>;

Usage Example:

// Get all tickets
const response = await posthog.conversations.getTickets();
console.log('Tickets:', response.tickets);

// Filter tickets by status
const openTickets = response.tickets.filter(
    ticket => ticket.status === 'open'
);

Mark as Read

Marks a ticket as read.

/**
 * Marks a ticket as read
 * @param ticketId - Ticket ID to mark as read
 * @returns Promise with result
 */
function markAsRead(ticketId: string): Promise<MarkAsReadResponse>;

Usage Example:

// Mark ticket as read
await posthog.conversations.markAsRead('ticket-123');

Conversations Usage Pattern

// Initialize with conversations enabled
posthog.init('token', {
    disable_conversations: false
});

// Wait for conversations to load
// Then use the API
async function initConversations() {
    // Check if conversations are available
    if (posthog.conversations) {
        // Get existing tickets
        const { tickets } = await posthog.conversations.getTickets();
        console.log('User has', tickets.length, 'tickets');

        // Send a new message
        await posthog.conversations.sendMessage('I need support', {
            email: 'user@example.com'
        });

        // Get messages for a ticket
        const { messages } = await posthog.conversations.getMessages(tickets[0].id);
        console.log('Ticket messages:', messages);
    }
}

Toolbar

The PostHog toolbar provides visual analytics and feature management capabilities.

Load Toolbar

Loads the PostHog toolbar for visual analytics and editing.

/**
 * Loads the PostHog toolbar
 * @param params - Toolbar parameters
 * @returns True if toolbar loaded successfully
 */
function loadToolbar(params: ToolbarParams): boolean;

Usage Examples:

// Load toolbar with API key
posthog.loadToolbar({
    apiKey: 'your-api-key'
});

// Load with user email
posthog.loadToolbar({
    apiKey: 'your-api-key',
    userEmail: 'user@example.com'
});

// Load for admin users only
if (user.isAdmin) {
    posthog.loadToolbar({
        apiKey: process.env.POSTHOG_API_KEY,
        userEmail: user.email
    });
}

// Load via URL parameter
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('posthog_toolbar') === 'true') {
    posthog.loadToolbar({
        apiKey: 'your-api-key'
    });
}

Toolbar Types

/**
 * Toolbar parameters
 */
interface ToolbarParams {
    /**
     * PostHog API key
     */
    apiKey?: string;

    /**
     * User email for toolbar
     */
    userEmail?: string;

    /**
     * Additional toolbar parameters
     */
    [key: string]: any;
}

Event Listeners

Register callbacks for various PostHog events.

Event Captured Listener

Registers a callback that fires whenever an event is captured.

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

Usage Examples:

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

// Stop listening
unsubscribe();

// Filter specific events
posthog.on('eventCaptured', (data) => {
    if (data.event === 'purchase_completed') {
        showThankYou();
    }
});

// Send to external service
posthog.on('eventCaptured', (data) => {
    if (data.event.startsWith('$')) {
        return; // Skip internal events
    }

    sendToExternalAnalytics(data);
});

Feature Flags Listener

Registers a callback for when feature flags are loaded or updated.

/**
 * Registers a callback for when feature flags are loaded
 * @param callback - Function to call when flags are loaded
 * @returns Function to unsubscribe from callback
 */
function onFeatureFlags(callback: FeatureFlagsCallback): () => void;

See Feature Flags for details.

Surveys Listener

Registers a callback for when surveys are loaded.

/**
 * Registers a callback for when surveys are loaded
 * @param callback - Function to call when surveys are loaded
 * @returns Function to unsubscribe from callback
 */
function onSurveysLoaded(callback: SurveyCallback): () => void;

See Surveys for details.

Session ID Listener

Registers a callback for when the session ID changes.

/**
 * Registers a callback for when the session ID changes
 * @param callback - Function to call when session changes
 * @returns Function to unsubscribe from callback
 */
function onSessionId(callback: SessionIdChangedCallback): () => void;

See Session Recording for details.

Utility Methods

Get Page View ID

Gets the unique ID for the current page view.

/**
 * Gets the unique ID for the current page view
 * @returns Page view ID or undefined
 */
function getPageViewId(): string | undefined;

Usage Example:

const pageViewId = posthog.getPageViewId();
console.log('Page view:', pageViewId);

Push to Queue

Pushes a deferred operation to the queue (mainly for snippet usage).

/**
 * Pushes a deferred operation to the queue
 * @param item - Operation to push
 */
function push(item: SnippetArrayItem): void;

Usage Example:

// Used primarily with the snippet loader
posthog.push(['identify', 'user-123']);

To String

Returns a string representation of the PostHog instance.

/**
 * Returns a string representation of the instance
 * @returns String representation (typically the instance name)
 */
function toString(): string;

Usage Example:

// Get instance name
const instanceName = posthog.toString();
console.log('Instance:', instanceName); // 'posthog' or custom name

Integrations

Sentry Integration

PostHog provides a Sentry integration class for connecting error tracking with PostHog analytics.

/**
 * Sentry integration class
 * Use this with Sentry SDK to link errors with PostHog sessions
 */
class SentryIntegration {
    // Integration class for Sentry error tracking
}

/**
 * Creates a Sentry integration instance
 * @param options - Configuration options for the integration
 * @returns SentryIntegration instance
 */
function sentryIntegration(options?: SentryIntegrationOptions): SentryIntegration;

Usage Example:

import * as Sentry from '@sentry/browser';
import posthog, { sentryIntegration } from 'posthog-js';

// Initialize PostHog
posthog.init('your-api-key');

// Initialize Sentry with PostHog integration
Sentry.init({
    dsn: 'your-sentry-dsn',
    integrations: [
        sentryIntegration({
            posthog: posthog,
            organization: 'my-org',
            projectId: 123456
        })
    ]
});

// Errors will now be linked between Sentry and PostHog
try {
    riskyOperation();
} catch (error) {
    // Both PostHog and Sentry will capture this
    Sentry.captureException(error);
    posthog.captureException(error);
}

Common Patterns

Multi-Tenant Applications

Track group-level analytics for SaaS applications:

// On user login
async function onUserLogin(user) {
    // Identify user
    posthog.identify(user.id, {
        email: user.email,
        name: user.name
    });

    // Associate with organization
    posthog.group('organization', user.organizationId, {
        name: user.organization.name,
        plan: user.organization.plan,
        seat_count: user.organization.seats,
        created_at: user.organization.createdAt
    });

    // Associate with team
    if (user.teamId) {
        posthog.group('team', user.teamId, {
            name: user.team.name,
            role: user.team.role
        });
    }

    // Reload flags with group context
    posthog.reloadFeatureFlags();
}

// Track organization-level events
function trackOrganizationEvent(eventName, properties = {}) {
    const groups = posthog.getGroups();

    posthog.capture(eventName, {
        ...properties,
        organization_id: groups.organization,
        team_id: groups.team
    });
}

LLM Application Tracking

Comprehensive LLM interaction tracking:

class LLMTracker {
    async trackGeneration(prompt, context = {}) {
        const traceId = this.generateTraceId();
        const startTime = Date.now();

        try {
            // Capture generation start
            posthog.capture('llm_generation_started', {
                trace_id: traceId,
                prompt_length: prompt.length,
                ...context
            });

            // Generate response
            const response = await this.llm.generate(prompt);

            // Capture metrics
            const duration = Date.now() - startTime;
            posthog.captureTraceMetric(traceId, 'response_time_ms', duration);
            posthog.captureTraceMetric(traceId, 'tokens_used', response.usage.total_tokens);
            posthog.captureTraceMetric(traceId, 'model', response.model);
            posthog.captureTraceMetric(traceId, 'temperature', response.temperature);

            // Capture completion
            posthog.capture('llm_generation_completed', {
                trace_id: traceId,
                success: true
            });

            return { traceId, response };
        } catch (error) {
            // Capture error
            posthog.captureException(error, {
                trace_id: traceId,
                context: 'llm_generation'
            });

            posthog.capture('llm_generation_failed', {
                trace_id: traceId,
                error: error.message
            });

            throw error;
        }
    }

    trackFeedback(traceId, feedback) {
        posthog.captureTraceFeedback(traceId, feedback);

        // Also capture as event for analysis
        posthog.capture('llm_feedback_received', {
            trace_id: traceId,
            feedback: feedback
        });
    }

    generateTraceId() {
        return `trace-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
    }
}

// Usage
const tracker = new LLMTracker();

async function generateResponse(userPrompt) {
    const { traceId, response } = await tracker.trackGeneration(
        userPrompt,
        { user_id: currentUser.id }
    );

    return { traceId, response };
}

function handleFeedback(traceId, rating, comment) {
    const feedback = JSON.stringify({ rating, comment });
    tracker.trackFeedback(traceId, feedback);
}

Admin Toolbar Integration

Load toolbar for admin users:

function initializeAdminFeatures(user) {
    if (user.role === 'admin' || user.role === 'developer') {
        // Load toolbar
        posthog.loadToolbar({
            apiKey: process.env.POSTHOG_API_KEY,
            userEmail: user.email
        });

        // Add toolbar toggle to UI
        addToolbarToggle();
    }
}

function addToolbarToggle() {
    const toggle = document.createElement('button');
    toggle.textContent = 'Toggle PostHog Toolbar';
    toggle.onclick = () => {
        // Toolbar can be toggled with keyboard shortcut
        // or programmatically loaded
        posthog.loadToolbar({
            apiKey: process.env.POSTHOG_API_KEY
        });
    };

    document.body.appendChild(toggle);
}

Event Pipeline Integration

Forward events to external systems:

// Initialize external integrations
const integrations = {
    segment: new SegmentClient(),
    amplitude: new AmplitudeClient(),
    mixpanel: new MixpanelClient()
};

// Forward all PostHog events
posthog.on('eventCaptured', (data) => {
    // Skip internal events
    if (data.event.startsWith('$')) {
        return;
    }

    // Forward to other platforms
    Object.values(integrations).forEach(integration => {
        integration.track(data.event, data.properties);
    });
});

// Selective forwarding
posthog.on('eventCaptured', (data) => {
    const criticalEvents = [
        'purchase_completed',
        'signup_completed',
        'subscription_changed'
    ];

    if (criticalEvents.includes(data.event)) {
        // Send to data warehouse
        sendToWarehouse(data);

        // Alert monitoring system
        sendToMonitoring(data);
    }
});

Custom Event Enrichment

Enrich events with additional context:

posthog.on('eventCaptured', async (data) => {
    // Skip if already enriched
    if (data.properties._enriched) {
        return;
    }

    // Add user context
    const user = await getCurrentUser();
    if (user) {
        data.properties.user_plan = user.plan;
        data.properties.user_cohort = user.cohort;
        data.properties.user_tenure_days = calculateTenure(user.createdAt);
    }

    // Add session context
    data.properties.session_duration = getSessionDuration();
    data.properties.pages_viewed = getPageViewCount();

    // Add technical context
    data.properties.connection_type = navigator.connection?.effectiveType;
    data.properties.device_memory = navigator.deviceMemory;

    // Mark as enriched
    data.properties._enriched = true;
});

Real-Time Analytics Dashboard

Build real-time monitoring:

class AnalyticsDashboard {
    constructor() {
        this.events = [];
        this.maxEvents = 100;

        this.setupListeners();
    }

    setupListeners() {
        // Track all events
        posthog.on('eventCaptured', (data) => {
            this.addEvent(data);
        });

        // Track session changes
        posthog.onSessionId((sessionId) => {
            this.updateSessionInfo(sessionId);
        });

        // Track flag changes
        posthog.onFeatureFlags((flags) => {
            this.updateFlags(flags);
        });
    }

    addEvent(data) {
        this.events.unshift({
            ...data,
            timestamp: Date.now()
        });

        if (this.events.length > this.maxEvents) {
            this.events.pop();
        }

        this.render();
    }

    updateSessionInfo(sessionId) {
        console.log('Session changed:', sessionId);
        this.render();
    }

    updateFlags(flags) {
        console.log('Flags updated:', flags);
        this.render();
    }

    render() {
        // Update dashboard UI
        this.renderEventList();
        this.renderMetrics();
    }

    renderEventList() {
        // Display recent events
        console.table(this.events.slice(0, 10));
    }

    renderMetrics() {
        // Calculate and display metrics
        const eventCounts = this.events.reduce((acc, e) => {
            acc[e.event] = (acc[e.event] || 0) + 1;
            return acc;
        }, {});

        console.log('Event counts:', eventCounts);
    }
}

// Initialize dashboard
const dashboard = new AnalyticsDashboard();

Advanced Group Analytics Patterns

Hierarchical Groups

// Handle nested organizational structures
interface OrganizationHierarchy {
    company: string;
    division: string;
    department: string;
    team: string;
}

function setOrganizationContext(user: User, hierarchy: OrganizationHierarchy) {
    // Set all group levels
    posthog.group('company', hierarchy.company, {
        company_name: user.companyName,
        company_size: user.companySize,
        industry: user.industry
    });
    
    posthog.group('division', hierarchy.division, {
        division_name: user.divisionName,
        division_head: user.divisionHead
    });
    
    posthog.group('department', hierarchy.department, {
        department_name: user.departmentName,
        budget: user.departmentBudget
    });
    
    posthog.group('team', hierarchy.team, {
        team_name: user.teamName,
        team_size: user.teamSize
    });
    
    // Reload flags to get group-based targeting
    posthog.reloadFeatureFlags();
}

Group Property Management

// Update group properties dynamically
class GroupPropertyManager {
    updateCompanyMetrics(companyId: string, metrics: {
        mrr?: number;
        userCount?: number;
        activeUsers?: number;
    }) {
        posthog.group('company', companyId, {
            mrr: metrics.mrr,
            user_count: metrics.userCount,
            active_users: metrics.activeUsers,
            last_updated: new Date().toISOString()
        });
    }
    
    incrementUsage(companyId: string, feature: string) {
        posthog.capture('feature_used', {
            feature: feature
        });
        
        // Update company-level usage tracking
        posthog.group('company', companyId, {
            [`${feature}_usage_count`]: 1  // PostHog will increment
        });
    }
}

Multi-Tenant Analytics

// Complete multi-tenant tracking
class MultiTenantAnalytics {
    identifyTenant(user: User, tenant: Tenant) {
        // Identify user
        posthog.identify(user.id, {
            email: user.email,
            name: user.name,
            role: user.role
        });
        
        // Associate with tenant
        posthog.group('tenant', tenant.id, {
            tenant_name: tenant.name,
            plan: tenant.plan,
            created_at: tenant.createdAt,
            user_count: tenant.userCount,
            mrr: tenant.mrr,
            industry: tenant.industry,
            region: tenant.region
        });
        
        // Track tenant-level events
        posthog.capture('tenant_session_started', {
            tenant_id: tenant.id,
            user_role: user.role
        });
        
        // Reload flags for tenant-specific features
        posthog.reloadFeatureFlags();
    }
    
    trackTenantEvent(event: string, properties: Properties = {}) {
        const groups = posthog.getGroups();
        
        posthog.capture(event, {
            ...properties,
            tenant_id: groups.tenant
        });
    }
    
    switchTenant(newTenant: Tenant) {
        // Clear old group associations
        posthog.resetGroups();
        
        // Set new tenant
        posthog.group('tenant', newTenant.id, {
            tenant_name: newTenant.name,
            plan: newTenant.plan
        });
        
        // Track switch
        posthog.capture('tenant_switched', {
            new_tenant_id: newTenant.id
        });
        
        // Reload flags
        posthog.reloadFeatureFlags();
    }
}

Advanced LLM Analytics

Comprehensive LLM Tracking

// Complete LLM interaction tracking
interface LLMTrace {
    id: string;
    model: string;
    prompt: string;
    response?: string;
    startTime: number;
    endTime?: number;
    error?: Error;
}

class LLMAnalytics {
    private traces = new Map<string, LLMTrace>();
    
    startTrace(model: string, prompt: string): string {
        const traceId = `trace_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
        
        this.traces.set(traceId, {
            id: traceId,
            model,
            prompt,
            startTime: Date.now()
        });
        
        // Capture trace start
        posthog.capture('llm_trace_started', {
            trace_id: traceId,
            model: model,
            prompt_length: prompt.length,
            prompt_hash: this.hashPrompt(prompt)
        });
        
        return traceId;
    }
    
    endTrace(traceId: string, response: string, usage: {
        promptTokens: number;
        completionTokens: number;
        totalTokens: number;
    }) {
        const trace = this.traces.get(traceId);
        if (!trace) return;
        
        trace.response = response;
        trace.endTime = Date.now();
        
        const duration = trace.endTime - trace.startTime;
        
        // Capture metrics
        posthog.captureTraceMetric(traceId, 'duration_ms', duration);
        posthog.captureTraceMetric(traceId, 'prompt_tokens', usage.promptTokens);
        posthog.captureTraceMetric(traceId, 'completion_tokens', usage.completionTokens);
        posthog.captureTraceMetric(traceId, 'total_tokens', usage.totalTokens);
        posthog.captureTraceMetric(traceId, 'tokens_per_second', usage.totalTokens / (duration / 1000));
        
        // Capture completion
        posthog.capture('llm_trace_completed', {
            trace_id: traceId,
            model: trace.model,
            response_length: response.length,
            success: true
        });
    }
    
    errorTrace(traceId: string, error: Error) {
        const trace = this.traces.get(traceId);
        if (!trace) return;
        
        trace.error = error;
        trace.endTime = Date.now();
        
        // Capture error
        posthog.captureException(error, {
            trace_id: traceId,
            model: trace.model,
            prompt_length: trace.prompt.length
        });
        
        posthog.capture('llm_trace_failed', {
            trace_id: traceId,
            error: error.message
        });
    }
    
    captureFeedback(traceId: string, rating: number, comment?: string) {
        const feedbackData = JSON.stringify({
            rating: rating,
            comment: comment,
            timestamp: new Date().toISOString()
        });
        
        posthog.captureTraceFeedback(traceId, feedbackData);
        
        // Also track as event for analysis
        posthog.capture('llm_feedback', {
            trace_id: traceId,
            rating: rating,
            has_comment: !!comment
        });
    }
    
    private hashPrompt(prompt: string): string {
        // Simple hash for prompt deduplication
        return prompt.split('').reduce((hash, char) => {
            return ((hash << 5) - hash) + char.charCodeAt(0);
        }, 0).toString(36);
    }
}

// Usage
const llm = new LLMAnalytics();

async function generateResponse(prompt: string) {
    const traceId = llm.startTrace('gpt-4', prompt);
    
    try {
        const result = await openai.chat.completions.create({
            model: 'gpt-4',
            messages: [{ role: 'user', content: prompt }]
        });
        
        llm.endTrace(traceId, result.choices[0].message.content!, {
            promptTokens: result.usage!.prompt_tokens,
            completionTokens: result.usage!.completion_tokens,
            totalTokens: result.usage!.total_tokens
        });
        
        return { traceId, response: result.choices[0].message.content };
    } catch (error) {
        llm.errorTrace(traceId, error as Error);
        throw error;
    }
}

// Track feedback
function handleUserFeedback(traceId: string, thumbsUp: boolean, comment?: string) {
    const rating = thumbsUp ? 5 : 1;
    llm.captureFeedback(traceId, rating, comment);
}

LLM Cost Tracking

// Track LLM costs and usage
interface TokenPricing {
    prompt: number;    // Price per 1K tokens
    completion: number;  // Price per 1K tokens
}

const modelPricing: Record<string, TokenPricing> = {
    'gpt-4': { prompt: 0.03, completion: 0.06 },
    'gpt-3.5-turbo': { prompt: 0.001, completion: 0.002 }
};

function captureTraceWithCost(
    traceId: string,
    model: string,
    usage: { promptTokens: number; completionTokens: number }
) {
    const pricing = modelPricing[model];
    
    if (pricing) {
        const promptCost = (usage.promptTokens / 1000) * pricing.prompt;
        const completionCost = (usage.completionTokens / 1000) * pricing.completion;
        const totalCost = promptCost + completionCost;
        
        posthog.captureTraceMetric(traceId, 'prompt_cost_usd', promptCost);
        posthog.captureTraceMetric(traceId, 'completion_cost_usd', completionCost);
        posthog.captureTraceMetric(traceId, 'total_cost_usd', totalCost);
    }
    
    posthog.captureTraceMetric(traceId, 'prompt_tokens', usage.promptTokens);
    posthog.captureTraceMetric(traceId, 'completion_tokens', usage.completionTokens);
}

Testing Advanced Features

Testing Group Analytics

describe('Group Analytics', () => {
    beforeEach(() => {
        posthog.resetGroups();
    });
    
    it('associates user with groups', () => {
        posthog.group('company', 'test-company', {
            name: 'Test Company'
        });
        
        const groups = posthog.getGroups();
        expect(groups.company).toBe('test-company');
    });
    
    it('resets groups on logout', () => {
        posthog.group('company', 'test-company');
        posthog.resetGroups();
        
        const groups = posthog.getGroups();
        expect(Object.keys(groups).length).toBe(0);
    });
});

Testing LLM Analytics

describe('LLM Analytics', () => {
    it('captures trace feedback', () => {
        const traceId = 'test-trace-123';
        
        posthog.captureTraceFeedback(traceId, 'positive');
        
        expect(posthog.capture).toHaveBeenCalledWith(
            '$ai_feedback',
            expect.objectContaining({
                trace_id: traceId
            })
        );
    });
    
    it('captures trace metrics', () => {
        const traceId = 'test-trace-123';
        
        posthog.captureTraceMetric(traceId, 'tokens', 100);
        
        expect(posthog.capture).toHaveBeenCalledWith(
            '$ai_metric',
            expect.objectContaining({
                trace_id: traceId,
                metric_name: 'tokens',
                metric_value: 100
            })
        );
    });
});

Troubleshooting Advanced Features

Group Analytics Not Working

// Debug group associations
function debugGroups() {
    console.log('Current groups:', posthog.getGroups());
    
    // Test group association
    posthog.group('test_group', 'test-key', {
        test_property: 'test_value'
    });
    
    console.log('After test group:', posthog.getGroups());
    
    // Verify group events are captured
    posthog.on('eventCaptured', (data) => {
        if (data.event === '$groupidentify') {
            console.log('Group identify event:', data);
        }
    });
    
    // Trigger a group event
    posthog.capture('test_event_with_group');
}

LLM Traces Not Appearing

// Debug LLM trace capture
function debugLLMTraces() {
    const testTraceId = 'debug-trace-123';
    
    // Test feedback capture
    posthog.captureTraceFeedback(testTraceId, 'test feedback');
    
    // Test metric capture
    posthog.captureTraceMetric(testTraceId, 'test_metric', 42);
    
    // Listen for events
    posthog.on('eventCaptured', (data) => {
        if (data.event === '$ai_feedback' || data.event === '$ai_metric') {
            console.log('LLM event captured:', data);
        }
    });
}

Toolbar Not Loading

// Debug toolbar loading
function debugToolbar() {
    console.log('Current URL:', window.location.href);
    console.log('Has __posthog param:', window.location.search.includes('__posthog'));
    
    // Try loading toolbar
    const result = posthog.loadToolbar({});
    console.log('Toolbar load result:', result);
    
    // Check toolbar config
    console.log('Toolbar disabled:', posthog.config.advanced_disable_toolbar_metrics);
    
    // Force load with specific intent
    posthog.loadToolbar({
        userIntent: 'heatmaps'
    });
}

Performance Considerations

Optimize Group Tracking

// Batch group updates
async function updateMultipleGroups(updates: Array<{
    type: string;
    key: string;
    properties: Properties;
}>) {
    // Pause flag reloading during batch
    posthog.featureFlags.setReloadingPaused(true);
    
    // Update all groups
    for (const { type, key, properties } of updates) {
        posthog.group(type, key, properties);
    }
    
    // Resume and reload once
    posthog.featureFlags.setReloadingPaused(false);
    posthog.reloadFeatureFlags();
}

Optimize Event Listeners

// Clean up event listeners properly
class EventListenerManager {
    private unsubscribers: Array<() => void> = [];
    
    addListener(type: 'eventCaptured' | 'featureFlags' | 'sessionId', callback: any) {
        let unsubscribe: () => void;
        
        if (type === 'eventCaptured') {
            unsubscribe = posthog.on('eventCaptured', callback);
        } else if (type === 'featureFlags') {
            unsubscribe = posthog.onFeatureFlags(callback);
        } else {
            unsubscribe = posthog.onSessionId(callback);
        }
        
        this.unsubscribers.push(unsubscribe);
        return unsubscribe;
    }
    
    removeAllListeners() {
        this.unsubscribers.forEach(unsubscribe => unsubscribe());
        this.unsubscribers = [];
    }
}

// Usage in React
function usePostHogListener(callback: (data: CaptureResult) => void) {
    useEffect(() => {
        const unsubscribe = posthog.on('eventCaptured', callback);
        return unsubscribe;  // Cleanup on unmount
    }, [callback]);
}

Edge Cases

Group Reset on User Switch

// Handle organization switching
async function switchOrganization(newOrgId: string) {
    // Get current groups for logging
    const oldGroups = posthog.getGroups();
    
    // Capture switch event before reset
    posthog.capture('organization_switched', {
        old_organization: oldGroups.company,
        new_organization: newOrgId
    });
    
    // Reset all groups
    posthog.resetGroups();
    
    // Load new organization
    const newOrg = await loadOrganization(newOrgId);
    
    // Set new groups
    posthog.group('company', newOrg.id, {
        name: newOrg.name,
        plan: newOrg.plan,
        user_count: newOrg.userCount
    });
    
    // Reload flags for new organization context
    posthog.reloadFeatureFlags();
}

Trace ID Generation Best Practices

// Generate unique, traceable trace IDs
class TraceIdGenerator {
    generate(prefix: string = 'trace'): string {
        const timestamp = Date.now();
        const random = Math.random().toString(36).substring(2, 15);
        const sessionId = posthog.get_session_id().substring(0, 8);
        
        return `${prefix}_${timestamp}_${sessionId}_${random}`;
    }
    
    // Validate trace ID format
    isValid(traceId: string): boolean {
        return /^[a-z]+_\d+_[a-f0-9]+_[a-z0-9]+$/.test(traceId);
    }
}

const traceIdGen = new TraceIdGenerator();

// Usage
const traceId = traceIdGen.generate('llm');
if (traceIdGen.isValid(traceId)) {
    // Use trace ID
}

Feedback with Sentiment Analysis

// Enhanced feedback tracking with sentiment
interface FeedbackData {
    rating: number;
    comment?: string;
    sentiment?: 'positive' | 'neutral' | 'negative';
    categories?: string[];
}

function captureFeedbackWithSentiment(traceId: string, feedback: FeedbackData) {
    // Determine sentiment from rating if not provided
    const sentiment = feedback.sentiment || (
        feedback.rating >= 4 ? 'positive' :
        feedback.rating <= 2 ? 'negative' :
        'neutral'
    );
    
    // Capture structured feedback
    const feedbackJson = JSON.stringify({
        rating: feedback.rating,
        comment: feedback.comment,
        sentiment: sentiment,
        categories: feedback.categories,
        timestamp: new Date().toISOString()
    });
    
    posthog.captureTraceFeedback(traceId, feedbackJson);
    
    // Also capture as event for aggregation
    posthog.capture('llm_feedback_detailed', {
        trace_id: traceId,
        rating: feedback.rating,
        sentiment: sentiment,
        has_comment: !!feedback.comment,
        categories: feedback.categories?.join(',')
    });
}

Security Considerations

Sanitize Group Properties

// Sanitize sensitive group properties
function sanitizeGroupProperties(properties: Properties): Properties {
    const sensitive = ['password', 'api_key', 'secret', 'token'];
    const sanitized = { ...properties };
    
    for (const key of Object.keys(sanitized)) {
        if (sensitive.some(s => key.toLowerCase().includes(s))) {
            delete sanitized[key];
        }
    }
    
    return sanitized;
}

// Usage
posthog.group('company', 'company-123', sanitizeGroupProperties({
    name: 'Company Name',
    api_key: 'secret',  // Will be removed
    plan: 'enterprise'
}));

Validate Trace Data

// Validate and sanitize trace data
function captureSafeTraceFeedback(traceId: string, feedback: string) {
    // Validate trace ID format
    if (!traceId || traceId.length > 255) {
        console.error('Invalid trace ID');
        return;
    }
    
    // Sanitize feedback (remove PII)
    const sanitized = feedback
        .replace(/\b[\w.-]+@[\w.-]+\.\w+\b/g, '[EMAIL]')
        .replace(/\b\d{3}-\d{2}-\d{4}\b/g, '[SSN]')
        .replace(/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, '[CARD]');
    
    posthog.captureTraceFeedback(traceId, sanitized);
}

See Also

  • Group Analytics in Events - Capturing group-level events
  • Feature Flags - Group-based feature flags
  • Identification - User and group identification
  • Sentry Integration - Error tracking integration
  • Toolbar - Debugging with the toolbar
  • Types - Advanced type definitions