posthog-rs 0.4.0

The official Rust client for Posthog (https://posthog.com/).
Documentation

PostHog Rust

Crates.io Documentation

The official Rust SDK for PostHog. See the PostHog docs for more information.

Features

  • 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

Quickstart

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!");
}

Feature Flags

The SDK now supports PostHog feature flags, allowing you to control feature rollout and run A/B tests.

Basic Usage

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"),
}

With Properties

// 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();

With Groups (B2B)

// 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 Flags

// 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);
}

Feature Flag Payloads

// 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);
}

Error Handling

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)
}

Observability

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 run

Log levels:

  • error: Connection failures, HTTP errors
  • warn: Configuration issues, failed flag fetches
  • info: Client initialization, poller start/stop
  • debug: Flag evaluation results, API fallback decisions
  • trace: Detailed cache updates, individual flag lookups

Acknowledgements

Thanks to @christos-h for building the initial version of this project.