The official Rust SDK for PostHog. See the PostHog docs for more information.
- Event capture - Send events to PostHog for product analytics
- Feature flags - Evaluate feature flags with local or remote evaluation
- A/B testing - Support for multivariate flags and experiments
- Group analytics - Track events and flags for B2B use cases
- Async and sync clients - Choose based on your runtime
Add posthog-rs to your Cargo.toml.
[dependencies]
posthog-rs = "0.3.7"let client = posthog_rs::client(env!("POSTHOG_API_KEY"));
// Capture events
let mut event = posthog_rs::Event::new("test", "1234");
event.insert_prop("key1", "value1").unwrap();
event.insert_prop("key2", vec!["a", "b"]).unwrap();
client.capture(event).unwrap();
// Check feature flags
let is_enabled = client.is_feature_enabled(
"new-feature".to_string(),
"user-123".to_string(),
None,
None,
None,
).unwrap();
if is_enabled {
println!("Feature is enabled!");
}The SDK now supports PostHog feature flags, allowing you to control feature rollout and run A/B tests.
use posthog_rs::{client, ClientOptions, FlagValue};
use std::collections::HashMap;
use serde_json::json;
let client = client(ClientOptions::from("your-api-key"));
// 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 (feature_flags, _payloads) = client.get_feature_flags(
"user-id".to_string(),
None, None, None
).unwrap();
for (key, value) in 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);
}In production code, handle errors properly instead of using .unwrap():
use posthog_rs::{client, Error, FlagValue};
let client = client("your-api-key");
// Pattern 1: Match on the error type
match client.get_feature_flag("my-flag", "user-123", None, None, None) {
Ok(Some(FlagValue::Boolean(true))) => {
// Flag is enabled
enable_feature();
}
Ok(Some(FlagValue::String(variant))) => {
// Multivariate flag - use the variant
use_variant(&variant);
}
Ok(Some(FlagValue::Boolean(false))) | Ok(None) => {
// Flag is disabled or doesn't exist
use_default_behavior();
}
Err(Error::Connection(e)) => {
// Network error - maybe use cached value or default
eprintln!("Failed to fetch flag: {}", e);
use_default_behavior();
}
Err(Error::InconclusiveMatch(reason)) => {
// Local evaluation couldn't determine flag value
// (missing properties, unknown cohort, etc.)
eprintln!("Inconclusive evaluation: {}", reason);
use_default_behavior();
}
Err(e) => {
// Other errors
eprintln!("Unexpected error: {}", e);
use_default_behavior();
}
}
// Pattern 2: Use unwrap_or for simple defaults
let is_enabled = client
.is_feature_enabled("my-flag", "user-123", None, None, None)
.unwrap_or(false); // Default to disabled on error
// Pattern 3: Propagate errors with ?
fn check_feature(client: &posthog_rs::Client, user_id: &str) -> Result<bool, Error> {
let flag = client.is_feature_enabled("premium-feature", user_id, None, None, None)?;
Ok(flag)
}The SDK uses tracing for structured logging. To see logs, add a tracing subscriber to your application:
use tracing_subscriber::{fmt, EnvFilter};
// Initialize tracing (e.g., in main.rs)
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.init();Then set the RUST_LOG environment variable to control log levels:
# See all posthog logs
RUST_LOG=posthog_rs=debug cargo run
# See only warnings and errors
RUST_LOG=posthog_rs=warn cargo run
# See trace-level logs for flag evaluation
RUST_LOG=posthog_rs=trace cargo runLog levels:
error: Connection failures, HTTP errorswarn: Configuration issues, failed flag fetchesinfo: Client initialization, poller start/stopdebug: Flag evaluation results, API fallback decisionstrace: Detailed cache updates, individual flag lookups
Thanks to @christos-h for building the initial version of this project.