Please see the main PostHog docs
This crate is under development
Add posthog-rs to your Cargo.toml.
[dependencies] posthog-rs = "0.3.7"use posthog_rs::{Event, ClientOptionsBuilder}; // Simple initialization with API key (defaults to US region) let client = posthog_rs::client(env!("POSTHOG_API_KEY")); // Create and send an event let mut event = Event::new("user_signed_up", "user_distinct_id"); event.insert_prop("plan", "premium").unwrap(); event.insert_prop("source", "web").unwrap(); client.capture(event).unwrap();use posthog_rs::{Event, ClientOptionsBuilder}; // Configure for EU region - just provide the base URL let options = ClientOptionsBuilder::new() .api_key("phc_your_api_key") .api_endpoint("https://eu.posthog.com") // SDK handles /i/v0/e/ and /batch/ automatically .build() .unwrap(); let client = posthog_rs::client(options); // Single event capture let event = Event::new("user_signed_up", "user_distinct_id"); client.capture(event).unwrap(); // Batch event capture (uses same base URL, different endpoint path) let events = vec![ Event::new("page_view", "user_1"), Event::new("button_click", "user_2"), ]; client.capture_batch(events).unwrap();Old format with full URLs still works - the SDK automatically normalizes them:
// This still works - path is automatically stripped let options = ClientOptionsBuilder::new() .api_key("phc_your_api_key") .api_endpoint("https://eu.posthog.com/i/v0/e/") // Gets normalized to base URL .build() .unwrap();The SDK now supports PostHog feature flags, allowing you to control feature rollout and run A/B tests.
use posthog_rs::{ClientOptionsBuilder, FlagValue}; use std::collections::HashMap; use serde_json::json; let options = ClientOptionsBuilder::default() .api_key("phc_your_project_key") .build() .unwrap(); let client = posthog_rs::client(options); // Check if a feature is enabled let is_enabled = client.is_feature_enabled( "feature-key".to_string(), "user-id".to_string(), None, None, None ).unwrap(); // Get feature flag value (boolean or variant) match client.get_feature_flag( "feature-key".to_string(), "user-id".to_string(), None, None, None ).unwrap() { Some(FlagValue::Boolean(enabled)) => println!("Flag is: {}", enabled), Some(FlagValue::String(variant)) => println!("Variant: {}", variant), None => println!("Flag is disabled"), }// Include person properties for flag evaluation let mut person_props = HashMap::new(); person_props.insert("plan".to_string(), json!("enterprise")); person_props.insert("country".to_string(), json!("US")); let flag = client.get_feature_flag( "premium-feature".to_string(), "user-id".to_string(), None, Some(person_props), None ).unwrap();// For B2B apps with group-based flags let mut groups = HashMap::new(); groups.insert("company".to_string(), "company-123".to_string()); let mut group_props = HashMap::new(); let mut company_props = HashMap::new(); company_props.insert("size".to_string(), json!(500)); group_props.insert("company".to_string(), company_props); let flag = client.get_feature_flag( "b2b-feature".to_string(), "user-id".to_string(), Some(groups), None, Some(group_props) ).unwrap();// Get all feature flags for a user let response = client.get_feature_flags( "user-id".to_string(), None, None, None ).unwrap(); for (key, value) in response.feature_flags { println!("Flag {}: {:?}", key, value); }// Get additional data associated with a feature flag let payload = client.get_feature_flag_payload( "onboarding-flow".to_string(), "user-id".to_string() ).unwrap(); if let Some(data) = payload { println!("Payload: {}", data); }For significantly faster flag evaluation, enable local evaluation to cache flag definitions locally:
use posthog_rs::ClientOptionsBuilder; let options = ClientOptionsBuilder::default() .api_key("phc_your_project_key") .personal_api_key("phx_your_personal_key") // Required for local evaluation .enable_local_evaluation(true) .poll_interval_seconds(30) // Update cache every 30s .build() .unwrap(); let client = posthog_rs::client(options); // Flag evaluations now happen locally (no API calls needed) let enabled = client.is_feature_enabled( "new-feature".to_string(), "user-123".to_string(), None, None, None ).unwrap();Performance: Local evaluation is 100-1000x faster than API evaluation (~119µs vs ~125ms per request).
Get your personal API key at: https://app.posthog.com/me/settings
The SDK automatically captures $feature_flag_called events when you evaluate feature flags. These events include:
- Feature flag key and response value
- Deduplication per user + flag + value combination
- Rich metadata (payloads, versions, request IDs)
To disable automatic events globally:
let options = ClientOptionsBuilder::default() .api_key("phc_your_key") .send_feature_flag_events(false) .build() .unwrap();The SDK provides error handling with semantic categories:
use posthog_rs::{Error, TransportError, ValidationError}; match client.capture(event).await { Ok(_) => println!("Event sent successfully"), Err(Error::Transport(TransportError::Timeout(duration))) => { eprintln!("Request timed out after {:?}", duration); // Retry logic here } Err(Error::Transport(TransportError::HttpError(401, _))) => { eprintln!("Invalid API key - check your configuration"); } Err(e) if e.is_retryable() => { // Automatically handles: timeouts, 5xx errors, 429 rate limits tokio::time::sleep(Duration::from_secs(2)).await; client.capture(event).await?; } Err(e) => eprintln!("Permanent error: {}", e), }- TransportError: Network issues (DNS, timeouts, HTTP errors, connection failures)
- ValidationError: Data problems (serialization, batch size, invalid timestamps)
- InitializationError: Configuration issues (already initialized, not initialized)
is_retryable(): Returnstruefor transient errors (timeouts, 5xx, 429)is_client_error(): Returnstruefor 4xx HTTP errors
See examples/error_classification.rs for comprehensive error handling patterns