Skip to content
Armin RAD edited this page Dec 16, 2025 · 1 revision

Feature Flags (Statsig)

A/B testing and feature management with Statsig


Overview

Feature flags enable:

  • Gradual rollouts - Release features to subsets
  • A/B testing - Test variations
  • Kill switches - Disable features instantly
  • User targeting - Target specific users/segments
  • Analytics integration - Track feature impact

Provider: Statsig


Quick Start

Environment Setup

STATSIG_SDK_KEY=secret-xxx
STATSIG_ENVIRONMENT=production  # or staging, development

Check Feature Flag

from src.services.statsig_service import check_feature_gate

# Check if feature enabled for user
enabled = await check_feature_gate(
    user_id="user_123",
    gate_name="new_pricing_ui"
)

if enabled:
    # Show new UI
    return render_new_ui()
else:
    # Show old UI
    return render_old_ui()

Get Feature Config

config = await get_feature_config(
    user_id="user_123",
    config_name="pricing_tiers"
)

# Use config values
tier_prices = config.get("prices", default_prices)

API Integration

Check Gate

GET /features/gate/{gate_name}

Response:

{
  "enabled": true,
  "gate_name": "new_pricing_ui",
  "user_id": "user_123"
}

Get Config

GET /features/config/{config_name}

Response:

{
  "config_name": "pricing_tiers",
  "value": {
    "free": 0,
    "basic": 10,
    "pro": 50
  }
}

Common Use Cases

1. Gradual Rollout

# Statsig Dashboard:
# Gate: "new_dashboard"
# Rule: % rollout = 10%

if check_gate(user_id, "new_dashboard"):
    return new_dashboard()
else:
    return old_dashboard()

10% of users see new dashboard.

2. Beta Features

# Gate: "beta_features"
# Rule: User email ends with @company.com

if check_gate(user_id, "beta_features"):
    show_beta_features()

3. A/B Testing

# Experiment: "pricing_experiment"
# Variants: control, variant_a, variant_b

variant = get_experiment(user_id, "pricing_experiment")

if variant == "variant_a":
    price = 9.99
elif variant == "variant_b":
    price = 14.99
else:  # control
    price = 12.99

4. Kill Switch

# Emergency disable
if not check_gate(user_id, "enable_payments"):
    return {"error": "Payments temporarily disabled"}

Toggle off instantly in dashboard.


Statsig Dashboard

Creating Gates

  1. Go to Statsig Dashboard
  2. Navigate to Feature Gates
  3. Click Create
  4. Set name: new_pricing_ui
  5. Add rules:
    • All users: 0% (default off)
    • Email contains "@admin.com": 100%
    • Random: 10% rollout
  6. Save

Creating Configs

  1. Navigate to Dynamic Configs
  2. Click Create
  3. Set name: pricing_config
  4. Add JSON value:
{
  "tiers": ["free", "basic", "pro"],
  "prices": [0, 10, 50]
}
  1. Save

Experiments

  1. Navigate to Experiments
  2. Click Create
  3. Set name: button_color_test
  4. Define variants:
    • Control (blue)
    • Variant A (green)
    • Variant B (red)
  5. Set allocation: 33% each
  6. Start experiment

Code Examples

Frontend (React)

import { useGate } from 'statsig-react';

function PricingPage() {
  const { value: newPricing } = useGate('new_pricing_ui');

  return newPricing ? <NewPricing /> : <OldPricing />;
}

Backend (FastAPI)

from fastapi import Depends
from src.services.statsig_service import StatsigService

@app.get("/pricing")
async def get_pricing(
    user_id: int = Depends(get_current_user),
    statsig: StatsigService = Depends()
):
    if await statsig.check_gate(user_id, "new_pricing"):
        return new_pricing_structure
    return old_pricing_structure

Implementation

Location: src/services/statsig_service.py

from statsig import statsig

class StatsigService:
    def __init__(self):
        statsig.initialize(os.getenv("STATSIG_SDK_KEY"))

    async def check_gate(self, user_id: str, gate: str) -> bool:
        user = {"userID": user_id}
        return statsig.check_gate(user, gate)

    async def get_config(self, user_id: str, config: str):
        user = {"userID": user_id}
        return statsig.get_config(user, config)

    async def log_event(self, user_id: str, event: str, metadata: dict):
        user = {"userID": user_id}
        statsig.log_event(user, event, metadata=metadata)

User Targeting

By User ID

# Rule: userID in ["user_1", "user_2", "user_3"]

By Email

# Rule: email ends with "@company.com"

By Custom Attribute

user = {
    "userID": "123",
    "custom": {
        "plan": "pro",
        "country": "US",
        "signup_date": "2024-01-01"
    }
}

statsig.check_gate(user, "us_only_feature")
# Rule: country == "US"

By Percentage

# Rule: Random % = 25%
# 25% of users see feature

Analytics

Track Events

from src.services.statsig_service import log_event

# Log feature usage
await log_event(
    user_id="user_123",
    event="feature_used",
    metadata={
        "feature": "new_dashboard",
        "action": "clicked_button"
    }
)

Experiment Metrics

Statsig automatically tracks:

  • Exposure: Users who saw experiment
  • Conversions: Users who completed goal
  • Lift: % improvement over control

Best Practices

  1. Start small: 5-10% rollout initially
  2. Monitor metrics: Watch for errors/performance
  3. Use descriptive names: new_pricing_v2_2024
  4. Document gates: What they control
  5. Clean up old gates: Remove unused flags
  6. Test both paths: Ensure on/off works
  7. Set defaults: Fail-safe if Statsig down

Troubleshooting

Gate always returns false

Cause: SDK key wrong or gate doesn't exist

Solution:

  1. Verify STATSIG_SDK_KEY in .env
  2. Check gate name spelling
  3. Verify gate exists in dashboard

Changes not reflecting

Cause: Cache not refreshed

Solution:

# Force refresh
statsig.shutdown()
statsig.initialize(sdk_key)

User not in experiment

Cause: User doesn't match targeting rules

Solution: Check rules in dashboard, verify user attributes


Configuration

Environment Variables

STATSIG_SDK_KEY=secret-xxx
STATSIG_ENVIRONMENT=production
STATSIG_LOG_LEVEL=info

Initialize

import statsig

statsig.initialize(
    sdk_key=os.getenv("STATSIG_SDK_KEY"),
    options={
        "environment": {"tier": "production"},
        "disable_diagnostics": False
    }
)

Related Documentation


Last Updated: December 2024 Status: Production Ready

Clone this wiki locally