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