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.
Advanced features include group analytics, LLM analytics, product tours, conversations, toolbar, and event listeners for extended functionality.
Group analytics allows you to track organizations, companies, teams, or any entity that groups users together.
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()
});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';
}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
}Track Large Language Model (LLM) interactions, traces, feedback, and metrics.
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>
</>
);
}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 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
});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 reloadFetches 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);
}
});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');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);Dismisses the currently displayed tour.
/**
* Dismisses the current tour
*/
function dismissProductTour(): void;Usage Example:
// User clicks close button
posthog.productTours.dismissProductTour();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();
});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();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 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';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
});/**
* 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.
Shows the conversations widget (button and chat panel).
/**
* Shows the conversations widget
*/
function show(): void;Usage Example:
// Show conversations widget
posthog.conversations.show();Hides the conversations widget completely.
/**
* Hides the conversations widget
*/
function hide(): void;Usage Example:
// Hide conversations widget
posthog.conversations.hide();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');
}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');
}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);
}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);Resets conversations state.
/**
* Resets conversations state
*/
function reset(): void;Usage Example:
// Reset conversations on logout
function logout() {
posthog.conversations.reset();
posthog.reset();
}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'
});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');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'
);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');// 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);
}
}The PostHog toolbar provides visual analytics and feature management capabilities.
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 parameters
*/
interface ToolbarParams {
/**
* PostHog API key
*/
apiKey?: string;
/**
* User email for toolbar
*/
userEmail?: string;
/**
* Additional toolbar parameters
*/
[key: string]: any;
}Register callbacks for various PostHog events.
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);
});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.
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.
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.
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);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']);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 namePostHog 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);
}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
});
}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);
}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);
}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);
}
});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;
});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();// 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();
}// 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
});
}
}// 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();
}
}// 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);
}// 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);
}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);
});
});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
})
);
});
});// 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');
}// 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);
}
});
}// 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'
});
}// 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();
}// 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]);
}// 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();
}// 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
}// 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(',')
});
}// 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 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);
}