Skip to content

fern-api/posthog-rs-fern

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PostHog Rust

Please see the main PostHog docs

This crate is under development

Quickstart

Add posthog-rs to your Cargo.toml.

[dependencies]
posthog-rs = "0.3.7"

Basic Usage (US Region)

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

EU Region Configuration

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

Backward Compatibility

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

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

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 response = client.get_feature_flags(
    "user-id".to_string(),
    None, None, None
).unwrap();

for (key, value) in response.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);
}

Local Evaluation (High Performance)

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

Automatic Event Tracking

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

Error Handling

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

Error Categories

  • TransportError: Network issues (DNS, timeouts, HTTP errors, connection failures)
  • ValidationError: Data problems (serialization, batch size, invalid timestamps)
  • InitializationError: Configuration issues (already initialized, not initialized)

Helper Methods

  • is_retryable(): Returns true for transient errors (timeouts, 5xx, 429)
  • is_client_error(): Returns true for 4xx HTTP errors

See examples/error_classification.rs for comprehensive error handling patterns

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Rust 100.0%