# Amplitude Documentation — Full Corpus # Generated: 2026-07-11T21:20:15Z # Pages: 815 # Source: https://docs.statsig.com/llms.txt ================================================================================ # Statsig Overview URL: https://docs.statsig.com/welcome Updated: 2026-07-11 ================================================================================ Statsig is a unified platform for feature flags, A/B testing, and product analytics. Ship, measure, and learn with tools used by leading tech companies. ## Quick links {% card-grid columns="3" %} {% card title="SDK Quickstart" href="/sdks/quickstart" icon="bolt" %} Install the Statsig SDK and get started in minutes. {% /card %} {% card title="Intro to Statsig" href="/understanding-platform" icon="flag-checkered" %} Learn more about core concepts and features of Statsig. {% /card %} {% card title="Warehouse Native" href="/statsig-warehouse-native/introduction" icon="database" %} Read about how to use Statsig with your data warehouse. {% /card %} {% /card-grid %} ## Key features {% card-grid columns="3" %} {% card title="Feature Flags" href="/feature-flags/overview" img="images/homepage/flags.png" %} Control feature rollouts and manage deployments safely with feature flags. {% /card %} {% card title="Experimentation" href="/experiments/overview" img="images/homepage/pulse.png" %} Run A/B tests and experiments to optimize your product decisions. {% /card %} {% card title="Product Analytics" href="/product-analytics/overview" img="images/homepage/mex.png" %} Analyze user behavior and product metrics with powerful analytics tools. {% /card %} {% /card-grid %} You can use each of these features on top of your own data warehouse through [Warehouse Native](/statsig-warehouse-native/introduction) with no ETL, or host your data in Statsig's infrastructure. *** ## Explore SDKs Statsig offers SDKs for a wide variety of platforms to suit any codebase or deployment preference: ### Client SDKs {% card-grid columns="3" %} {% card title="JavaScript" href="/client/javascript-sdk" icon="js" %} Browser JavaScript {% /card %} {% card title="React" href="/client/React" icon="react" %} Client-Side React {% /card %} {% card title="React Native" href="/client/ReactNative" icon="react" %} Bare React Native SDK {% /card %} {% card title="Next.js" href="/client/Next" icon="n" %} Next.js SSR, SSG & Client-Side {% /card %} {% card title="Angular" href="/client/Angular" icon="angular" %} Angular bindings for Javascript SDK {% /card %} {% card title="Swift" href="/client/iosClientSDK" icon="swift" %} iOS, MacOS, tvOS SDK {% /card %} {% card title="Android" href="/client/Android" icon="android" %} Android Kotlin/Java SDK {% /card %} {% card title=".NET Client" href="/client/DotNet" icon="dotnet" %} Client SDK for .NET framework {% /card %} {% card title="Roku" href="/client/Roku" icon="roku" %} Roku Brightscript SDK {% /card %} {% card title="Unity" href="/client/Unity" icon="unity" %} Unity game engine SDK {% /card %} {% card title="Dart/Flutter" href="/client/Dart" icon="flutter" %} Flutter/Dart Mobile App SDK {% /card %} {% card title="C++ Client" href="/client/CPP" icon="cplusplus" %} C++ client-side SDK {% /card %} {% /card-grid %} ### Server side SDKs {% card-grid columns="3" %} {% card title="Node.js" href="/server-core/node-core" icon="node-js" %} Node.js server SDK {% /card %} {% card title="Java" href="/server-core/java-core" icon="java" %} Java server SDK {% /card %} {% card title="Python" href="/server-core/python-core" icon="python" %} Python server SDK {% /card %} {% card title="Go" href="/server/go" icon="golang" %} Go server SDK {% /card %} {% card title="Ruby" href="/server/ruby" icon="ruby" %} Ruby server SDK {% /card %} {% card title=".NET Server" href="/server-core/dotnet-core" icon="dotnet" %} .NET server SDK {% /card %} {% card title="PHP" href="/server-core/php-core" icon="php" %} PHP server SDK {% /card %} {% card title="Rust" href="/server-core/rust-core" icon="rust" %} Rust server SDK {% /card %} {% card title="C++ Server" href="/server-core/cpp-core" icon="cplusplus" %} C++ server SDK {% /card %} {% /card-grid %} ### Integrations {% card-grid columns="3" %} {% card title="Webflow" href="/guides/webflow-sidecar-ab-test" icon="w" %} Webflow integration {% /card %} {% card title="Shopify" href="/guides/shopify-ab-test" icon="shopify" %} Shopify integration {% /card %} {% card title="Segment" href="/integrations/data-connectors/segment" icon="segment" %} Segment data connector {% /card %} {% card title="Rudderstack" href="/integrations/data-connectors/rudderstack" icon="rudderstack" %} Rudderstack connector {% /card %} {% card title="Hightouch" href="/integrations/data-connectors/hightouch" icon="hightouch" %} Hightouch integration {% /card %} {% card title="mParticle" href="/integrations/data-connectors/mparticle" icon="mparticle" %} mParticle connector {% /card %} {% card title="Framer" href="/guides/framer-analytics" icon="f" %} Framer integration {% /card %} {% card title="Slack" href="/integrations/slack" icon="slack" %} Slack notifications {% /card %} {% card title="Integrations" href="/integrations/introduction" icon="puzzle" %} View more integrations {% /card %} {% /card-grid %} If you don't see the SDK or framework you need, reach out directly in the [Slack Community](https://statsig.com/slack). *** ## Learning resources * **Statsig University**: A comprehensive collection of onboarding and training content, including a video Resource Library and a schedule of user training Webinars. * **Community**: Join the Slack Community to connect with other users and get help from the Statsig team. * **Blog**: Stay updated with the latest developments in product experimentation and feature management on the [Blog](https://statsig.com/blog). * **Use Cases**: Discover [Customer Stories](https://statsig.com/customers) to learn why customers love Statsig and how they use it to power their product development. ================================================================================ # Platform Overview URL: https://docs.statsig.com/understanding-platform Updated: 2026-07-11 ================================================================================ ## What Statsig is used for Statsig's goal is to be the single platform to ship, measure, and learn from the products you build. The most popular features of Statsig are: * [Feature Flags](/feature-flags/overview): Expose new features to select user groups, roll them out (and roll them back, when things go wrong), and measure the impact they have. * [Experiments](/experiments/overview): Run randomized, controlled experiments on different variations of your product, and measure the exact impact on your users. Customers use Statsig to run thousands of experiments each year, and iterate in the right direction. * [Product Analytics](/product-analytics/overview): Understand the trends of your core business metrics, user behavior, and more. * [Infra Analytics](/infra-analytics/overview): Monitor and debug service health alongside product outcomes. Ingest metrics and traces with OpenTelemetry, search and group logs, and set alerts to catch regressions fast. Statsig also offers other tools like [Session Replay](/session-replay/overview) and [Web Analytics](/webanalytics/overview/), and these features are most effective when used together. *** ## How to set up Statsig The most common setup approach is to integrate [Statsig's SDKs](/sdks/quickstart), which let you integrate flags and experiments, and track your core business metrics. If you want to bring existing data to Statsig, you have a few options: * **I'd like to bring existing data from my Data Warehouse:** You'll likely want to use [Statsig Warehouse Native](/statsig-warehouse-native/introduction), a zero-ETL model for running experiments and product analytics on top of your existing data. Alternatively, you can import warehouse data into Statsig Cloud with the [warehouse ingestions](/data-warehouse-ingestion/introduction). * **I'd like to bring existing data from my Segment, Rudderstack, Amplitude, or another platform:** Consider one of Statsig's [integrations](/integrations/introduction), which can port your events straight into Statsig Cloud. * **I'd like to import my existing experiment assignment data, and use Statsig for Analysis:** You'll need to use [Statsig Warehouse Native](/statsig-warehouse-native/introduction). Statsig has two deployment models based on your needs: Statsig Cloud (where Statsig hosts your data) and Statsig Warehouse Native (where you host your data in your own warehouse). *** ## Statsig Cloud With Statsig Cloud, install the Statsig SDK and configure event logging. Statsig handles everything else. * You get feature flags and 2 million metered events for free. * Includes analytics tools such as Dashboards, Metrics Explorer, and Insights. * For pricing details, refer to the [pricing page](https://www.statsig.com/pricing). Statsig Cloud is a good option for teams that want to get started quickly without managing infrastructure or data warehousing. *** ## Statsig Warehouse Native (WHN) If your events and metrics already reside in your own data warehouse and you have a dedicated data team, Statsig Warehouse Native (WHN) may be a better option. * WHN lets you host Statsig's Stats Engine within your warehouse, enabling you to calculate metric lifts on your pre-existing datasets. * You can choose between two methods: 1. **Using 3rd party or your own SDKs**: You handle feature assignment and provide exposure data (you randomize the users). 2. **Using Statsig SDKs**: Statsig handles randomization and writes data into your warehouse. The first method helps you scale analysis, while the second can significantly increase your experimentation velocity. {% callout type="note" %} WHN is available only with Enterprise contracts. If you're interested in this option, go to [Statsig Warehouse Native](/statsig-warehouse-native/introduction) or [schedule a demo](https://www.statsig.com/contact/demo) with the Sales team. {% /callout %} *** ## Choosing between Cloud and Warehouse Native The following criteria can help you decide between the two deployment modes: | Criteria | Cloud-hosted | Warehouse native (WHN) | | --- | --- | --- | | Data source | Primary source of metrics comes from Statsig SDKs or CDPs like Segment. Some metrics can still come from a warehouse. | Warehouse is the primary source of metrics, making WHN ideal when you want to reuse existing data pipelines and computation. | | Analysis needs | Automated experimentation for every experiment and product launch, especially with metrics derived from event logging. | Flexible analysis on top of your existing source of truth metric data. | | Data team involvement | Optional but recommended for experiment design and readouts. | Necessary for setting up the warehouse connection and configuring core metrics, but not mandatory for every experiment. | | Costs | TCO is slightly lower. No warehouse costs involved. | TCO includes Statsig license + costs incurred for computation and storage in your warehouse. | | Modularity | An integrated end-to-end platform that spans SDKs for feature rollout, experiment execution, analysis, and experiment readouts. | Modular: You can opt for the integrated end-to-end platform or choose to use only a subset of capabilities, such as assignment or experiment analysis. | For a detailed comparison, go to [Statsig Cloud vs Warehouse Native](https://www.statsig.com/blog/deciding-cloud-hosted-versus-warehouse-native-experimentation-platforms). ## Get started with Statsig After you decide which deployment model fits your needs, choose the appropriate getting started guide: * [Getting Started with Statsig Cloud](/sdks/getting-started) * [Getting Started with Statsig Warehouse Native](/statsig-warehouse-native/guides/quick-start) {% callout type="info" %} Have a question or need help? The Statsig Engineering, Data, and Product teams are available in the [Slack community](https://www.statsig.com/slack). {% /callout %} ================================================================================ # Support Options URL: https://docs.statsig.com/support-options Updated: 2026-07-11 ================================================================================ Statsig uses automated tooling in the Slack Connect channel to triage and prioritize inbound support messages. All support at Statsig is handled by actual, full-time engineers. While Statsig maintains a monitored support email, Slack is strongly encouraged as the primary channel for support and communication to ensure the fastest response times. Preferred methods of raising a support request: 1. Slack message through your dedicated Slack channel or [Slack Community](https://statsig.com/slack) 2. Email to support@statsig.com is discouraged but available for sensitive topics ## Community vs Standard vs Premium support | Feature | Community | Standard | Premium | |---------|-----------|----------|---------| | Access to Statsig University [certifications](https://learn.statsig.com/certifications) and [webinar](https://learn.statsig.com/pages/upcoming-webinars) training sessions | Yes | Yes | Yes | | Access to Statsig's global [Slack community](https://statsig.com/slack) for best practices and peer insights | Yes | Yes | Yes | | Basic assistance during Statsig business hours | Yes | Yes | Yes | | Customer member access to Private Slack channel with Statsig(1) | No | No | Up to 4 members | | Critical/High Priority support with 4 hour response time during Statsig business hours(2) | No | No | Yes | | 24 hour response time for all other inquiries during Statsig business hours | No | No | Yes | | Dedicated Account Manager to support ongoing needs | No | Yes | Yes | | Check-ins with the Statsig team experts to align on goals and progress | No | As needed | Quarterly | | Data science consultations for advisory and experiment reviews | No | No | Up to 4 | | Early access invitation to events hosted by Statsig and partners | No | No | Yes | 1. Standard or Premium support is only available for [Enterprise plans](https://www.statsig.com/pricing). 2. Statsig standard business hours of 8am-5pm PT. ## Learning resources * **Statsig University**: A comprehensive collection of onboarding and training content, including a video [Resource Library](https://learn.statsig.com/pages/resource-library) and a schedule of user training [Webinars](https://learn.statsig.com/pages/upcoming-webinars). * **Statsig Status Page**: Monitor Statsig's operational status and configure [Slack alerts](/integrations/slack) to be notified of updates. * **Community**: Join the [Slack Community](https://statsig.com/slack) to connect with other users and get help from the Statsig team. * **Blog**: Stay updated with the latest developments in product experimentation and feature management on the [Blog](https://statsig.com/blog). * **Use Cases**: Discover [Customer Stories](https://statsig.com/customers) to learn why customers love Statsig and how they use it to power their product development. ================================================================================ # Experiments Overview URL: https://docs.statsig.com/experiments/overview Updated: 2026-07-11 ================================================================================ Experimentation is a tool for making data-driven decisions that improve product outcomes and customer experiences. This guide covers key concepts of experimentation: control variables, randomization units, and statistical significance. It helps you understand the science behind A/B testing and multivariate experiments and how to use experiments to validate product changes, discover new opportunities, and drive business impact. {% figure %} ![Experiments banner image](/images/experiments-hero.png) {% /figure %} ## What are experiments Experiments enable you to run randomized controlled trials (A/B or A/B/n tests) to measure the impact of product changes on key metrics. Statsig’s experimentation platform is designed to make it straightforward to create, manage, and analyze experiments, ensuring you ship features that deliver value to your users and business. Experiments are ideal when you want to: * Test multiple variants (A/B or A/B/n) of a product feature. * Run mutually exclusive experiments in parallel. * Measure the direct impact of changes on product and business metrics. *** ## Why experiment Controlled experiments are the most scientifically reliable way to establish causality between your product changes and their effect on customer behavior. By running experiments, you can: * **Validate Hypotheses**: Only ship features that have been proven to improve the customer experience or drive key business metrics. * **Measure Success**: Measure feature performance post-launch and detect any unexpected side effects. * **Drive Innovation**: Experiments give teams real-time feedback on product performance, enabling faster iteration and better, data-driven decisions. Historical metrics may show correlation, but experiments allow you to establish causal relationships. Experiments reduce the influence of uncontrolled external factors, ensuring that observed effects are due to the tested changes. *** ## Key concepts ### Control variables A **control variable** is the variable in an experiment that is manipulated to observe its effect on key metrics. In a simple A/B test, the control variable usually has two values (A and B). More complex experiments may have additional values (e.g., A, B, C, D), known as multivariate experiments. ### Variants A **variant** is a specific version of the product or feature being tested. For example, in an A/B test: * **A (Control)**: Represents the current state of the product or feature. * **B (Treatment)**: Represents the modified state you want to evaluate. Each variant is randomly assigned to users, allowing you to compare their performance. ### Randomization unit The **randomization unit** is the entity (such as a user, device, or session) that is randomly assigned to control or treatment groups in an experiment. Choosing the right randomization unit ensures consistency in user experience and reliable experiment results. This choice is critical to ensure that experiment results reflect real-world user behavior and that data isn't skewed by unintentional crossovers between groups. ### Statistical significance Statistical significance determines whether the observed changes in metrics are likely due to the product change or random variation. Two commonly used methods are: * **p-value**: The p-value measures the probability of observing the results by chance if the variant had no effect. A p-value below 0.05 is typically used to determine statistical significance. * **Confidence Interval**: A confidence interval defines the range in which the true effect of a variant lies, with a given level of confidence (e.g., 95%). If the confidence interval doesn't overlap zero, the effect is considered statistically significant. For more information on designing, monitoring, and analyzing experiments, refer to [Product Experimentation Best Practices](https://statsig.com/blog/product-experimentation-best-practices). *** ## Common scenarios for experimentation ### Optimize product growth Use experiments to refine and optimize user experiences, helping you climb toward a local maximum in your product strategy. Common goals include: * Optimizing a specific user journey (e.g., improving onboarding). * Iterating on features to identify high-return opportunities. * Aligning experiments with business-critical metrics and guardrails to prevent negative side effects on fundamental business needs. ### Explore new opportunities Use exploratory experiments to discover entirely new directions. These experiments help you develop new ideas, validate strategies, and uncover long-term opportunities. * Run experiments over longer durations to account for novelty effects and adoption time. * Slowly ramp up experiments to minimize risk and build statistical power. * Test multiple related hypotheses to explore a broader business strategy. *** ## Choosing the right randomization unit The Randomization Unit is the variable that determines how users are distributed across your groups (for example, test and control). When you set a variable as the Randomization Unit, any value for that variable always receives the same experience. The Randomization Unit is also the reference unit for your metrics. If you choose userID, the userID deterministically buckets each user and serves as the basis of measurement: your analysis might look at Revenue per userID. The following are common units of randomization and when to use them. ### User identifiers The most commonly used randomization unit is the **User ID**. Your application generates this identifier when a user registers or signs in. Using User IDs ensures a consistent user experience across sessions and devices, because the user is always assigned the same variant regardless of where or when they access the product. Advantages: * Persistent across sessions and devices. * Independent of client-side cookies, which can be cleared by users. For more details on using User IDs with Statsig, refer to [Statsig Docs on User Identifiers](/sdks/user). ### Device identifiers For users who haven't registered or signed in, using **Device IDs** or **Anonymous User IDs** is common. These identifiers track users based on their device and are ideal when experimenting with unregistered or guest users. Example: * You can use device IDs to experiment on landing page optimizations aimed at improving user registration rates. **Drawbacks**: * Device-specific: If the same user accesses your app from multiple devices, they may have different experiences. * Shared devices: If multiple users share a device, the experiment may mistakenly treat their behavior as belonging to one individual. Statsig SDKs automatically generate **Stable IDs** for anonymous users, making it easier to manage device-based experiments. For more details, refer to [Statsig Guide for Device-Level Experiments](/guides/first-device-level-experiment). ### Session identifiers In certain cases, you may use **Session IDs** as the unit of randomization, particularly when testing behavior during a single session (for example, optimizing a checkout flow). Session-based randomization assumes each session is independent of others. This assumption may not hold if users return in multiple sessions. Example: * Session IDs might be used when experimenting with conversion funnels for guest checkouts, which are typically completed within a single session. **Drawbacks**: * Users may remember their experience from one session to another, undermining the assumption of session independence. * If users return in future sessions, they may be placed in different variants, leading to inconsistent user experiences. *** ## Tutorials * [Run your first A/B test](/guides/abn-tests) * [Create an experiment using a userID](/experiments/create-new) * [Create an experiment using a customID](/guides/experiment-on-custom-id-types) * [Use a language specific Statsig SDK to implement an experiment in your application](/experiments/implementation/implement) * [Monitor an experiment](/experiments/monitor) ================================================================================ # Create an Experiment URL: https://docs.statsig.com/experiments/create-new Updated: 2026-07-11 ================================================================================ {% callout type="tip" %} Metrics and experiments behave differently in Warehouse Native. Read about [Configuring Experiments in Warehouse Native](/statsig-warehouse-native/features/experiment-options). {% /callout %} This doc walks through the steps of creating a new experiment in the Statsig console. For an end-to-end guide that includes integrating the Statsig SDK, go to [Run your first experiment](/guides/abn-tests). ## User-level experiments To create a user-level experiment, follow these steps: 1. Log into the Statsig console at [https://console.statsig.com/](https://console.statsig.com/) 2. Navigate to **Experiments** in the left-hand navigation panel 3. Click on the **Create** button 4. Enter the name and description for your experiment as shown in the figure below 5. By default, your experiment runs in its own **Layer**. A Layer allows you to manage multiple experiments and feature flags together. To add this experiment to an existing Layer, select **Add Layer** under **Advanced** in the experiment creation modal. To create a new Layer, select **Create New Layer**. 6. Click **Create** {% figure %} ![Experiment creation modal interface](/images/experiments/create-modal.png) {% /figure %} ## Configure your scorecard When running an experiment, it’s common to test a specific hypothesis using a set of key metrics. The **Scorecard** feature makes this easy by letting you enter your hypothesis and select both primary and secondary metrics. * **Primary Metrics** are those you expect to be directly impacted by the experiment. * **Secondary Metrics** are important to monitor to ensure there are no unintended side effects, but they aren’t the primary focus of your experiment. Configuring the Scorecard is a required step when creating an experiment. It provides your team with clear context on what is being tested and how success is measured. You must enter your hypothesis and select at least one primary metric. Metrics added to the Scorecard are computed daily and eligible for advanced treatments like [CUPED](/experiments/statistical-methods/methodologies/cuped) and [Sequential Testing](/experiments/advanced-setup/sequential-testing). For best practices on configuring your Scorecard, go to [Read Results](/experiments/interpreting-results/read-results). {% figure %} ![Scorecard configuration modal](/images/experiments/scorecard.png) {% /figure %} ## Configure allocation and targeting The Allocation and Targeting section is where most of your experiment configuration happens. ### Allocation For **Allocation**, enter the percentage of users you want to assign to this experiment. You can allocate up to 100% of eligible users. It’s good practice to start with a smaller percentage, verify the experiment’s stability, and then increase the allocation. {% figure %} ![Experiment allocation configuration interface](/images/experiments/allocation.png) {% /figure %} You can increase the allocation of your experiment anywhere from 0% to 100% at any time after experiment start; however, you can't decrease allocation without resetting your experiment, as this would cause biases in group allocation and pollute your metric results. ### Targeting To configure **Targeting** criteria, click to edit the **Targeting** section. You can either set new targeting criteria or use an existing **Feature Gate**. This limits the experiment to only the users who meet the defined conditions. {% figure %} ![Experiment targeting configuration interface](/images/experiments/targeting.png) {% /figure %} * For basic targeting criteria, creating it through Inline Targeting works well. (Select "Criteria: Everyone" to get started.) * For more advanced targeting (for example, progressive rollouts) or if you want to maintain targeting criteria when you launch your experiment, reference an existing **Feature Gate**. By default, no targeting criteria are set, so your experiment includes all allocated users within the defined **Layer** or exposed user base. ## Configure your groups and parameters When configuring **Groups and Parameters**, define your parameters first. These are the variables that control the behavior of the different experiment variants. * Enter the values the experiment parameter takes for each variant. For more about the difference between **Groups** and **Parameters**, go to [Groups vs. Parameters](/experiments/implementation/getting-group). You can add additional groups by clicking the "+" next to the existing groups. The user allocation automatically adjusts as you add more groups. {% figure %} ![Experiment groups and parameters configuration interface](/images/experiments/groups.png) {% /figure %} You can also name, describe, and add variant images for each group under the **Groups** section. Only the parameters and values affect what users see; group names and descriptions aren't used in the experiment code. ## Device-level and custom ID experiments By default, experiments randomize users based on **User ID**. If you need to use a different ID type (for example, device-level), follow steps 1–4 from the "User-level Experiments" section, then: 1. Click the **ID Type** dropdown menu and choose the ID type you need. 2. Click **Create** {% figure %} ![ID type selection dropdown interface](/images/experiments/id-type.png) {% /figure %} Afterward, continue with the same steps described above to finish configuring the experiment. ## ID mapping capabilities When running experiments, you may want to start with one ID type (like stableID for device-level targeting) but analyze results using events from another ID type (like userID for logged-in user metrics). **Warehouse Native**: Supports ID mapping between different identifier types (for example, stableID to userID) through Entity Property Source configuration. **Cloud**: Does not currently support mapping between different ID types. Experiments started with stableID only analyze events with stableID, and experiments started with userID only analyze events with userID. For advanced ID mapping requirements, consider using Statsig Warehouse Native. ## Isolated experiments If you want to create an experiment that excludes users exposed to other experiments, follow steps 1–4 from the "User-level Experiments" section. Then: 1. Select **Advanced** options. 2. Select an existing **Layer** or create a new one. 3. Click **Create**. {% figure %} ![Isolated experiment layer configuration interface](/images/experiments/isolated-layer.png) {% /figure %} Now, complete the rest of the experiment setup as described above. ## Reusing experiment salts The Statsig SDKs use [deterministic hashing](/sdks/how-evaluation-works) to bucket users. The same user evaluated for the same experiment is bucketed identically, regardless of where evaluation occurs. Every experiment has its own unique salt so that each experiment's assignment is random. For advanced use cases (for example, a series of related experiments that need to reuse the control and test buckets), Statsig exposes the ability to copy and set the salts used for deterministic hashing. This feature is intended for careful use and is available only to Project Administrators. It is available in the Overflow (...) menu in Experiments. ## Significance level adjustments By default, Experiment Results display with 95% confidence intervals and without Bonferroni correction. You can customize this during experiment setup or later when viewing results in Experiment Results. * **Bonferroni Correction:** Apply this to reduce the risk of false positives in experiments with multiple test groups. The significance level (*α*) is divided by the number of test variants. * **Default Confidence Interval:** Choose a lower confidence interval (for example, 80%) if you prefer faster results with higher tolerance for false positives, or use 95% for greater certainty. {% figure %} ![Significance level adjustment settings interface](/images/experiments/significance-settings.png) {% /figure %} ## Target duration Setting a target duration is optional, but it helps ensure that you wait long enough for the experiment to reach full power. You can set the target as either a specific number of days or a number of exposures, and use the [**Power Analysis Calculator**](/experiments/power-analysis) to determine what target works best for your metrics. {% figure %} ![Target duration setting interface](/images/target_duration_setting.png) {% /figure %} **Target durations longer than 90 days:** By default, Statsig computes Experiment Results for the first 90 days, though the experiment itself can run longer. Before setting a duration beyond 90 days, consider whether results past that period will still be relevant, and whether earlier data might already provide the insights you need. {% figure %} ![Hypothesis Advisor screenshot](/images/experiments/hypothesis-advisor.png) {% /figure %} After you set the target, you can track progress against the target duration/exposures in the experiment header. You also receive notifications through email and Slack (if integrated) when the target is reached. *** ## Hypothesis Advisor Writing good experiment hypotheses is key to a strong experimentation culture. Statsig gives instant feedback on experiment hypotheses, flagging what’s missing. Admins can set custom requirements, which Statsig uses to guide experimenters toward stronger, more complete hypotheses. This Statsig AI feature is disabled by default. Enable it from *Settings > Experiment > Project > Statsig AI*. This is also where you configure any custom requirements you want Hypothesis Advisor to enforce (for example, "Strongly recommend that a validation plan be mentioned"). {% figure %} ![Setting showing how to enable Statsig AI](/images/ai/hypothesis-advisor-enable-settings.png) {% /figure %} ================================================================================ # Power Analysis URL: https://docs.statsig.com/experiments/power-analysis Updated: 2026-07-11 ================================================================================ ## What is power analysis The Statsig Power Analysis Calculator helps determine the experiment parameters needed to reach a statistically significant result before an experiment runs. The most common variable to optimize is duration (how long your experiment runs), but the power calculator supports other variables as well. Using the known mean and variance of a metric and the observed traffic volume, the Power Analysis Calculator estimates the relationship between three variables: - **Minimum detectable effect (MDE)**: The smallest change in the metric that the experiment can reliably detect. For example: an MDE of 1% with Power set to 80% means that if there's a true effect of 1% on the metric, the experiment has an 80% chance to produce a statistically significant result. If the magnitude of the true effect is smaller than 1%, the experiment is less likely to produce a statistically significant result (though it can still occur). - **Number of days or exposures**: How long the experiment is active and the number of users enrolled in it. Longer running experiments typically have more observations, leading to tighter confidence intervals and smaller MDE. We use historical data to estimate the number of new users that would be eligible for the experiment each day. - **Allocation**: The percentage of traffic that participates in the experiment. Larger allocation leads to smaller MDE, so allocating as many users as possible is often preferable for faster or more sensitive results. When there's a risk of negative impact or a need for mutually exclusive experiments, it's useful to know the smallest allocation that can achieve your target MDE. Many experimentation practitioners use the Power Analysis Calculator during setup of every experiment. ## Using the power analysis calculator The Power Analysis Calculator is accessible from the tools menu. It is also linked on the experiment setup page, below the Experiment Duration field. {% figure %} ![Power Analysis entry points](/images/experiments/power-analysis/navigation.png) {% /figure %} 1. Select the population used to determine the metric mean and variance and to estimate the number of exposures over time. - **Everyone**: Analysis is based on the entire user base. - **Targeting gate**: Analysis is scoped to the set of users who pass the selected feature gate, which must have been active for at least 7 days. Choose this option when you plan to use a targeting gate for the experiment. - **Past experiment**: Analysis is based on data collected from a previous experiment. Use this option when the new experiment impacts a similar user base or part of the product as the previous one. - **Qualifying event**: Analysis is scoped to the set of users who logged the event specified. 2. Select a metric of interest (or multiple metrics for a targeting gate analysis) 3. Click on **Run Power Analysis** to calculate results {% figure %} ![Power analysis form inputs](/images/experiments/power-analysis/form-inputs.png) {% /figure %} Your past power analysis calculations are available in the "Past Analyses" tab. {% figure %} ![Past power analyses list](/images/experiments/power-analysis/past-analyses.png) {% /figure %} To attach an existing power analysis to an experiment, use the dropdown menu on an existing power analysis. {% figure %} ![Power analysis attachment interface](/images/experiments/attach-power-analysis-to-experiment.png) {% /figure %} ## Population types The population selected directly determines the inputs of the analysis (mean, variance, number of users). To obtain reliable power analysis estimates, the metric values of the selected population should roughly match those of the users you plan to target in the experiment. ### Example Suppose you want to test a change in the checkout flow and determine your expected MDE for total_purchases. If only approximately 10% of your daily users reach the checkout page and you use the _Everyone_ population for your analysis, you are likely to: - Overestimate the number of users the experiment gets. - Underestimate the mean value of the total_purchases metric. The 90% of users that don't reach the checkout page have a value of zero, but in practice they won't be in the experiment and won't contribute to the metric. - Incorrectly estimate the variance in the total_purchases metric. The distribution of metric values changes when you include the 90% of users with zero purchases because they never reached the checkout page. When an experiment includes only a biased subset of users, the MDE and duration from the power analysis may not be a reliable estimate. One way to address this is to use data from a past experiment to estimate the power of a new, similar experiment. For example, if a prior experiment also targeted the checkout page, that data provides better estimates of traffic volumes and metrics for that part of the product. ### Inputs by population type The following table shows how inputs for the power analysis are obtained from the different population types: | Population | Mean and Variance Calculation | Total Exposures by Week Estimate | | ---------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Everyone | Mean and variance across all users, estimated for 1, 2, 3, and 4 week rollups | Total count of users seen in the past 1, 2, 3, and 4 weeks | | Targeting Gate | Mean and variance for users that pass the targeting gate, computed for 1, 2, 3, 4 week rollups | Total users that passed the targeting gate after 1, 2, 3, 4 weeks | | Past Experiment | Cumulative mean and variance for the control group at 1, 2, 3, and 4 weeks | Total experiment exposures after 1, 2, 3, and 4 weeks, adjusted according to the past experiment's allocation and the target allocation for the new experiment. | | Qualifying Event | Mean and variance for users who logged a specified event, computed for 1, 2, 3, 4 week rollups | Total users who logged a specified event after 1, 2, 3, 4 weeks | ## Analysis types When viewing results, you can modify inputs such as "# of Groups", "Control Group %", and Analysis Types to update the power analysis results. {% figure %} Power analysis results showing adjustable parameters {% /figure %} ### Fixed allocation analysis If you already know the available allocation, fixed allocation analysis shows how the length of the experiment affects the MDE. The example below shows how the MDE for a page load metric shrinks over time in an experiment with 100% allocation. After 1 week, the expected user count per group is 5,200 with an MDE of 21.6%. By week 4, the user count per group increases to approximately 48,000 and the MDE is reduced to 7%. {% figure %} ![Fixed allocation analysis results chart](/images/experiments/power-analysis/fixed-allocation.png) {% /figure %} ### Fixed MDE analysis If you already know the effect size you want to measure, fixed MDE analysis shows the allocation and duration needed to achieve that MDE. Enter your target MDE as a percentage of the current metric value. For example, if a website gets 1,000 page loads per day, an MDE of 10% means a change of 100 or more page loads per day can be detected. The results show the minimum number of weeks needed to reach this MDE for different allocation percentages. In the example below, the experiment must run for at least 2 weeks with 65% allocation or 4 weeks with 50% allocation. The target MDE can't be achieved in 1 week, because doing so would require more than 100% allocation. {% figure %} ![Fixed MDE analysis results table](/images/experiments/power-analysis/fixed-mde.png) {% /figure %} ## Advanced options {% figure %} ![Advanced power analysis options interface](/images/experiments/power-analysis/advanced-options.png) {% /figure %} Advanced settings for customizing the analysis: - **Number of Experiment Groups**: The total number of groups in the experiment, including control. - **Control Group %**: What percent of users are in the control group, e.g. 50% if half of all users are control. - **Fixed Allocation or Fixed MDE Analysis**: Different types of analyses you want to run. Refer to [analysis types](/experiments/power-analysis#analysis-types) for more details. - **One-sided or Two-sided test**: Toggle this setting to select the type of z-test to use for the analysis. - **Significant Level (α)** - **Power (1-β)** - **Bonferroni Correction Per Variant**: Whether to include α correction for multiple tests in power analysis. ## Calculation details The relative percentage MDE for a given metric _X_ is computed using the following equation: {% figure %} ![MDE calculation formula](/images/experiments/power-analysis/mde-formula.png) {% /figure %} - _X-bar_ is the mean metric value across all users - _var(X)_ is the population variance of the metric - _Ntest_ and _Ncontrol_ are the estimated number of users in the test and control group. These are based on historical active user data along with experiment allocation and group size. - _Z1-β_ is the standard Z-score for the selected power. Typically _1-β = 0.8_ and _Z1-β = 0.84_ - _Z1-α/2_ is the standard Z-score for the selected significance level in a 2-sided test. Typically _α = 0.05_ and _Z1-α/2 = 1.96_ {% callout type="note" %} This calculation relies on statistics computed across the entire user base of the project. It doesn't account for experiments that target only a subset of users, which may have different summary statistics for their key metrics. For example, the metric mean and variance can differ in an experiment that targets only Android users or one that exposes users at the lower part of an acquisition funnel. {% /callout %} ================================================================================ # Implement an Experiment URL: https://docs.statsig.com/experiments/implementation/implement Updated: 2026-07-11 ================================================================================ To deploy an experiment, you need to: 1. Pull the experiment configurations in your application 2. Log the events you want in your experiment results 3. Test your experiment in development or a lower environment 4. Click "Start"! Every experiment needs to expose users into more than one bucket (#1) and log metrics on their behavior after exposure (#2, called "log events"). Statsig automates the setup steps, such as generating the bucket-assignment code and running analysis on exposures and log events. The experimenter's job is to design the experiments and use the Statsig SDKs to accomplish steps 1 and 2. ## Pulling experiment configurations from Statsig The code snippets below illustrate experimenting on a product demo flow, where you might experiment to improve conversion through the funnel to demo completion. For full implementation details, go to the [SDK documentation](/sdks/getting-started) for the language you are using, or go through the example guide for [your first A/B test](/guides/abn-tests). ```js const user = { userID: loggedInUserID }; const demoConfiguration = statsig.getExperiment(user, "demo_experience"); // use parameters to control the experience if (demoConfiguration.get("show_banner", false) { showBanner(); } const title = demoConfiguration.get("title", "Start Demo"); banner.setTitle(title); ``` You can also view a code snippet for your particular experiment by selecting the code snippet button on the experiment page and choosing the right SDK. {% figure %} ![experiment code snippet button](/images/experiments/code-snippet-button.png) {% /figure %} ## Logging events for your scorecard To get experiment results for the events and metrics you care about, instrument the experience with the proper event logging (or set up an event integration or data warehouse import to send events to the Statsig experimentation stats engine). Using the Statsig SDKs, your code might look like this: ``` statsig.logEvent(user, "demo_started"); ... statsig.logEvent(user, "demo_completed"); ``` A few events can help you measure how users move through a funnel in your product and enable you to experiment on those flows to increase conversion. ## Testing in a lower environment After an experiment is launched, you can't edit the groups without restarting the experiment, because users are already being allocated to each group. Statsig recommends testing each experiment in lower environments before starting. Select the "Test" button in the experiment setup page, then select "Enable for Environments". These environments should match your [SDK environment setup](/guides/using-environments/#configuring-environments). Testing in a lower environment and using [overrides](/experiments/setup/overrides) lets you manually set your experiment group to test each variant. {% figure %} ![Experiment test button interface](/images/experiments/experiment_test_button.png) {% /figure %} After the experiment is enabled for a lower environment, the experiment status shifts from “Not Started” to “Testing”. {% figure %} ![Lower environment resalt](/images/experiments/lower_environment_results.png) {% /figure %} In the results section, you can track cumulative exposures and metric results collected from lower environments. Statsig displays results in aggregate across all lower environments and doesn't distinguish between individual environments. Statsig retains metric data and exposure data even when the experiment is repeatedly disabled and re-enabled for the lower environment. You can resalt the experiment in lower environments. This is useful when you need to re-test the experiment end-to-end. {% figure %} ![Lower environment resalt](/images/experiments/lower_environment_resalt.png) {% /figure %} ## Starting your experiments After your experiment has metrics, parameters, and a hypothesis, and you have tested it in a lower environment, select the "Start" button. Your experiment is immediately live in Production. ================================================================================ # Experiment Overrides URL: https://docs.statsig.com/experiments/setup/overrides Updated: 2026-07-11 ================================================================================ ## Override group allocation for an experiment During development, explicitly stating which experiment group a user falls into can be useful. For example, if a developer is testing that each experiment condition works as expected, they might want to force themselves into each condition. You can add overrides based on Feature Gates and Segments to create rules that force all users who pass a given Feature Gate or Segment into a given group. When fetching parameters for an experiment, if the user matches any override, Statsig returns the overridden result immediately. {% callout type="info" %} **Note**: * For experiments that are in a layer, set the overrides at the layer level. Statsig can only override a user into one experiment within that layer (since layers always make experiments within them mutually exclusive). * Add overrides before the experiment starts, or at least before a user's first exposure. Statsig excludes users whose first exposures are controlled by overrides from Pulse results, because these users aren't randomized. Adding a large number of users to the override can affect the reliability of your experimental results. * **Warning**: if you add overrides after a user has already been exposed in an experiment, Statsig doesn't exclude that user from Pulse results. Statsig continues to attribute that user's events and metrics to the group they were first assigned to. However, Statsig applies and honors the override after you define it. This can dilute or pollute your experiment results, because Statsig attributes the user's actions to one group while they were actually exposed to a different group. The overall impact depends on how many users fall into this category. * Statsig evaluates ID overrides first, then conditional overrides (from top to bottom). * ID type used for ID Overrides may or may not match the experiment’s ID type. {% /callout %} {% figure %} ![overrides modal](/images/overrides-modal.png) {% /figure %} ### Adding an override * Log into the Statsig console at https://console.statsig.com * On the left-hand navigation panel, select **Experiments** * Select the experiment where you want to add an override, and navigate to the *Setup* tab * Click **Manage Override**, configure the override, and select **Save** {% figure %} ![overrides modal](/images/experiments/overrides-entry.png) {% /figure %} ### Deleting an override If you add an override but later decide it isn't needed, you can remove it so Statsig evaluates the rules as normal. * Log into the Statsig console at https://console.statsig.com * On the left-hand navigation panel, select **Experiments** * Select the experiment or layer from which you want to delete the override * Click **Manage Overrides** * Click on the trash can icon next to the override you want to delete, and select **Save** ### Testing an override After adding your override, test it in the "Check Group for a User" window by navigating to the **Diagnostics** tab and adding the properties required for a user to pass the gate or segment set as an override. ================================================================================ # Getting the Group URL: https://docs.statsig.com/experiments/implementation/getting-group Updated: 2026-07-11 ================================================================================ A common misconception when working with experiments on Statsig is trying to check the experiment group in code. Checking the experiment group in code is an anti-pattern. It isn't necessary with Statsig, and it limits your ability to quickly test different variants. Experiment groups are useful for understanding what a set of parameters represents in the Statsig console. Comparing "Sorted Long List" vs "Default Search Results" is easier to discuss than understanding what the `sorted = true, length = 10` parameters represent. In code, checking parameters directly is more powerful and simpler to reason about. Rather than hard-coding a particular value, your variable is dynamically evaluated by Statsig. Parameters are the building blocks of your experiments in code, rather than group names. ### Example: group names vs parameters Hard-coding experiment group names is fragile and limiting. Here is why. In code, a function using experiment groups might look like this: ```ts async function getSearchItems(user: StatsigUser, searchTerm: String): String[] { const results = index.get(searchTerm); const experiment = statsig.getExperimentSync(user, 'search_results'); // NOTE - these APIs don't actually exist - this is for the sake of an example if (experiment.groupName === 'Sorted Long List') { return results.sort().slice(10); } else if (experiment.groupName === 'Sorted Short List') { return results.sort().slice(3); } else { return results; } } ``` There are two problems with this code: 1. It is fragile. If the group name in code doesn't match the name in the Statsig console, the correct experience isn't returned. 2. It is static. Adding another experiment group, such as an "Unsorted long list", requires a code change. So instead, this is what the code would look like using experiment parameters directly: ```ts async function getSearchItems(user: StatsigUser, searchTerm: String): String[] { let results = index.get(searchTerm); const experiment = statsig.getExperimentSync(user, 'search_results'); results = experiment.get("sorted", false) ? results.sort() : results; const numItems = experiment.get("length", 0); return numItems > 0 ? results.slice(numItems) : results; } ``` Your code is now completely decoupled from the names of experiment groups in the Statsig console. You are left with a set of dynamic parameters. You can create any experiment groups from these building blocks and the same code works. To test an unsorted list of 5 items against a sorted list of 20 items, configure it in the Statsig console and name the groups however you want. If you were using group names instead, you would need to add conditions like: ```ts } else if (experiment.groupName === 'Unsorted Short List') { return results.slice(5); } else if (experiment.groupName === 'XL Sorted List') { return results.sort().slice(20); } ``` Using parameters directly is much simpler and more flexible. It makes your code dynamic and offloads experimentation setup to the Statsig console. The group names you configure to describe each set of parameters make it easy to compare one group against another when analyzing experiment results. ## Rules The diagnostics stream is for debugging your integration and understanding which groups a user is being assigned to. The following table defines the Rules you see and what they mean. | Rule | Meaning | |---|---| | Not started | The experiment hasn't been started, so the allocation groups aren't determined yet | | Holdout | The user is in a holdout that this experiment references, so they aren't in the experiment | | Layer Assignment | The user isn't allocated to this experiment because they are bucketed to a different part of the Layer | | Targeting Gate/Inline Targeting | The user doesn't meet the requirements for the targeting gate or inline targeting | | Not Allocated | The user isn't allocated to this experiment because they don't meet the rollout % | | \{group name}\{override name} | The user was forcefully bucketed into a given group by an experiment override | | \{group name} | The user was bucketed into this experiment group | | Abandoned | "Make Decision" selected the control group. This experiment has been abandoned. | | \{group name} (Launched) | "Make Decision" selected this group as the launch group, so the user is seeing the launched experience. | ================================================================================ # Monitor an Experiment URL: https://docs.statsig.com/experiments/monitor Updated: 2026-07-11 ================================================================================ After an experiment launches, you can monitor its health and exposure mix directly from the Statsig console. ## Experiment health checks 1. Open **Experiments** from the navigation. 2. Select the experiment you want to inspect. 3. Review the **Experiment Health Checks** banner at the top of the scorecard. {% figure %} ![Experiment health checks showing status icons](/images/experiments/monitor/health-checks.png) {% /figure %} Hover a status icon to read the summary, then click for full context. Common health checks include: * **Checks started** - Verifies the SDK is reporting config checks shortly after launch. * **Checks have valid unit type** - Confirms checks include the configured unit identifier (userID by default). * **Event metrics have data** - Ensures events carry the same unit ID as exposures so Pulse can compute metrics. This often surfaces when downstream tooling (e.g., Segment) omits stableID or custom IDs. * **Pulse metrics available** - Indicates Pulse results have landed (typically the day after launch). * **Exposures are balanced** - Runs a chi-squared test for sample ratio mismatch (SRM). Occasional warnings happen due to randomness, but persistent red alerts point to assignment or logging issues. * p-value between 0.001 and 0.01 -> Warning (yellow). * p-value \< 0.01 with \<0.1% absolute deviation -> Warning (yellow) with low expected impact. * p-value \< 0.001 and >=0.1% deviation -> Alert (red) requiring investigation. * **Crossover units detected** - Flags users exposed to multiple variants. Statsig Cloud keeps these users in both groups (since the SDK rarely produces crossovers) but highlights them so you can address root causes. Reach out if you see rates above 1%. * **Default value type mismatch** - Warns if an experiment's fallback default value type disagrees with the parameter definition. * **Group assignment healthy** - Surfaces unexpected assignment reasons (e.g., `Uninitialized`, `InvalidBootstrap`). Click **View Assignment Reasons** to see the hourly breakdown. {% figure %} ![Assignment reasons breakdown chart](/images/experiments/monitor/assignment-reasons.png) {% /figure %} ## Crossover troubleshooting Crossover warnings usually mean: 1. The request bootstrapped with a different stable ID (`BootstrapStableIDMismatch`). 2. Both client and server SDKs are checking the same gate/experiment without synchronized updates. If you can't pinpoint the cause, contact Statsig in Slack for help. ## Exposure streams Scroll below the health checks to view exposure streams. These tables show every recent check, including the rule that matched and any secondary exposures (holdouts, targeting gates, etc.). They are useful for validating targeting and confirming ramp progress. ## Cumulative exposures To track growth per variant: 1. Open the **Results** tab. 2. Locate the **Cumulative Exposures** chart. {% figure %} ![Cumulative exposures chart](/images/experiments/monitor/cumulative-exposures.png) {% /figure %} The chart shows how many users have entered each group over time, making it easy to spot ramp issues early. Monitoring these diagnostics helps you resolve issues quickly and keep experiments on track. ================================================================================ # Experiment Quality Score URL: https://docs.statsig.com/experiments/setup/quality-score Updated: 2026-07-11 ================================================================================ ## How experiment quality score works The Experiment Quality Score is a metric that provides a quick measure of the quality and trustworthiness of an experiment configured in Statsig. The score helps experimenters and their peers quickly identify potential issues in experiment setup, execution, and data collection, enabling more confident decision-making. Measuring this score across all experiments helps teams discover systematic issues and identify opportunities to improve their experimentation program over time. ## Configure experiment quality score To enable the Experimentation quality score, go to *Settings > Experimentation > Experiment Quality Score* in the project settings. Statsig evaluates experiments against a list of pre-defined assessment criteria. You can customize the weight of each criterion based on your organization's needs, though Statsig provides default values. {% figure %} ![Experiment quality score configuration interface](/images/snippets/quality-score/91fea3df-0dae-4d0a-ada8-1c1c9313e60e.png) {% /figure %} ## Advanced configuration For organizations with more complex requirements, you might need additional checks, different requirements per product team, or thresholds that differ from the defaults. For example, hypotheses might need to be at least 200 characters and contain a link to an external planning doc. To manage these requirements, use the Statsig console API. Run a POST or PATCH on the `console/v1/experiments` endpoint to update individual scores on any experiment. Targeting the existing set of scores lets you override weights (usually to 0), so the list contains only the custom set you need. For example, running patch on an experiment with this payload: ``` { "manualQualityScores": [ { "criteriaName": "HYPOTHESIS_LENGTH", "criteriaDescription": "Check passed", "status": "PASSED", "score": 0, "weight": 0 }, { "criteriaName": "MyCompany\'s Hypothesis Check", "criteriaDescription": "Has Internal URL and > 200 Chars", "status": "PASSED", "score": 100, "weight": 100 }, { "criteriaName": "Naming", "criteriaDescription": "Experiment prefixed with team name", "status": "FAILED", "score": 0, "weight": 100 } ] } ``` This payload would: * Drop the original HYPOTHESIS\_LENGTH check * Keep the other original checks, with their weights * Add a new check, `MyCompany's Hypothesis Check`, for custom logic on the hypothesis * Add a new check, `Naming`, for custom logic on the name Statsig normalizes the other weights. If the original HYPOTHESIS\_LENGTH had a weight of 10, the total weight would be 290 and scores normalize accordingly. If all non-custom checks were passing, the score would be 190/290 or ~66%. The general flow for using this approach: * Use the Console API's `experiments/get` to pull all experiments * For each experiment: * Run custom logic * Patch results ## How scores are calculated Statsig skips checks in an unready state during evaluation and renormalizes the other weights to 100%. For example, if the experiment hasn't started, the `Balanced Exposures` component is in an unready state and ignored. Statsig omits checks with a weight of 0 from the card entirely. ## Viewing quality scores When enabled, quality scores appear in the details tab of an experiment. Statsig evaluates each applicable check and contributes it to the displayed score. Statsig color-codes the score based on the threshold it reaches. * \>= 85% corresponds to passing/green * \>= 50% corresponds to warning/yellow * \< 50% corresponds to error/red. {% figure %} ![Experiment quality score display with color-coded status](/images/snippets/quality-score/d7483b96-8077-419d-9e23-a3e648b7e066.png) {% /figure %} Quality scores are also available through the console API, which you can use for bulk data retrieval and analysis. ================================================================================ # Switchback Tests URL: https://docs.statsig.com/experiments/types/switchback-tests Updated: 2026-07-11 ================================================================================ ## What is switchback testing? Switchback tests are an alternative experiment form in which an entire population switches back and forth between test and control treatments on a set cadence, rather than being split and evenly divided between test and control for the duration of the experiment. Switchback tests are particularly common in marketplaces. Running a traditional A/B test on one side of the marketplace can have unintended consequences on the rest of the marketplace due to network effects, which affect experiment results. Another common use case for switchbacks is when applying different variants to different users isn't feasible for fairness, legal, or logistical reasons. Switchback tests are often carried out across multiple "buckets", typically regions or other defined groups that are flipped between test and control treatments over the course of the experiment. ### Example Consider a rideshare platform that wants to test pricing. The initial approach splits riders into two groups: one with a higher price and one with a lower price. Riders with the lower price request rides at a significantly higher rate, consuming all available driver supply in a given area. This leaves riders with higher prices facing not only a higher ride estimate but also longer ETAs, making them even less likely to request a ride. The experiment results become unclear: the decreased ride request rate in the higher-price group could be caused by the higher prices or by the longer ETAs. The experimental design has introduced bias into the results. A switchback test resolves this. Instead of splitting users, 100% of riders and drivers in a given metro switch in and out of the new pricing plan hourly. The test then measures the impact on overall ride request rates during hours when prices were higher versus lower. ## Switchback testing on Statsig ### Methodology The switchback testing methodology for computing results consists of 3 steps: 1. Attribute events to the corresponding switchback bucket, where each bucket is defined by the time window and grouping attribute. 2. Calculate the variant-level and bucket-level metrics based on the attributed events. 3. Calculate the difference in means between test and control. Use bootstrapping to obtain the confidence intervals. ### Event attribution Attribution of events to a particular bucket is based on the timestamp and unit_id of the exposure, the length of the attribution window, and the timestamp of subsequent events for that unit_id. For example: User 123 is exposed to bucket A at 9:15 AM. The test has an attribution window of 90 minutes. All events triggered by user 123 between 9:15 AM and 10:45 AM are included in the metric calculations for bucket A. ### Bucket-level metrics Once Statsig has all the events corresponding to a bucket, it calculates the scorecard metrics derived from these events. {% figure %} ![Bucket metrics table summarizing switchback exposures](/images/experiments/types/switchback-tests/adf0e2b0-0e4c-47b9-8a04-d0f01f130f7c.png) {% /figure %} For sum and count metrics, Statsig uses the mean value per unit exposed to that bucket. {% figure %} ![Metric detail view showing per-unit averages within a bucket](/images/experiments/types/switchback-tests/8f234be9-7848-46fa-a84d-6d6c7facf51b.png) {% /figure %} ### Variant-level metrics Statsig calculates overall metric means for test and control by aggregating values across all buckets in that variant. If there are **M** buckets in the test group, the mean value of a ratio metric is given by: {% figure %} ![Formula showing ratio metric mean calculation across switchback buckets](/images/experiments/types/switchback-tests/8239da03-6f94-47b0-b9b5-39e87566f7fc.png) {% /figure %} The mean of a sum or count metric would be: {% figure %} ![Formula for sum or count metric averages in switchback tests](/images/experiments/types/switchback-tests/220515f6-e0b0-4f64-a441-e5facdde5530.png) {% /figure %} ### Deltas and confidence intervals The treatment effect is calculated as: {% figure %} ![Equation for treatment effect delta between test and control](/images/experiments/types/switchback-tests/1fb290c9-f253-4be0-9bef-b212f74f6e0a.png) {% /figure %} The bootstrapped confidence intervals are obtained as follows: 1. Collect a bootstrap sample with replacement from the set of test buckets and separately from the set of control buckets. 2. Calculate the difference in means between test and control samples. 3. Repeat steps one and two 10,000 times to produce a distribution of metric deltas. 4. The 95% confidence interval is the range from the 2.5% quantile to the 97.5% quantile of that distribution. In general, the confidence interval with significance level $\alpha$ is given by: {% figure %} ![Bootstrap confidence interval formula for switchback tests](/images/experiments/types/switchback-tests/bea68643-3ade-4daf-a6fa-79e39a7274d7.png) {% /figure %} ## Setup To set up a switchback test on Statsig, when you create an experiment tap **Advanced Settings** → **Experiment Type** and select "Switchback Test". {% figure %} Experiment type menu selecting switchback test {% /figure %} Switchback test configuration adds two new aspects to the standard experiment setup: 1. **Targeting**: The defined population(s) you run your experiment on. 2. **Schedule**: The switching frequency and starting treatments for different pre-defined populations. There are two ways to define targeting: - **Targeting Gate**: Specify a targeting gate to define your target experiment population, the same as any other experiment on Statsig. - **Bucketing Method**: Bucket users based on either pre-defined buckets or randomized across an ID type. {% figure %} ![Switchback targeting configuration showing gate and bucketing options](/images/experiments/types/switchback-tests/a3e951fb-9a62-48be-9fec-59fcc2003d17.png) {% /figure %} **Buckets** let you specify pre-defined buckets, such as _Country_, _Locale_, or a _Custom Field_ you log. Use this option when you have a few pre-defined populations you want to switch in and out of Test/Control over the course of the experiment. {% figure %} ![Buckets configuration table listing predefined regions](/images/experiments/types/switchback-tests/e53ea760-91c1-40e3-85fd-3da3d886d1ed.png) {% /figure %} **ID Type** lets you specify an ID type to randomize across. For example, choosing a custom ID such as CityID automatically randomizes different CityIDs across Treatment/Control over the different switchback windows. Use this option when you have a very large or dynamic number of experiment units to randomize across. {% callout type="info" %} Randomized bucketing is an advanced feature. Reach out to the support team, your sales contact, or the [Slack community](https://statsig.com/slack) to have this enabled. {% /callout %} {% figure %} ![Randomized ID bucketing interface selecting custom ID](/images/experiments/types/switchback-tests/3c79d1c1-8b15-401c-9635-6b3d4b08d416.png) {% /figure %} Depending on which bucketing method is selected, the **Schedule** section of experiment setup lets you configure: - Start time - Duration (in days) - Assignment window size (in minutes) - Burn-in/ burn-out periods (in minutes) - _(Pre-defined bucketing only)_ Starting phase (treatment group) for each bucket {% figure %} Switchback schedule editor specifying assignment windows and bucket starting phases {% /figure %} Burn-in/burn-out periods let you define time intervals at the start and end of your switchback windows to discard exposures from analysis. Use these when there are risks of bleed-over effects from the previous treatment while a population is switching between test and control. ## Reading results Diagnostics and Pulse metric lift results for switchback tests resemble Statsig's traditional A/B tests, with a few differences: - **No hourly Pulse:** Because a switchback experiment starts with all-Test or all-Control exposures, hourly Pulse is disabled until there is a meaningful amount of data. Use the **Diagnostics** tab in the meantime to verify checks are arriving and bucketing as expected. - **No time-series:** The Daily and Days Since First Exposure time-series aren't available for switchback tests. The bootstrapping methodology requires pooling all available days together to achieve sufficient statistical power. - **No dimension breakdown:** Breaking down a metric by user property or event property isn't available for switchback tests. - **Advanced statistical techniques:** CUPED and Sequential Testing aren't yet available for switchback tests. {% figure %} ![Switchback experiment pulse results showing bucket-level metrics](/images/experiments/types/switchback-tests/657cce2d-22b0-41cf-8b43-2d3f05cfefb8.png) {% /figure %} ================================================================================ # One-Sided Test URL: https://docs.statsig.com/experiments/statistical-methods/methodologies/one-sided-test Updated: 2026-07-11 ================================================================================ ## One-sided tests (aka one-tailed test, non-inferiority test) A one-sided test lets you test for a metric moving in only one direction, which you specify in advance. This trade-off gives you additional sensitivity (or power) compared to the standard Pulse results, which show two-sided results by default. Use cases for one-sided testing include detecting regressions in guardrail metrics and testing for a change where only one direction has meaningful business impact. For example, you may not care whether a new feature reduces crash rates, but you do care whether the new feature increases crash rates. In that case, you are willing to forgo detecting the reduction in favor of better detecting the increase. {% callout type="info" %} One-sided tests don't detect metric movement in the unspecified direction. They give higher sensitivity in the direction of interest by allocating all alpha to that direction. This results in one-sided confidence intervals (CIs) that are narrower in the direction of interest than their two-sided counterparts. {% /callout %} ## How to enable one-sided tests When setting up an experiment and identifying metrics to measure, the default setting is to run a two-sided test. To change this, click the metric name on the experiment setup screen. A popup opens where you can modify the test type and indicate the direction you want to measure. {% callout type="note" %} V1 doesn't support Bayesian testing yet. {% /callout %} {% figure %} ![One-sided test configuration interface](/images/snippets/stats-methods/one-sided-test/23044f21-6249-4fc1-9895-22111bb16010.png) {% /figure %} ## How to read one-sided test results Metrics using one-sided tests appear in Pulse similarly to two-sided tests. The difference is that Pulse shows a one-sided CI rather than a two-sided CI. One-sided CIs extend to infinity or negative infinity, because Statsig only detects changes in the specified direction. As with all CIs, the one-sided CI indicates that the true mean value of the metric likely falls within that range. Because the CI is wide on the unbounded side, you can also interpret the result as high confidence that the mean doesn't fall outside the CI. {% figure %} ![One-sided confidence interval visualization](/images/snippets/stats-methods/one-sided-test/7ab3d680-b4c4-4321-b1fc-cc506b0ab21b.png) {% /figure %} ## FAQ #### Why can't I run two one-sided tests? Running two one-sided tests results in a less powerful test. One-sided tests work by allocating all Type I error (alpha) to one direction. Adding a second one-sided test in the other direction reintroduces the chance of a Type I error in that direction. This produces confidence intervals that appear tighter than warranted, leading to higher decision error rates than the specified confidence level (default 95%). #### Why use a one-sided test rather than a two-sided test? The choice depends on your use case, the metric of interest, and the business impact of any decision. Select a one-sided test when detecting a change in only one direction is valuable and you can accept not detecting changes in the other direction. ================================================================================ # Running an A/A Test URL: https://docs.statsig.com/experiments/types/aa-test Updated: 2026-07-11 ================================================================================ This guide walks through creating and implementing an A/A test on your product in Statsig. A/A tests are commonly used to validate a new experimentation engine you may be integrating with. For new users, running an A/A test provides a low-stakes first test environment. Use it to confirm that your metrics are set up correctly and that exposures are flowing through as expected before starting your first A/B test. By the end of this tutorial, you'll have: * Created a new **Feature Gate** in the Statsig console, set up as an "A/A test" ## Prerequisites 1. You already have a [Statsig account](https://console.statsig.com/sign_up) 2. You already [integrated the Statsig Client SDK](/sdks/quickstart) into an existing application ## Step 1: Create a feature gate in the console The easiest way to run an A/A test in Statsig is by using a [Feature Gate](/feature-flags/overview). You can also use an [Experiment](/guides/abn-tests) to run an A/A, but this tutorial uses a Feature Gate. Log into the Statsig console at https://console.statsig.com/ and navigate to **Feature Gates** in the left-hand navigation panel. Click on the **Create** button and enter the name and (optional) description for your feature gate. This example calls the feature gate “aatest\_example”. Click **Create**. {% figure %} ![create_new_fg_empty](/images/experiments/types/aa-test/163246908-24494f12-9d2e-4d8b-8e3e-4fc0ad9c7e41.png) {% /figure %} In the Setup tab, define the rules for this feature gate. Tap **+ Add New Rule**. You can run an A/A test on a specific user group or platform, but the recommended setup is to divide all traffic 50/50 and deliver the same experience to each group. {% figure %} ![add_new_rule_empty](/images/experiments/types/aa-test/163247089-360857f8-ada3-46af-ac82-e41fc99274b5.png) {% /figure %} Under **Criteria**, select **Everyone** (you may need to scroll up), name your rule, and set the **Pass Percentage** to 50%. Click **Add Rule**, then tap **Save Changes** in the upper right-hand corner. {% figure %} ![add_new_rule_filled](/images/experiments/types/aa-test/163247141-30c96f8a-8257-4b39-aa9f-830bb3c89228.png) {% /figure %} Your feature gate setup should now look as follows: {% figure %} ![aa_rule_filled_out](/images/experiments/types/aa-test/163247211-aacb2c54-1088-4c4a-ab7b-64e393383bdb.png) {% /figure %} Verify that the gate is working as expected by typing in some sample user IDs into the console. Roughly 50% of the IDs should pass and 50% should fail. {% figure %} ![check_rule_pass](/images/experiments/types/aa-test/163247281-c0fb8089-f418-41af-a3a7-d8e684a3cdf3.png) {% /figure %} {% figure %} ![check_rule_fail](/images/experiments/types/aa-test/163247287-7d565983-8253-4841-a65a-0f74d2e103b2.png) {% /figure %} ## Step 2: Check the feature gate in your code Copy the code snippet from the upper right corner of your feature gate page (under the **\< >** symbol) and add it to your application at the point where you want to call the A/A check. ```jsx statsig.checkGate("aatest_example") ``` When a user renders this page in their client application, a live log stream appears in the Statsig console under the **Diagnostics** tab of your feature gate. {% figure %} ![logstream](/images/experiments/types/aa-test/163247712-4610d7b4-188f-4418-a696-127b3c2f54da.png) {% /figure %} ## Step 3: Review A/A test results Within 24 hours of starting your experiment, cumulative exposures appear in the **Pulse Results** tab of your feature gate. {% figure %} ![cumulative_exposures](/images/experiments/types/aa-test/163247787-be1e816c-f715-4fd3-ad59-3a6caf48027a.png) {% /figure %} This shows your logged exposures and their distribution. If something looks off, check the **Diagnostics** tab for day-by-day exposure breakdowns at both the Checks and User level. In the **Metric Lifts** panel, you can see the full picture of how all your tagged metrics are performing. {% figure %} ![pulse_results_empty](/images/experiments/types/aa-test/163248267-7bd7419a-59e0-4d58-b8e5-8ace95ed74d9.png) {% /figure %} What should you expect to see? * **Exposures**- make sure you’re seeing exposures flowing through as expected from your product. If you’re not seeing exposures, use the **Diagnostics** tab and the **Exposure Stream** to debug * **Pulse results**- roughly 5% of your metrics in Pulse should be showing a statistically significant change due to the 95% confidence interval of Statsig’s stats engine Run your A/A long enough to reach most of your weekly active users, or at least a week. ## Simulated A/A tests Statsig runs simulated A/A tests every day in the background for every company on the platform. An A/A test is structured like an A/B test, but both groups receive the same experience. A/A tests help build trust in your experimentation platform and metrics. A/A tests can be Online or Offline. An [Online A/A test](/experiments/types/aa-test) runs on real users. An engineer instruments your app with the Statsig SDK to check for experiment assignment. Statsig logs assignment, but delivers no difference in experience to the user. Since there is no effect, you expect to only see statistical noise. When using 95% confidence intervals, only ~1 in 20 metrics shows a stat-sig difference between control and test. ### Offline A/A tests A single request runs on one unit type. An offline A/A test works by: 1. Querying a representative sample of your data 2. Randomly assigning subjects to Test or Control 3. Computing relevant metrics for Test vs Control and running them through the stats engine 4. Measuring the percentage of false positives. With a p-value cutoff of 0.05 (typical), you can expect a ~5% false positive rate. Download the running history of your simulated A/A test performance through the “Tools” menu in the Statsig Console. Statsig runs 100 tests per request. ### Simulated A/A test file columns | Column Name | Description | | -------------------------------- | ------------------------------------------------------------------- | | metric\_name | Name of the Metric | | metric\_type | Type of Metric | | unit\_type | The unit used to randomize (e.g. userID) | | n\_tests | The number of tests run | | pct\_ss\_95\_pct\_confidence | The percentage of tests that have a stat-sig result for this metric | | avg\_units\_per\_test | The number of units (often users) sampled into the A/A test | | avg\_participating\_units\_per\_test | The number of units in the test with a value for this metric | {% figure %} ![A/A test results table showing statistical significance percentages](/images/experiments/types/aa-test/199562491-84d9b7c4-1cea-4308-a0a9-c04a14a41671.png) {% /figure %} ================================================================================ # SEO Experimentation with Statsig URL: https://docs.statsig.com/experiments/types/seo-testing Updated: 2026-07-11 ================================================================================ In late 2017, Airbnb's growth team faced a deceptively simple question: > Would their new "Magic Carpet" landing page design drive more organic traffic than their existing search results page? With over 100,000 unique URLs spanning different cities, any template change would cascade across their entire search footprint. Traditional A/B testing couldn’t solve this puzzle because Google’s crawlers needed consistent page versions, making user-level randomization impossible. Every marketplace with thousands of templated pages faces the same dilemma: measuring how changes to your template actually impact how Google ranks your pages. This is true whether you’re dealing with physical goods at **Amazon** or **eBay**, or more virtual things at **ZipRecruiter** or **Eventbrite**. Companies can implement the same framework Airbnb developed in hours, not months, and view results in the same dashboards they already use for product experiments. *** ## 1. Select a deterministic page bucket * Crawlers must see a consistent version of each URL during the test window, so you **can’t randomize by user**. * Instead, hash the **canonical URL** into buckets. * In Statsig, you formalize this by adding `page_url` as a **Custom Unit ID**. Steps: 1. From **Project Settings**, navigate to **Custom Unit IDs**. 2. Provide a name and description (it then immediately becomes available to experiments, gates, and dynamic configs). Statsig can now deterministically hash your pages into **Control vs Test** in experiments and keep this assignment stable across sessions. {% callout type="note" %} Strip out `http` vs `https` and query params, leaving only the stable base URL, so that is what is hashed deterministically. {% /callout %} *** ## 2. Define metrics before shipping Make sure the metrics you want to measure are in your data warehouse, keyed on `page_url`. Register these with Statsig’s **metric catalog**. Because the same pipeline powers feature experiments, your existing CUPED or stratified-sampling settings apply automatically. ### Example metrics | Layer | Metric Source | Why it Matters | |------------------|--------------------------------------------------|-------------------------------------------------| | Indexing lag | Impressions, average position (Search Console) | Early signal during re-crawling | | Primary KPI | Organic sessions keyed by `page_url` (Statsig Events) | Measures traffic that actually lands | | Quality guardrail| Conversion, bounce, read-depth, revenue | Ensures traffic is useful | *** ## 3. Implement the change behind an experiment * Create an experiment called `seo_title_test` in Statsig Docs. * Target on the **Custom Unit ID `page_url`** with a 50/50 split across Control and Test. * Expose the variant in the template renderer or CDN edge function. *** ## 4. Ship, monitor, decide * Use **Power Analysis** to determine how long your experiment should run based on traffic volume. * Expect first signals in 2–7 days; wait for re-indexing to plateau before results stabilize. * Merge the winner into your template and archive the test. Experiment summaries remain searchable after archival. ### SEO-specific guardrails | Guardrail | What to Watch | Why it Protects You | |----------------------|-------------------------------|---------------------| | Indexation Δ | `indexed_pages` vs baseline | A template tweak that blocks crawl (robots, canonicals, noindex) will show a sharp drop long before traffic falls | | Cannibalization ratio| Avg. URLs served per query | Multiple pages newly ranking for the same query dilute CTR and can tank combined traffic | | HTTP response mix | % 410 vs 301 vs 200 | A bulk 410 (gone) or mis-configured 301 can wipe out long-tail pages | | Core Web Vitals drift| LCP & CLS p75 | Page-speed regressions may hurt rankings silently | | Crawl budget | Avg. TTFB + bytes/page | Slow/bloated pages decrease crawl rate | *** ## 5. Concrete page-level changes worth A/B testing | Theme | Why It Might Move Organic Traffic | Typical Implementation Knob | |-----------------------|-----------------------------------|-----------------------------| | Title & meta variants | Query-matching, CTR uplift | Add/remove brand suffix, noun → verb phrasing, insert dynamic price | | Structured data | Rich-result eligibility | Inject FAQ, HowTo, Breadcrumb, or Product schema blocks | | Internal-link blocks | Crawl priority & PageRank flow | Swap “related articles” widget ordering; test link count caps | | Content snippets | Relevance & long-tail keywords | Auto-generate 50-word intro vs. none; expand FAQ length | | Canonical/hreflang tags | Duplicate-content handling | Toggle self-canonical vs. cluster canonical; add `hreflang="x-default"` | | Media handling | CLS/LCP scores influence rankings | Defer off-screen images; inline critical hero image; switch to AVIF | | Pagination model | Crawl depth & index coverage | Classic `?page=` URLs vs. `rel="next/prev"` vs. load-more buttons | | Performance budgets | Core Web Vitals ranking factor | 200 ms JS chunk split vs. baseline; CSS purge + inline critical-CSS | | Ad layout | CLS penalties, user engagement | Reserve fixed ad slots vs. dynamic; lazy-load below first viewport | | Schema position | Parser friendliness | Move JSON-LD block to `` vs. end of `` | *** ## 6. Is SEO experimentation right for you? | Great Fit | Maybe Not Yet | |---------------------------------------------------------------------------|--------------------------------------------------------------| | Large page surface (10k+ URLs): marketplaces, docs, publishers | Marketing sites with \<1k pages or sporadic organic traffic | | Teams already shipping **weekly** and want proof before rollouts | Heavy paid-ads model where SEO is \<5% of acquisition | | Companies with engineering bandwidth to template page changes | Sites on locked-down CMSs that forbid code/tag edits | *** ## 7. Key takeaways * **Segment by page, not user.** Use a Statsig Custom ID for deterministic hashing into Control/Test. * **Measure beyond clicks.** Pair Search Console data with product analytics for full-funnel insight. * **Catch problems early.** Statsig’s sequential engine and guardrails identify underperforming variants before they cause harm. * **One platform for every test.** Product, pricing, UX, and SEO experiments in a single, trusted workflow. *** {% callout type="note" %} Statsig also supports other experiment types such as **switchback testing** and **geo-testing**. Geo-testing is particularly useful for measuring the **incrementality of ad spend**, which is hard to measure with traditional experiments due to privacy requirements. {% /callout %} ================================================================================ # Contextual Bandit (Autotune AI) URL: https://docs.statsig.com/autotune/contextual/introduction Updated: 2026-07-11 ================================================================================ Contextual Multi-Armed Bandits are a subset of Multi-Armed Bandits that use context about a user to personalize their experience. Autotune AI predicts outcomes for each variant and selects the best outcome while accounting for uncertainty. Autotune AI prefers variants with high uncertainty over variants with slightly better predictions but low uncertainty, which drives exploration. ## Use cases Contextual bandits bridge the gap between un-personalized solutions and fully fledged ranking solutions. The main limitation is that contextual bandits: * Have a fixed output set of variants they can show * Have limited ability to account for complex context on the "object" being seen/predict for novel content (e.g. video ranking) Their simplicity also provides advantages. Statsig's Autotune AI evaluates in near-real-time on both the server and client, taking a few milliseconds or less to return the ideal experience for a given user context. Contextual bandits are also simple to set up and test: you can set up a test in less than an hour, get model results the next hour, and start seeing experiment results the hour after that in the Statsig console. For more discussion on use cases and motivations, refer to the [Statsig blog](https://www.statsig.com/blog/statsig-autotune-contextual-bandits-personalization). ## Methodology Statsig's autotune AI uses a LinUCB based approach. This paper is a good introduction to the topic: [Li, Chu, Langford, Schapire](https://arxiv.org/pdf/1003.0146). For coverage of regret analysis, these lecture notes from [Jain from the University of Washington](https://courses.cs.washington.edu/courses/cse599i/18wi/resources/lecture10/lecture10.pdf) are a useful resource. Autotune AI works with categorical and numerical features. Key-value pairs attached to the custom object on the Statsig user are converted into categorical or numerical features based on their data type. Categorical features are one-hot-encoded. You don't need to build complex training pipelines, though many customers pass pre-evaluated user attributes or predictions as context objects. {% callout type="note" %} Statsig's console supports specifying features up front. This can help you identify which features to fetch for the bandit when lookups are expensive or live. For Warehouse Native customers, planned work will allow joining entity properties during analysis, enabling you to plug in your own feature store for Autotune AI analysis, similar to the approach used with [CURE](/statsig-warehouse-native/cure/introduction). {% /callout %} Statsig selects the best model (for example, Ridge or Logistic regression) based on your data types and performance, then generates a model from your data. The estimated standard error of the model produces a prediction confidence interval. During evaluation, Statsig uses user context to predict an outcome for each variant and applies the corresponding confidence interval to that prediction. The best variant is the one with the highest upper end of a 95% confidence interval. To adjust the interval size, modify the exploration parameter on the Autotune setup page. For a detailed discussion, refer to the [Methodology](/autotune/contextual/methodology) page. {% callout type="note" %} You can also fetch a ranked list from Statsig and manually expose the variants you show to the user. This is useful when you have client-side filtering or want to show multiple options. Refer to [Advanced Usage](/autotune/using-bandits). {% /callout %} ## Drawbacks Because Statsig manages the models, it can't guarantee perfect model tuning, and more advanced models such as neural networks aren't available. If recommendations are a critical business problem, this feature can serve as a starting point but isn't an appropriate long-term solution. The current approach balances simplicity, speed, and regret minimization. Specific use cases such as real-time updates may not be fully supported. Because the models generally assume linearity, they may not capture complex user interactions. This approach works best for broad-level effects, though feature interaction terms can provide reasonable predictive power for conditional relationships between predictors and outcomes. ## Outcome types Autotune AI supports multiple model types internally, covering both classification use cases (for example, whether a user clicks a button) and continuous outcomes (for example, how much time a user spends reading articles). You can optimize for both "outcomes" and "metrics." To minimize a metric such as latency, disable the "higher is better" setting for that metric. For classification cases, Autotune AI identifies whether any outcome occurs within its attribution window. For continuous cases, Autotune AI requires an event name and field name, and uses the numerical value associated with that field. ## Training Training pipelines are run hourly. For Warehouse Native customers, Statsig processes data in your warehouse and uses an anonymized feature set to train the models. Statsig exports exposures on-demand for each load up to the first million, and in daily batches after that. Log events sent to Statsig are exported hourly if you use Statsig to log outcomes, or you can connect metric sources from your warehouse for outcome tracking. For cloud customers, Statsig processes and trains the data entirely on its servers. ## SDK support Statsig supports contextual autotune in all Client SDKs, but only in the following server SDKs: * [Node](/server-core/node-core) * [Python](/server-core/python-core) * [Java](/server-core/java-core) * [Elixir](/server-core/elixir-core) * [Rust](/server-core/rust-core) * [Go](/server/go) v1.39.0+ * [Ruby](/server/ruby) v2.4.0+ ================================================================================ # Get Started with Autotune AI URL: https://docs.statsig.com/autotune/contextual/getting-started Updated: 2026-07-11 ================================================================================ Autotune AI is quick to set up. {% callout type="warning" %} Statsig supports contextual autotune in all Client SDKs, but only in the following server SDKs: * [Node](/server-core/node-core) * [Python](/server-core/python-core) * [Java](/server-core/java-core) * [Elixir](/server-core/elixir-core) * [Rust](/server-core/rust-core) * [Php](/server-core/php-core) * [Ruby](/server/ruby) {% /callout %} ## Set up your Contextual Autotune Configure your contextual autotune in the Statsig console. You can also configure it programmatically through the Statsig console API. ## Create the contextual autotune Log in to your Statsig console and navigate to Autotune under Experiments. {% figure %} ![Statsig console navigation with Autotune section highlighted](/images/autotune/contextual/getting-started/167d5e87-c155-4465-9419-bcf018c496cb.png) {% /figure %} Select **Create**. Name your contextual autotune and, optionally, specify the goal so other users can understand the motivation behind it. {% figure %} ![Create contextual autotune dialog with name and goal fields](/images/autotune/contextual/getting-started/b16fb9a6-80f4-495c-8ebc-b0cdcdb1d8a8.png) {% /figure %} Set your autotune type to **Contextual**. {% figure %} ![Autotune type selector set to Contextual](/images/autotune/contextual/getting-started/4398ae6f-0763-49c0-a49b-804daece4da8.png) {% /figure %} ## Configure optimization If optimizing for discrete outcomes such as: * User clicks * Checkouts * Actions Choose **Event Occurring** as the outcome type. If optimizing for a continuous output such as: * Revenue * Latency Choose **Event Value** and set the directionality. Also choose the field from your log or metric source (Warehouse Native) to use for the value. For Warehouse Native customers, specify the metric source and any optional filters for your target event. {% figure %} ![Optimization settings screen choosing event outcome and metric source](/images/autotune/contextual/getting-started/4885eac2-38ec-42f1-bd52-592b728623a0.png) {% /figure %} Wrapping contextual autotunes in an experiment is highly recommended but not required. You can set this up before or after your contextual autotune. The experiment wraps autotune calls in code and measures the overall impact of using this contextual autotune in your project. ## Training settings The remaining settings have defaults, but you can tune them as needed: * **Exploration window:** How long to serve traffic randomly to bootstrap bandit exploration. * **Attribution window:** How long after a user sees your variant to count outcome events. If set to 1 hour, a user has 1 hour to take action after seeing the experience. * **Exploration rate:** Controls how much the bandit favors exploration over exploitation. 0 uses only the best prediction without confidence intervals. 1 uses the 99.9% CI instead of the 95% CI for exploration. * **Long-term exploration allocation %:** The percentage of traffic that is always randomly assigned. Use higher values for contextual autotunes you plan to run for a long time, to help avoid model drift. * **Feature list:** A list of features Statsig should use to train the model. This acts as a filter; if not set, Statsig reads every custom attribute. The main use case is fetching this through CAPI to understand which features a given contextual autotune requires for on-demand evaluation. {% figure %} ![Training settings configuration showing exploration and attribution options](/images/autotune/contextual/getting-started/8c87e142-247d-42b3-8baa-04f8967ae400.png) {% /figure %} Set up your variants. Variants are configurations you fetch in code. For example, the configuration below sends a "red" value to your codebase, which can be passed to the color setting on a button. {% figure %} ![Variants list showing configuration keys and values for contextual autotune](/images/autotune/contextual/getting-started/591c0b49-2a40-4bb9-9bef-311bd1d64651.png) {% /figure %} {% figure %} ![Variant detail editor specifying contextual parameters like button color](/images/autotune/contextual/getting-started/10b13396-9bb5-4045-b514-45b0861885cb.png) {% /figure %} ## Use the Contextual Autotune in code (Python example) The following code assumes you have your server secret key. Before running Python, install the SDK: `pip install statsig-python-core` First, import and initialize Statsig: ``` from statsig_python_core import Statsig, StatsigUser key = autotune_name = statsig = Statsig(key) statsig.initialize().wait() ``` Then, create a user object and fetch your config: ``` user = StatsigUser('user_id', custom={'key1': 'value1', 'key2': 'value2'}) cfg = statsig.get_experiment(user, autotune_name) ``` Now you have your config and can apply it: ``` color = cfg.get_string("color", "default color") print(f"Going to use {color} for my color now") ``` At this point, you should be able to: * Confirm that you fetched a value from one of your variants. * Navigate to the diagnostics page of your autotune and see a log of the user ID along with the corresponding variant. Your code is now serving personalized variants to your users. ## Important considerations Statsig requires a few hundred units to train a model and doesn't start training until those units' attribution window has elapsed. To test the functionality, you can simulate events to confirm things work as expected. Use logic like: ``` fetch_autotune_value() if(user country == 'us'): log_click() ``` to conditionally send events and verify that the model picks up on the conditional behavior. ================================================================================ # Monitoring your Contextual MAB URL: https://docs.statsig.com/autotune/contextual/monitoring Updated: 2026-07-11 ================================================================================ There are three primary ways to monitor autotune performance. ## Linked experiments The most reliable way to evaluate whether a bandit is working is to measure whether it drives more of the targeted behavior compared to a baseline experience. You can set up and link an A/B test in Statsig to evaluate this, which also lets you monitor other user behaviors and guardrail metrics. This is the gold standard of measurement and is highly encouraged. Standard practice is to wrap the autotune in an experiment with a binary parameter, either as 50/50 or a 90/10 holdback. You can link the experiment to the autotune to get the results on the autotune page. In code, this might look like: ``` experiment_value = statsig.get_experiment('wrapping_experiment').get('flag') default_param = '..." if(experiment_value): param = statsig.get_experiment('autotune').get('param_name') else: param = default_param # use param in code ``` You would start this experiment at the same time that you launch your autotune. ## Success rate Statsig tracks the cumulative and daily success rate of your variants over time. This can be tricky to interpret. For example, variant A may have lower CTR, but the users being served variant A might have had even lower CTR on other variants. Use this view for tracking and understanding, and to identify outlier variants with notably high or low performance. ## Traffic allocation Traffic allocation shows where Statsig is sending users who see your Autotune. Use this view to identify whether a variant is dominating traffic or receiving no traffic. ## Model features Statsig tracks and surfaces coefficients and feature importance; this can be very useful for understanding which features might be worth further study, or which populations may have unmet needs in your product. * Importance is an estimate of the influence of a feature on the outcome: how much the feature contributes to the prediction. * A positive coefficient means that feature leads to an outcome being more likely (or for continuous outcome spaces is associated with a higher outcome). A negative coefficient means the outcome is less likely, or is associated with a lower continuous outcome. ================================================================================ # Contextual Bandit Methodology URL: https://docs.statsig.com/autotune/contextual/methodology Updated: 2026-07-11 ================================================================================ ## How contextual bandits work This page covers the high-level approach that Statsig takes to running contextual bandits across cloud and Warehouse Native deployments. Implementation details change frequently as Statsig experiments and optimizes its approaches, so this documentation is intentionally high-level. ## Core approach The implementation follows the disjoint model methodology from [Li, Chu, Langford, Schapire](https://arxiv.org/pdf/1003.0146). One model is trained per variant, and estimated confidence intervals (CIs) are computed for each. When contextual autotune is triggered, the latest model version estimates the user's outcome and adds the upper end of the 95% CI to that estimate. {% figure %} ![methodology workflow](/images/experiments/whn/linucb.png) {% /figure %} Statsig models categorical outcomes as logistic regression with L2 penalty. Statsig models continuous outcomes as multivariate ridge regression. ## Training data and sampling To keep data relevant, contextual autotune data is upsampled to prefer recent dates. Sampling uses these mechanisms: * A flat number of samples is selected, preferring the most recent records. * Per day, over the last two weeks, samples are chosen to prefer more recent records. * Samples from the explore dataset are strictly preferred, but non-explore data may be used to satisfy sample requirements. Records are then prioritized by a unit-ID hash to maintain stability in the training set between runs and avoid major jitter. * A sample set is chosen per variant to avoid bias from a dominant model being overrepresented in the training data. If a model has very low volume, it has low representation in the training data. Lower representation causes higher CIs, which increases the upper bound and makes that variant more likely to be selected. This acts as a bounce-back mechanism for low-traffic variants. ## Model and feature updates Models are updated hourly. If a model definition changes (features or target outcome), all data is reset, and the model retrains to match the new definition on the next hourly update. The pre-training data pipeline is available in the history view on the results page, showing the SQL used and the caching tables where data is stored. You can use this data to validate or explore modeling approaches. ## Feature encoding Features with numerical-only values are treated as continuous random variables. All others are string-encoded and one-hot-encoded into binary variables for regression, using the top 25 levels available in the data with more than 1% coverage. Arrays of categories or tags aren't supported, or encode only the most common tag sets. Provide tags as individual key-value pairs in the user custom object instead. ## Model monitoring Diagnostics for model characteristics over time aren't currently available. Model coefficients are visible in the results tab. You can view a comparison of performance between naive random traffic and targeted traffic to determine whether model performance relative to blind allocation improves or degrades over time. ================================================================================ # Autotune (Bandits) URL: https://docs.statsig.com/autotune/overview Updated: 2026-07-11 ================================================================================ Autotune and Autotune AI are Statsig's Multi-Armed Bandit solutions. They automatically find the best variant among a group of candidates while dynamically allocating traffic to optimize for a single target metric. Autotune, the Multi-Armed Bandit solution, allocates traffic toward high-performing variants and can eventually identify a winning variant. ## How Autotune works Autotune is Statsig's [Bayesian Multi-Armed Bandit](./multi-armed-bandit). It tests and measures different variations and their effect on a target outcome. The multi-armed bandit continuously adjusts traffic toward the best-performing variations until it can confidently pick the best variation, which then receives 100% of traffic. Bandits balance the explore/exploit problem: exploiting the current best-known solution versus exploring to gather more information about other solutions. The blog posts on [Multi-Armed Bandits](/autotune/multi-armed-bandit) and [Contextual Bandits](https://www.statsig.com/blog/statsig-autotune-contextual-bandits-personalization) go into depth on use cases and considerations. | | A/B/n Test | Multi-Armed Bandit (Autotune) | Contextual Bandit (Autotune AI) | Ranking Engine | | -------------------------- | ---------- | ----------------------------- | -------------------------------- | ---------------------------------------- | | Typical # Variants | 2-3 | 4-8 | 4-8 | Arbitrary # | | Personalization Factor | None | None | Moderate | High | | Input Data Required | None | Very Little (100+ samples) | Little - generally 1000+ samples | Tens of thousands to millions of samples | | Model Efficacy | None | Basic | Moderate | High | | Identifies Best Variant | Yes | Yes | No | No | | Consistent User Assignment | Yes | No | No | No | ## Implementing Autotune Implementing an Autotune requires checking an experiment in Statsig. After initialization, or on server SDKs, this comes with sub-millisecond latency. Autotune has a JSON config associated with each variant. The SDK returns this config, which you can use to modify elements of your webpage (for example, an image URL or button color) or to identify which variant is active so you know which code to run. ## When to use Autotune Autotune has two major differences from A/B testing (Statsig Experiments): 1. The traffic split isn't fixed over the duration of the test. This allows Autotune to divert more traffic to the winner and less to losers while making fewer mistakes. However, the user experience may not be consistent upon repeated visits. 2. Autotune can only optimize for a single metric. Autotune can't accurately measure a collection of metrics, and isn't a reliable way to understand secondary effects of your changes. It works best when the metric is well-understood and has a direct, immediate relationship to the change being tested. Because of these differences, Statsig recommends Autotune in the following scenarios: 1. The cost of exposing users to a losing treatment is high. For example, sending new users to an inferior landing page may result in lost revenue or churn. Testing two registration flows may cause some users to never sign up. Autotune avoids permanently losing users because it adapts quickly to feedback, unlike a static A/B test. 2. You want the decision to be automated. Because Autotune automatically selects the winner, no human decision-making is required. This is well-suited for launching many simultaneous tests or running a long-term unmonitored test. 3. It's acceptable for users to see different experiences upon return visits. For example, when changing text or recommendation algorithms. 4. You have one simple metric to optimize for (for example, click-through rate) with an immediate effect on the test. 5. You want to test multiple variations. Autotune quickly eliminates poor performers while focusing traffic on the best variants. Autotune should be avoided in the following scenarios: 1. When you have a complex ecosystem and want to understand secondary effects, tradeoffs between variants, and user behavior. 2. When you are optimizing for complex metrics or delayed effects. For these cases, use A/B testing with [Experiments](/experiments/overview). In general, it's also a best practice to run Autotune within an experiment with a small holdback group that doesn't receive the Autotune, so you can measure the impact of the Autotune. ================================================================================ # Get Started With Autotune URL: https://docs.statsig.com/autotune/setup Updated: 2026-07-11 ================================================================================ ## How to set up Autotune 1. To create a new Autotune experiment, navigate to the [Autotune section on the Statsig console](https://console.statsig.com/autotune). 2. Click the Create button and enter the name and description of the Autotune experiment that you want to create. 3. Provide the variants that you want to test in the Autotune experiment. Each variant needs a name, and a corresponding JSON value. The variant listed as Control/Default is returned when the Autotune experiment isn't running. {% figure %} ![Autotune experiment variant configuration interface](/images/autotune/setup/131385189-5f0c1d93-ba87-4159-8995-3c30991587a0.png) {% /figure %} 4. Select the success event to optimize for as shown below. You can further specify an optional [event value](/guides/logging-events). {% figure %} ![Autotune success event selection interface](/images/autotune/setup/131385239-5a76d253-022b-457e-a370-f9ee7ce566a1.png) {% /figure %} There are a few parameters you can specify: * Exploration Window: The initial time period where Autotune equally splits traffic. This is useful for noisy or temporal metrics where hourly data swings can bias Autotune's initial measurements. * Attribution Window: The maximum duration between the exposure and success event that counts as a success. 1 hour is recommended for most applications; adjust if you expect the success event to lag the exposure event by several hours. * Winner Threshold: The "probability of best" threshold a variant must reach for Autotune to declare it the winner, stop collecting data, and direct all traffic. A lower value results in faster decisions but increases the probability of selecting the wrong winner. Click **Create** to finalize the setup. 6. As with Feature Gates and Experiments, Statsig provides a code snippet for the exposure check event to add to your code. Select **Start** when you're ready to launch your Autotune test. {% figure %} ![Autotune code snippet and launch interface](/images/autotune/setup/131384977-144dd868-787b-45ad-9ff1-fc9afbd4c769.png) {% /figure %} ## Schedule the start time Instead of launching an Autotune right away, you can schedule it to start at a specific time. This is useful when you want a test to begin during a particular window, such as after a holiday, without having to start it manually. To schedule the start: 1. Select **Start** to open the start dialog. 2. Turn on **Schedule Autotune Start Time**. 3. Choose the date and time you want the Autotune to begin. You can schedule a start up to one year in advance, and times snap to the nearest 15-minute interval. The picker uses your local time zone. 4. Select **Schedule Start**. The Autotune shows a Scheduled status until the start time, when it launches automatically. To change or remove a scheduled start before it begins, use the **Edit Scheduled Start** or **Cancel Scheduled Start** option on the Autotune. ================================================================================ # Autotune URL: https://docs.statsig.com/autotune/monitoring Updated: 2026-07-11 ================================================================================ ## How to monitor your Autotune test ### Computations and traffic allocation The results tab within Autotune provides a view of your ongoing and completed Autotune tests. Statsig computes Autotune hourly, with metrics and traffic allocation updated throughout the day. Statsig ingests results from exposures after the attribution window is complete. For example, an exposure at 1pm with a 6-hour attribution window isn't counted until 7pm. ### Result {% figure %} ![Autotune test results summary](/images/autotune/monitoring/150008289-2119f756-ff71-4634-af85-fca840cf1e4c.png) {% /figure %} This section shows a summary of your Autotune test. The top bars show a 95% Bayesian Credible interval for the estimated conversion rate (exposure to success event). There's a 95% chance that the real value is within this interval. The table shows the number of exposures, success events, and overall success rate for each variation across the duration of the test, along with a plain-language description of the current test state. ### Details {% figure %} ![Autotune test detailed analytics charts](/images/autotune/monitoring/150009034-74a9f2b9-42ee-4d24-9c95-5fe6dff35334.png) {% /figure %} There are several charts provided: 1. Probability of Best - shows the progress of the Autotune test, and which variant is currently winning. 2. Cumulative success rate - shows the overall success rate (exposure -> success) to date. 3. Daily success rate - shows the success rate for each variation per day. 4. Traffic allocation - shows the daily number of exposures allocated to each variation on a given day. ### Linked experiments The most reliable way to evaluate whether a bandit is working is to measure whether it drives more of the targeted behavior compared to a baseline experience. You can set up and link an A/B test in Statsig to evaluate this, which also lets you monitor other user behaviors and guardrail metrics. This is the gold standard of measurement and is highly encouraged. Standard practice is to wrap the autotune in an experiment with a binary parameter, either as 50/50 or a 90/10 holdback. You can link the experiment to the autotune to get the results on the autotune page. In code, this might look like: ``` experiment_value = statsig.get_experiment('wrapping_experiment').get('flag') default_param = '..." if(experiment_value): param = statsig.get_experiment('autotune').get('param_name') else: param = default_param # use param in code ``` You would start this experiment at the same time that you launch your autotune. ================================================================================ # Methodology URL: https://docs.statsig.com/autotune/multi-armed-bandit Updated: 2026-07-11 ================================================================================ ## Model The base Autotune implementation uses a Thompson Sampling (Bayesian) algorithm to estimate each variant's probability of being the best variant and allocate a proportional amount of traffic. For example, if a given variant has a 60% probability of being the best, Autotune allocates 60% of the traffic to it. The multi-armed bandit algorithm adds more users to a treatment as soon as it determines that treatment is clearly better at maximizing the reward (the target metric). Throughout the process, higher-performing treatments receive more traffic and underperforming treatments receive less. When the winning treatment beats the second-best treatment by a specified margin, the process ends. Some helpful references: * [Statsig Blog](https://www.statsig.com/blog/introducing-autotune) * [Goyal and Agrawal (Microsoft Research)](https://proceedings.mlr.press/v23/agrawal12/agrawal12.pdf) Regret Analysis * [Doordash Engineering](https://doordash.engineering/2022/03/15/using-a-multi-armed-bandit-with-thompson-sampling-to-identify-responsive-dashers/) Summary Blog ## Advantages The main advantage of the base Multi-Armed Bandit over a contextual bandit is its ability to converge and identify the best variant. When a single solution works well for all users, the Multi-Armed Bandit efficiently allocates traffic and determines the correct long-term solution while minimizing regret (the cost of exposing many users to a worse variant, as happens in an A/B test). ## Disadvantages The main disadvantage of a Multi-Armed Bandit compared to a contextual bandit is its inability to personalize. When user attributes interact with variants, Autotune can identify a global maximum that is worse than serving each user their individual best variant. For example, even if the "US Flag" variant had the highest overall CTR, it would be a poor choice for Canadian users. In such cases, both groups converge to a sub-optimal variant. | | A/B/n Test | Multi-Armed Bandit (Autotune) | Contextual Bandit (Autotune AI) | Ranking Engine | | -------------------------- | ---------- | ----------------------------- | -------------------------------- | ---------------------------------------- | | Typical # Variants | 2-3 | 4-8 | 4-8 | Arbitrary # | | Personalization Factor | None | None | Moderate | High | | Input Data Required | None | Very Little (100+ samples) | Little - generally 1000+ samples | Tens of thousands to millions of samples | | Model Efficacy | None | Basic | Moderate | High | | Identifies Best Variant | Yes | Yes | No | No | | Consistent User Assignment | Yes | No | No | No | ================================================================================ # Bandit FAQs URL: https://docs.statsig.com/autotune/bandit-faq Updated: 2026-07-11 ================================================================================ ### When should I see data show up in a bandit? Diagnostic data appears in near-real time in the logstream on your bandit. Data in the models/results section depends on your settings. Statsig doesn't count units as enrolled until their attribution window has elapsed. For example, if you have a 24-hour attribution window, it takes 24 hours to see your first units and model updates. Statsig trains models hourly with a few minutes of data delay, so there may be 1 to 2 additional hours of delay beyond that period. This operation doesn't have a strict SLA on Statsig's side. Delays may occur due to data quality checks or other investigations to prevent bad model training data from affecting your production environment. ### What do I put into the Variant JSON? The variants return this JSON as a config, similar to dynamic configs or experiments. You can put an identifier for the variant, or specify a set of attributes that will be accessed in code to avoid writing conditional statements to fetch the corresponding attributes. ### Why use a linked experiment? Bandits optimize for a single target metric or event, which alone doesn't provide a detailed analysis of how the bandit affects your users. Wrapping a bandit in an experiment (where the control group receives a default experience and the variant group receives the bandit's experience) lets you get deeper insights on its impact on end users. This step is highly recommended. ### Where is my Autotune data available? Statsig downloads Autotune data hourly into the `statsig_first_exposures` metric source. Select the history modal on the autotune to view the SQL and tables generated for an individual autotune. ### Why was a "losing" variant chosen? Autotune assumes that a metric is consistent over time. A metric with strong seasonality (for example, a metric that drops on weekends) isn't a good candidate for an Autotune experiment. With seasonality, the following situation is possible: * A variant that consistently performs worse than others receives less and less traffic until it has essentially zero traffic. * Other variants, still receiving traffic, see their metric drop when seasonal effects occur. * The variant with zero traffic doesn't see that metric drop (because no traffic is allocated to it), which causes Statsig to select it as the winning variant. This can sometimes cause Statsig to choose a "losing" variant as the winner. ================================================================================ # Advanced Bandit Usage URL: https://docs.statsig.com/autotune/using-bandits Updated: 2026-07-11 ================================================================================ Both contextual and non-contextual bandits are managed on Statsig's console, or through Statsig's [console API](/console-api/introduction) for programmatic creation. Both bandit types use a common, streamlined API, so you can explore either use case without significant changes from using experiments. Statsig's bandits provide the power of complex bandits through a simple interface. A key observation from development is that many such systems fail due to implementation issues. No additional steps beyond a regular experiment check are required to use a bandit. For contextual multi-armed bandits, attach any context to your user object (usually at initialization) before making the experiment check. You can confirm this in the diagnostics tab of your bandit in the console. ### Checking a bandit Check bandits using your standard experiment call. For example, in React, configure a bandit's variant JSON like this: ``` { "text": "", ... } ``` and accessing it in code using the same pattern as [experiments](/guides/abn-tests): ``` const banditText = useExperiment('contextual_bandit').config.get('text'); ``` This is separated from a bandit's linked experiment call to reduce excessive logging; if you have a linked wrapping experiment, wrap the call after checking the experiment. Statsig will automatically detect and differentiate categorical and numerical features based on cardinality. ### Getting a list Getting a ranked list of variants is a common requirement. Client-side filtering or other constraints may prevent showing a particular variant. Statsig supports getting a list of scored outcomes, but requires manual exposure logging so that Statsig knows which variant you displayed to the user. ### Outcomes In Statsig Cloud, log an outcome event (as a flag, or with a value) to Statsig, and Statsig handles connecting exposures to outcome events within your configured time periods. In Statsig Warehouse Native, you can choose which approach to take. Logging events with Statsig exports events to your warehouse in near-real-time for use in Bandits, or you can provide your own outcome dataset as a metric source. {% callout type="note" %} Data latency matters: if your output dataset has more than 24 hours of lag, bandit exploration can slow down significantly. {% /callout %} ================================================================================ # Switchback V2 URL: https://docs.statsig.com/experiments/types/switchback-v2 Updated: 2026-07-11 ================================================================================ ## What is a switchback experiment? A **switchback experiment** tests two versions of a system by **alternating them over time**. This methodology is useful when it isn't possible to isolate user experiences between treatment and control groups. For example, on a rideshare platform, offering lower prices to a treatment group might increase demand for cars and indirectly affect control riders. A switchback experiment measures impact over time by alternating between experiences instead of splitting users. ## How switchback experiments work At a high level, switchback experiments work as follows: 1. **Define clusters and their schedule:** A **cluster** is a group of users that switch between experiences on the same cadence. *Example:* All users in **New York** and **Chicago** follow this schedule: * **9:00–10:00 AM:** Control * **10:00–11:00 AM:** Treatment * **11:00 AM–12:00 PM:** Control * **12:00–1:00 PM:** Treatment 2. **Aggregate data by time buckets:** A bucket represents a single window of time during which the user experience remains constant. Each bucket is treated as one data point in the analysis. *Example:* In the schedule above, the experiment produces **four buckets**: two for control and two for treatment. * **Control** * Bucket 1: 9:00–10:00 AM * Bucket 3: 11:00 AM–12:00 PM * **Treatment** * Bucket 2: 10:00–11:00 AM * Bucket 4: 12:00–1:00 PM 3. **Run regression analysis:** Statistical models account for factors such as time of day, day of week, or cluster attributes when estimating the difference between treatment and control. 4. **Compare results:** The final output resembles a traditional A/B test, including metrics such as estimated lift and confidence intervals. ## Setting up a switchback experiment Defining the hypothesis, metrics, groups, targeting, and parameters follows the same general workflow as a traditional A/B test. Switchback experiments add three additional configurations: **clusters**, **scheduling**, and **analysis configuration**. ## Defining cluster(s) Clusters are groups of users who follow the same experience cadence. In traditional A/B tests, the selected ID Type acts as both the randomization unit and the unit at which metrics are calculated. In a switchback experiment, however, the ID Type defines the unit for metric calculation, while clusters determine which experience a user receives over time. Statsig provides three ways to define a cluster. | Method | Description | Inputs | | :----- | :------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Single | Single Cluster where all users eligible for the experiment follows the same cadence | **Start With:** defines which experience (control or treatment) starts the switchback | | Auto | Provides a two-cluster configuration where Statsig automatically assigns users to each cluster based on the specified inputs. | **Cluster ID Type:** Select a custom ID from the Exposure User Object that Statsig uses to split users into clusters. For example, if server\_id is present on the user object, Statsig randomly assigns server\_id values to each cluster, and groups users based on their server\_id. | | Manual | Two-cluster configuration where users are manually assigned to each cluster. | **Cluster Field:** Select a field from the Exposure User Object that Statsig uses to assign users to clusters. For example, if Country is selected as the Cluster Field, you can assign specific countries to either Cluster 1 or Cluster 2. Statsig then places users into clusters based on the value of that field (for example, their country). | Fields used as Cluster ID Type (Auto) and Cluster Field (Manual) can be used as covariates in the regression analysis and are available to break down metric data in the results section. ## Defining scheduling Once the clusters are configured, you can define the schedule for experiences within each cluster. {% figure %} ![Screenshot2026 02 25at9 45 50AM](/images/Screenshot2026-02-25at9.45.50AM.png) {% /figure %} ### Inputs **Window Size / Unit:** The length of each window during which a user’s experience remains constant. **Experiment Start Date / Time / Timezone:** The date and time when the switchback experiment begins. All clusters start simultaneously. The experiment can't be started if the selected start date or time is in the past when the experiment starts. **Target Duration:** The intended length of time the experiment should run. By default, the experiment doesn't automatically stop when this duration is reached. Users continue to receive the switched experiences according to the configured schedule. If **”Stop Experiment at target duration”** is enabled, the experiment stops automatically at the end of the specified duration. After that, users are served the default experiment value configured in code. ## Define analysis configuration You can configure how exposures and metrics are handled during the transition periods between switchback windows. For example, if a rideshare marketplace switches from Control to Treatment at 9 AM, the system may still experience lingering effects from the Control period, such as drivers already on active trips or riders remaining in the queue from earlier periods. In these cases, you may want to exclude exposures and metric data recorded shortly after the switch. Configure **Burn-in** and **Burn-out** periods in the **Analysis Configuration** section to exclude these transition exposures. {% figure %} ![Screenshot2026 02 25at2 26 44PM](/images/Screenshot2026-02-25at2.26.44PM.png) {% /figure %} ### Inputs **Burn-in Period:** The amount of time at the beginning of each window that is excluded from analysis. **Burn-out Period:** The amount of time at the end of each window that is excluded from analysis. **Metric Calculation:** Determines how metric events are attributed to an exposure. The following options define how metrics are aggregated within each switchback window. {% figure %} ![Screenshot2026 02 25at2 44 24PM](/images/Screenshot2026-02-25at2.44.24PM.png) {% /figure %} * **Period from first exposure:** Aggregates metric data for a specified period of time after the user’s first exposure. * **Entire window:** Aggregates metric data across the full switchback window. * **Period between burn-in and burn-out:** Aggregates metric data only within the portion of the window between the burn-in and burn-out periods. **Exposure Calculation:** Defines how exposures logged during switchback windows are handled in the analysis. The following options are available: {% figure %} ![Screenshot2026 02 25at2 50 36PM](/images/Screenshot2026-02-25at2.50.36PM.png) {% /figure %} * **Include exposures in burn periods:** Considers all exposures recorded during the switchback window, including those that occur within the burn-in and burn-out periods. * **Exclude exposures in burn periods:** Considers only exposures recorded between the burn-in and burn-out periods. **\[Coming soon] Specify Pre-computed User Dimensions:** Configure user dimensions that can be used to break down experiment results in the results section. These fields are selected from the exposure user properties or entity properties. ================================================================================ # Bonferroni Correction URL: https://docs.statsig.com/experiments/statistical-methods/methodologies/bonferroni-correction Updated: 2026-07-11 ================================================================================ ## What is Bonferroni correction? A Bonferroni Correction is a statistical method that reduces the probability of false positives by adjusting the significance level for multiple comparisons. If you run a test with α = 0.05, the probability of a false positive is 5%. Running more comparisons at the same significance level increases the chance of at least one false positive, because each comparison is an additional opportunity for a false positive. Bonferroni correction is an optional feature on Statsig experiments. Statsig divides the significance level (α) by the number of comparisons being evaluated. You can choose to apply these based on one or both of the following: * The number of test groups (multiple treatment hypotheses). Statsig divides the significance level by the number of variants being compared against control. * The number of metrics in the scorecard. Here you may select what percentage of your total α is divided evenly among the Primary Metrics, and the remaining α is split equally among Secondary Metrics. For example: * Significance level of 0.05 * 2 Primary Metrics and 4 Secondary Metrics * 60% of α applied to Primary Metrics * Each Primary Metric is calculated with α = 0.6 \* 0.05 / 2 = 0.015 * Each Secondary Metric is calculated with α = 0.4 \* 0.05 / 4 = 0.005 * If both corrections are selected, Statsig applies them together. In the example above, to also correct for having 2 test groups, divide each α by 2. When analyzing dimensions, if correction for metrics is enabled, Statsig applies the correction separately for the dimensional breakdown. Statsig uses the number of dimensions as the total metric count for the dimensional analysis, but this doesn't impact topline metrics. {% figure %} ![Bonferroni correction configuration interface](/images/snippets/stats-methods/bonferroni-correction/038d75eb-5745-4587-b180-86b88594ccb9.png) {% /figure %} ## How Bonferroni correction affects experiment metrics In the experiment scorecard section, Statsig derives confidence intervals for applicable metrics from (1 - adjusted α). Hovering over a confidence interval displays the adjusted α alongside other relevant metric details. In the experiment explore section, Statsig calculates a new adjusted α based on your selections, and the confidence intervals use (1 - adjusted α). ================================================================================ # Benjamini–Hochberg Procedure URL: https://docs.statsig.com/experiments/statistical-methods/methodologies/benjamini-hochberg-procedure Updated: 2026-07-11 ================================================================================ ## What is the Benjamini-Hochberg procedure? The Benjamini-Hochberg Procedure ("BH" procedure) is a statistical method that reduces the probability of false positives by adjusting the significance level for multiple comparisons. BH is less strict than a [Bonferroni Correction](/experiments/statistical-methods/methodologies/bonferroni-correction): instead of controlling the chance of at least one false positive (Family Wise Error Rate), BH controls the expected proportion of false positives when the null hypothesis has been rejected (False Discovery Rate). You can enable the BH procedure for individual experiments, or configure global Experiment Settings to use it by default. {% figure %} ![Benjamini-Hochberg procedure configuration interface](/images/snippets/stats-methods/benjamini-hochberg-procedure/c865494e-0ae4-489c-a416-45848b4d10bc.png) {% /figure %} ## How BH adjusts the significance level The [Benjamini-Hochberg Procedure](https://www.statisticshowto.com/benjamini-hochberg-procedure/) updates the significance level (modifying your pre-set $\alpha$). Statsig calculates the new significance level by sorting metric p-values in ascending order and comparing each with a paired threshold. Each p-value’s paired threshold is the desired False Discovery Rate divided by the number of comparisons, multiplied by the rank of that p-value in the ordered list. The largest threshold value higher than its corresponding p-value becomes the new significance level ($\alpha$). You can apply the Benjamini-Hochberg Procedure based on: * The number of test groups (multiple treatment hypotheses). For each metric, aggregate the list of p-values from each variant and complete the Benjamini-Hochberg procedure. * The number of metrics in the scorecard. For each variant, aggregate the list of p-values from each metric and complete the Benjamini-Hochberg procedure. * Both the number of test groups and number of metrics in the scorecard. All p-values are aggregated to complete the Benjamini-Hochberg procedure. Statsig doesn't apply BH procedure when evaluating the p-values of any event-dimension or user-property experiment metric results. Only the top-line metric results are compared to the new significance level. ## How experiment metrics appear after applying BH In the experiment scorecard section, Statsig derives confidence intervals for applicable metrics from (1 - adjusted α). Hovering over a confidence interval displays the adjusted α alongside other relevant metric details. In the experiment explore section, Statsig calculates a new adjusted α based on your selections, and the confidence intervals use (1 - adjusted α). ================================================================================ # SRM Checks URL: https://docs.statsig.com/stats-engine/methodologies/srm-checks Updated: 2026-07-11 ================================================================================ ## SRM - sample ratio mismatch Sample ratio mismatch (SRM) occurs when the observed allocation of **unique** users between test groups differs from the expected allocation or split of the test. Refer to the [Statsig blog post on sample ratio mismatch](https://www.statsig.com/blog/sample-ratio-mismatch) for background. SRM is a signal that there may be unknown bias in the test. This is a significant problem because, without a clear diagnosis of the imbalance, it is difficult to know how much the bias affects your results. ## SRM checks Statsig runs SRM checks on all experiments and feature gates as part of Health Checks (refer to [Health Checks](/experiments/monitor)). Statsig uses a Chi-squared test to identify whether the split of users between groups indicates a sample ratio mismatch. {% figure %} ![SRM health check results interface](/images/experiments/srm-checks-health.png) {% /figure %} Statsig automatically analyzes data by common dimensions logged by the Statsig SDK to identify potential drivers of SRM. These include sdk\_type, sdk\_version, reason, is\_bot, browser\_name, browser\_version, os, os\_version, and region to identify potential causes. {% figure %} ![SRM dimension analysis breakdown](/images/experiments/srm-checks-dimensions.png) {% /figure %} ================================================================================ # Pre-Experiment Bias URL: https://docs.statsig.com/experiments/statistical-methods/pre-experiment-bias Updated: 2026-07-11 ================================================================================ In some cases, users in two experiment groups can have meaningfully different average behaviors before your experiment applies any intervention to them. If this difference persists after your experiment starts, experiment analysis may attribute that pre-existing difference to your intervention, making a result appear more or less significant than it is. [CUPED](/experiments/statistical-methods/methodologies/cuped) helps address this bias, but can't fully account for it. Some metrics, such as retention, aren't suitable candidates for CUPED and can't be easily adjusted. Statsig proactively measures the pre-experiment values of all scorecard metrics for all experiment groups, and determines whether the values are significantly different and could cause misinterpretations. If Statsig detects bias, it notifies users and places a warning on relevant Pulse results. ### How it works Statsig provides a "Days Since Exposure" view to help identify novelty effects and pre-experiment effects. For example, the test group in the experiment below had a consistently higher mean than the control group in the week before exposure for this metric. {% figure %} ![Pre-experiment bias visualization showing test group with consistently higher mean than control group](/images/snippets/stats-methods/pre-experiment-bias/246545035-22ff2db6-9c08-4227-a53d-8faa8feb5e92.png) {% /figure %} Statsig detects this bias by running the standard [Pulse](/experiments/interpreting-results/read-results) calculation on the pre-experiment term (looking back one week in Cloud, and your configured CUPED lookback window in Warehouse Native), and calculating the p-value for the null hypothesis that the groups are identical. Statsig flags relevant results according to logic that balances awareness and false positives stemming from high numbers of scorecard metrics or groups. ### What to do Pre-experiment bias can occur by chance and is not always a major issue. * If the total delta is small, it may not meaningfully influence your interpretation of results. * If CUPED can account for the bias, the bias shouldn't affect your results. In many cases, the warning is informational and you can proceed while treating impacted metrics with caution. This is often appropriate if the metric isn't critical to the experiment, or if you care more about the directional movement than the exact number. Additional time may also reduce the bias when there is no systemic source, as new users dilute the initial imbalance. If the metric is critical to your analysis and you need the exact numerical value, consider resalting and restarting the experiment. ================================================================================ # Managing SRM URL: https://docs.statsig.com/experiments/monitoring/srm Updated: 2026-07-11 ================================================================================ ## What is SRM SRM, or sample ratio mismatch, is a problem with experiments characterized by too many units in some groups and too few in others. The example below is an exposure crosstab of an experiment with SRM. Even though the group percentages may look similar, if an assignment system is splitting traffic evenly, an imbalance this extreme or greater would have less than a 0.01% chance of occurring randomly. {% figure %} ![srm_example](/images/experiments/srm.png) {% /figure %} Statsig and most experiment platforms normalize metrics per-user: a count metric is measured as total count divided by unique users in the experiment. In isolation, having more users in one group isn't a problem. However, SRM is problematic for the following reason. ## Why SRM is an issue SRM is an issue because it is usually *non-random*: the extra or missing traffic is *not identical* to the original traffic. Common causes of SRM include: * A bug causes a user's client or browser to crash before an exposure log can be sent. Users who don't return aren't re-exposed, but users who return are included. This introduces bias in measurement. * A conditional dependency filters who is exposed based on some characteristic for one or more groups, making those groups non-identical to other groups and biasing measurement. * A script bulk-exposes users one group at a time, and logs are truncated after a certain count, causing the last group's exposures to be truncated. SRM checks are critical because they detect these effects even at low rates. Even low-rate SRM can lead to serious inaccuracy in experiment readouts. ## How SRM is detected Statsig detects SRM using a [chi-squared test](https://en.wikipedia.org/wiki/Chi-squared_test), which analyzes categorical data to determine whether observed frequencies match expected frequencies. For example, in the experiment above, the expected distribution is 167.85k units per group, but the observed distribution is [166.08k, 171.18k, 166.30k]. If the p-value of the test is low, the null hypothesis that the groups are identical is rejected, and the result indicates a difference between the groups' observed and expected assignment rates. ## What to do if an experiment has SRM On Statsig, SRM creates a warning or failure state on an experiment's health check when detected, depending on how extreme the SRM is. {% figure %} ![srm_failure](/images/experiments/srm_failure.png) {% /figure %} This often causes concern: teams don't want to reset their experiment and lose collected data, and if there *is* an underlying issue it may reproduce after a reset. Follow these steps to diagnose and address SRM: **1.) Check the time series data** Statsig generates a chart of SRM p-value over time. If the chart is noisy and bounces around, the alert is more likely a false positive. If it consistently trends down to 0, there is likely a real assignment issue. The following is an example of a p-value chart that indicates a real issue. {% figure %} ![srm_bad_timeseries](/images/experiments/bad_pvalue.png) {% /figure %} **2.) Understand if there's a clear root cause for SRM** Use Statsig's SRM debugger or analyze exported exposures to determine whether a specific segment is driving SRM. Often a bug is isolated to one platform such as Android, or restricted to users with low internet speeds. If you find and fix the bug, you can restart the experiment safely. If the issue is clearly isolated, filtering out that segment from analysis is also reasonable when the experiment was expensive or required a long data collection period. At this point, you should have a reasonable sense of whether there is a real issue. Use that assessment to decide next steps. **3.) Assess options** In many cases, the best path is to investigate, fix, and restart. In some cases, the SRM may be mild enough, and the experiment low-risk enough, that making a decision with the affected data is acceptable. Statsig strongly recommends against proceeding without investigation and considers restarting the experiment a best practice, ideally after investigating any potential SRM cause. ================================================================================ # Variance Reduction URL: https://docs.statsig.com/experiments/statistical-methods/variance-reduction Updated: 2026-07-11 ================================================================================ ## Variance reduction [Variance](/experiments/statistical-methods/variance) measures the amount of noise in a metric or experiment results. Higher variance produces larger confidence intervals and requires more sample size to consistently observe a statistically significant result for the same effect size. Reducing variance shortens experiment run times by requiring less sample. Statsig uses a form of CUPED based on a [2013 Microsoft paper](https://www.exp-platform.com/Documents/2013-02-CUPED-ImprovingSensitivityOfControlledExperiments.pdf) (Deng, Xu, Kohavi, & Walker). Statsig automatically applies CUPED to experiments and runs it for the topline results on key metrics in Pulse. This produces significant variance reduction for the large majority of metrics where CUPED can be applied. For more details, refer to the [launch post for CUPED](https://blog.statsig.com/cuped-on-statsig-d57f23122d0e). ## CUPED - Controlled-experiment Using Pre-Existing Data CUPED (short for Controlled-experiment Using Pre-Existing Data) is a technique that uses user information from before an experiment to reduce variance and increase confidence in experimental metrics. At Statsig, this pre-experiment data covers the 7 days before each user's exposure, rather than a fixed window before the experiment starts for all users. This helps reduce bias in experiments where groups were randomly different before any treatment applied. The Cloud product uses stratification alongside CUPED to account for users who may not have pre-experiment data. Statsig groups users into strata based on available pre-experimentation information. Statsig first estimates treatment and control effects within each stratum, then aggregates them to produce an overall result. Statsig then applies the standard difference-in-means and variance estimation. This approach retains users with missing pre-experiment data while still providing variance reduction where applicable. ## Winsorization Winsorization is another technique for reducing noise by managing the influence of outliers. Winsorization measures the percentile *Px* of a metric and sets all values above *Px* to *Px*. This reduces the influence of extreme outliers caused by factors such as logging errors or bad actors. ## Metric selection The metrics you use can significantly influence the sensitivity of your analysis. The transformations described above, combined with techniques such as creating threshold-based flags, allow you to trade exact numbers for more statistical power. For more information, refer to the [blog post on understanding and reducing variance](https://www.statsig.com/blog/understanding-and-reducing-variance-and-standard-deviation). ## Related resources * [Deng, Xu, Kohavi, & Walker](https://exp-platform.com/Documents/2013-02-CUPED-ImprovingSensitivityOfControlledExperiments.pdf): seminal paper on using CUPED for online controlled experiments * [Booking.com: CUPED in practice](https://booking.ai/how-booking-com-increases-the-power-of-online-experiments-with-cuped-995d186fff1d): blog post on the theory and practice of CUPED * [Improving the Sensitivity of Online Controlled Experiments: Case Studies at Netflix](https://www.kdd.org/kdd2016/papers/files/adp0945-xieA.pdf) ================================================================================ # CUPED URL: https://docs.statsig.com/experiments/statistical-methods/methodologies/cuped Updated: 2026-07-11 ================================================================================ ## CUPED - Controlled-experiment Using Pre-Existing Data CUPED (short for Controlled-experiment Using Pre-Existing Data) is a technique that uses user information from before an experiment to reduce variance and increase confidence in experimental metrics. At Statsig, this pre-experiment data is defined as the 7 days before each user's exposure rather than a fixed window before the experiment starts for all users. This helps debias experiments that have meaningful pre-exposure bias (for example, groups that were randomly different before any treatment was applied). The Cloud product uses a 7-day window for CUPED calculation. For Warehouse Native customers, Statsig recommends a 7-day window, but you can customize it to any length. For more details, refer to the [Variance Reduction](/experiments/statistical-methods/variance-reduction) page. For an in-depth look at the methodology, refer to CURE by Statsig. ## CUPED for simple aggregations The methodology for simple aggregations is described in the original [Microsoft paper](https://www.exp-platform.com/Documents/2013-02-CUPED-ImprovingSensitivityOfControlledExperiments.pdf), as well as the in-depth [article](https://www.statsig.com/blog/cuped) on the technique. The Cloud product uses stratification alongside CUPED to account for users who may not have pre-experiment data. Statsig groups users into strata based on available pre-experimentation information, estimates treatment and control effects within each stratum, then aggregates them to produce an overall result. Statsig then applies the standard difference-in-means and variance estimation. This approach lets Statsig retain users with missing pre-data while still benefiting from variance reduction where applicable. ## CUPED for ratio metrics The Microsoft paper also gives details on how to implement CUPED for those with a different analysis unit (Appendix B). On Statsig, this extends to ratio metrics, where each experiment unit is represented by a numerator and a denominator. The variance reduction process finds the variance of experiment data, pre-experiment data, and the covariance between the two. Denote the numerator, denominator, pre-experiment numerator, and pre-experiment denominator of a unit as $Y$, $N$, $X$, and $M$, respectively. Using the CUPED-reduced variance formula, $$ Var(\frac\{Y\_\{cv}}\{N\_\{cv}})=Var(\frac\{Y}\{N})+\theta^2 Var(\frac\{X}\{M})-2\theta Cov(\frac\{Y}\{N}, \frac\{X}\{M}) $$ where optimal $\theta$ is found as $$ \frac\{Cov(\frac\{Y}\{N}, \frac\{X}\{M})}\{Var(\frac\{X}\{M})} $$ expanded to \\ $$ \frac\{Cov(\frac\{Y}\{\mu\_N}-\frac\{\mu\_Y N}\{\mu^2\_N}, \frac\{X}\{\mu\_M}-\frac\{\mu\_X M}\{\mu^2\_M})}\{Var(\frac\{X}\{\mu\_M}-\frac\{\mu\_X M}\{\mu^2\_M})} $$ This gives: $$ \frac\{\hat\{Y\_\{c}}}\{\hat\{N\_\{c}}}=\frac\{Y\_\{c}}\{N\_\{c}}-\theta( \frac\{X\_\{c}}\{M\_\{c}} - \mathbb\{E}\[R]) $$ $$ \frac\{\hat\{Y\_\{t}}}\{\hat\{N\_\{t}}}=\frac\{Y\_\{t}}\{N\_\{t}}-\theta( \frac\{X\_\{t}}\{M\_\{t}} - \mathbb\{E}\[R]) $$ Because $\mathbb\{E}\[R]$ is hard to derive, the expectation term is the same for both groups. Substituting $\mathbb\{E}\[R]$ with $\frac\{X\_\{c}}\{M\_\{c}}$ transforms the formulas above to the following two: $$ \frac\{Y\_\{cv}(control)}\{N\_\{cv}(control)}=\frac\{Y(control)}\{N(control)} $$ $$ \frac\{Y\_\{cv}(test)}\{N\_\{cv}(test)} \\\\ :=\frac\{Y(control)}\{N(control)} - (\frac\{Y(control)}\{N(control)} - \theta \frac\{X(control)}\{M(control)}) + (\frac\{Y(test)}\{N(test)} - \theta\frac\{X(test)}\{M(test)}) \\\\ :=\frac\{Y(test)}\{N(test)} - \theta\frac\{X(test)}\{M(test)} + \theta \frac\{X(control)}\{M(control)} $$ Using the optimal $\theta$, Statsig reduces group-level variance by applying the parameter to calculate the adjustment. Across-group $\theta$ doesn't necessarily reduce variance for one group, or the sum of variances of all groups, but in most cases it does. Simulations show that 98.3% of metrics saw a decrease through CUPED. Statsig uses CUPED variance when all of the following are met: * Core assumptions of the CUPED model are satisfied; rounding error or other data artifacts can violate this * E(X\_hat) = E(X) * The pooled variance of the adjusted population across groups is \< the variance of the unadjusted population * Enough units have pre-experiment values (> 100) * Enough percentage of units have pre-experiment values (> 5%) ================================================================================ # Winsorization URL: https://docs.statsig.com/experiments/statistical-methods/methodologies/winsorization Updated: 2026-07-11 ================================================================================ Winsorization is a technique for reducing noise in experiment results caused by outliers. Winsorization measures the percentile Px of a metric and sets all values above Px to Px. Statsig computes the Px value using all non-zero and non-null unit-level values of the metric. Metrics are aggregated from rows or events, and the Px'th unit's value is used as the threshold to adjust other units' values. {% tabs %} {% tab title="Statsig Cloud" %} At Statsig, the default percentile for winsorization is 99.9%. This reduces the influence of extreme outliers caused by factors such as logging errors or bad actors. Statsig applies winsorization to sum, event count, mean, ratio, and funnel metrics, including imported metrics. Winsorization doesn't apply to Participation or User Accounting metrics. {% /tab %} {% tab title="Warehouse Native" %} Statsig Warehouse Native lets you configure winsorization per metric, and choose the upper and/or lower bounds to apply. {% figure %} ![Winsorization configuration interface](/images/snippets/stats-methods/winsorization/6d058842-27f7-4b6b-9bd8-245a5f894f90.png) {% /figure %} Statsig applies winsorization to sum, event count, mean, and ratio metrics. {% /tab %} {% /tabs %} ## Metric capping Metric capping is a technique for handling outliers. You can define maximum values for a metric for any unit types configured for that metric. Statsig automatically adjusts any value that exceeds the cap downward to match the cap. For example, if purchases greater than $10,000 per day shouldn't skew analysis, Statsig adjusts any transaction exceeding that threshold downward to $10,000. Capped metrics are available for Event Count and Aggregation (sum) metric types. ================================================================================ # Confidence Intervals URL: https://docs.statsig.com/experiments/statistical-methods/confidence-intervals Updated: 2026-07-11 ================================================================================ Confidence intervals quantify the uncertainty in observed metric deltas. A 95% confidence interval contains the true effect 95% of the time. If an experiment ran 100 times, the true value of the metric delta would fall inside the confidence intervals 95 times. {% figure %} ![Confidence interval visualization showing statistical significance](/images/snippets/stats-methods/confidence-intervals/168382034-73afed14-d9f5-42cb-ade1-034526002a0b.png) {% /figure %} A 95% confidence interval that doesn't contain zero (the green bar above) represents a statistically significant result (with *α = 0.05*). This isn't always the case: there are cases when the p-value of the difference between test and control is statistically significant, but due to uncertainty in the control, a relative delta confidence interval may cross zero (using [The Delta Method](/experiments/statistical-methods/methodologies/delta-method)) or be represented as a point estimate (using [Fieller Intervals](/experiments/statistical-methods/methodologies/fieller-intervals)) while the absolute difference's p-value is statistically significant. Only 5% of the time would you expect to see the confidence interval exclude zero if the true effect was zero (a false positive). Larger confidence intervals imply less certainty in the exact size of the effect with a larger range of likely values. ## Computing confidence intervals Statsig calculates confidence intervals using a two-sample z-test. This test requires the variance of the metric delta being measured, which Statsig derives differently depending on the metric type (details at [Variance](/experiments/statistical-methods/variance)). After establishing the variance of the delta, you can compute the confidence intervals. ### Two-sided tests For the **absolute metric delta**, the confidence interval is given by: $$ CI(\Delta \overline\{X}) = \Delta \overline\{X} \pm Z\_\{\alpha/2} \cdot \sqrt\{\{var(\Delta \overline\{X})}} $$ where: * $Z\_\{\alpha/2}$ is the z-critical value for the desired significance level (1.96 for the standard $\alpha=0.05$ and 95% confidence interval) for a two-sided test * $var(\Delta \overline\{X})$ is the variance of the absolute delta (details [here](/experiments/statistical-methods/variance)) The confidence interval for the **relative metric delta** can use one of two methods: [Fieller Intervals](/experiments/statistical-methods/methodologies/fieller-intervals) or the [Delta Method](/experiments/statistical-methods/methodologies/delta-method). You can opt for either method. Statsig enables Fieller Intervals for all new customers by default. When using Fieller Intervals, compute the relative metric delta CI using: $$ CI(\\% \Delta \overline\{X} ) = \frac\{1}\{1-g} ( \frac\{\overline\{X\_T}}\{\overline\{X\_C}} - 1 \pm \frac\{Z\_\{\alpha/2}}\{\sqrt\{n\_C} \cdot \overline\{X\_C}} \sqrt\{(1-g) \cdot \frac\{var(X\_T)}\{n\_T(n\_T-1)} + \frac\{\overline\{X\_T} var(X\_C)}\{\overline\{X\_C} n\_C (n\_C-1)}}) $$ When using the Delta Method, the confidence interval is: $$ \begin\{split} CI(\Delta \overline X\\%) &= \Delta \overline X\\% \pm Z\_\{\alpha/2} \cdot\sqrt\{\{var(\Delta \overline X\\%)}}\\\\ &= \frac\{\Delta \overline X}\{\overline X\_c} \pm Z\_\{\alpha/2} \cdot\sqrt\{(\frac\{\overline X\_t}\{\overline X\_c})^\{2} \cdot (\frac\{var(X\_c)}\{n\_c \cdot \overline X\_c^2} + \frac\{var(X\_t)}\{n\_t \cdot \overline X\_t^2})} \cdot 100\\% \end\{split} $$ If using the Delta Method and the control mean is not significantly away from zero, then it's simplified to: $$ \begin\{split} CI(\Delta \overline X\\%) &= \Delta \overline X\\% \pm Z\_\{\alpha/2} \cdot\sqrt\{\{var(\Delta \overline X\\%)}} \\\\ &= \frac\{\Delta \overline X}\{\overline X\_c} \pm Z\_\{\alpha/2} \cdot \frac\{\sqrt\{\{var\left(\Delta \overline X\right)}}}\{\overline X\_c} \cdot 100\\% \end\{split} $$ ### One-sided tests When running one-sided tests, the confidence interval calculation changes to account for a redistribution of the desired false positive rate when looking for increases or decreases in the metric: $$ CI(\Delta \overline\{X}) = \begin\{cases} \left\[\Delta \overline\{X} - Z\_\{\alpha} \cdot \sqrt\{\{var(\Delta \overline\{X})}}, \quad +\infty \right) & \text\{if right-hand test}\\\\ \\\\ \left(-\infty, \quad \Delta \overline\{X} + Z\_\{\alpha} \cdot \sqrt\{\{var(\Delta \overline\{X})}} : \right] & \text\{if left-hand test} \end\{cases} $$ where: * $Z\_\{\alpha}$ is the z-critical value for the desired significance level (1.645 for the standard $\alpha=0.05$ and 95% confidence interval) for a one-sided test * $var(\Delta \overline\{X})$ is the same as for two-sided tests * the choice of confidence interval depends on if the one-sided test is looking for increases or decreases in the metric ## Welch's t-test for small sample sizes For small sample sizes, Statsig uses Welch's t-test instead of a standard z-test. Welch's t-test handles samples of unequal size or variance without increasing the false positive rate. The confidence interval calculation follows the same structure as the two-sample z-test (depending on whether the test is one- or two-sided), replacing the z-critical value with the t-critical value with degrees of freedom $\nu$. For a two-sided test, the confidence interval is therefore: $$ CI(\Delta \overline\{X}) = \Delta \overline\{X} \pm t\_\{\alpha/2} \cdot \sqrt\{\{var(\Delta \overline\{X})}} $$ $$ \nu = \frac\{\left(var(\overline X\_t) + var(\overline X\_c)\right)^2}\{\frac\{var(\overline X\_t)^2}\{N\_t - 1}+\frac\{var(\overline X\_c)^2}\{N\_c - 1}}\ \= \frac\{var(\Delta\overline\{X})^2}\{\frac\{var(\overline X\_t)^2}\{N\_t - 1}+\frac\{var(\overline X\_c)^2}\{N\_c - 1}} $$ Where $N\_t$ and $N\_c$ are the number of users in the test and control groups, respectively. For a large number of degrees of freedom, the t-statistic converges with the z-statistic. Therefore, Statsig uses Welch's t-test only when $\nu \< 100$. ## Comparing experiment data to a fixed baseline: one-sample t-test To answer questions such as "Does my test variant lead to a click-through rate higher than 0.5?", define a fixed-baseline comparison when adding metrics to the experiment. Statsig calculates the confidence interval as: $$ CI(\Delta \overline X) = (\overline X\_\{group} - fixed \ value) \pm Z \cdot\sqrt\{\{var( \overline X\_\{group})}} $$ ================================================================================ # p-Value Calculation URL: https://docs.statsig.com/experiments/statistical-methods/p-value Updated: 2026-07-11 ================================================================================ In Null Hypothesis Significance Tests, the p-value is the probability of observing an effect larger than or equal to the measured metric delta, under the assumption that the null hypothesis is true. A p-value below the pre-defined Type I Error threshold ($\alpha$) serves as evidence of a true effect. The methodology for p-value calculation depends on the number of degrees of freedom ($\nu$). A two-sample z-test is appropriate for most experiments. Statsig uses Welch's t-test for smaller experiments with $\nu \< 100$. In both cases, the p-value depends on the metric [mean](/experiments/statistical-methods/metric-deltas) and [variance](/experiments/statistical-methods/variance) computed for the test and control groups. Typically, a p-value below the threshold $\alpha$ occurs only when the confidence interval doesn't cross 0. However, an exception can occur in the Statsig UI: when the p-value of the difference between test and control is statistically significant, but uncertainty in the control causes a relative delta confidence interval to cross zero (using [The Delta Method](/experiments/statistical-methods/methodologies/delta-method)) or be represented as a point estimate (using [Fieller Intervals](/experiments/statistical-methods/methodologies/fieller-intervals)), while the absolute difference's p-value is statistically significant. ## Two-sample tests ### Two-sided z-test You can compute the z-statistic (a.k.a. z-score) of a two-sample z-test in multiple equivalent formats: $$ \begin\{split} Z &= \frac\{\overline X\_t - \overline X\_c}\{\sqrt\{var(\overline X\_t)+ var(\overline X\_c)}} \\\\ &= \frac\{\overline X\_t - \overline X\_c}\{\sqrt\{var(\Delta \overline\{X})}} \\\\ &= \frac\{\overline X\_t - \overline X\_c}\{\sqrt\{\sigma\_\{\overline\{X}\_t}^2 + \sigma\_\{\overline\{X}\_c}^2}} \end\{split} $$ where: * $Z$ is the observed z-statistic (not the z-critical value $Z\_\{\alpha/s}$) * $var(\Delta \overline\{X})$ is the variance of the absolute delta of means * $var(\overline\{X}\_i)$ is the variance of sample means either control or treatment group (details [here](/experiments/statistical-methods/variance)) * $\sigma\_\{\overline\{X}\_t}$ is the standard error of the mean of either control or treatment group (these are the terms you can find in Pulse under the Statistics tab of a metric) The two-sided p-value comes from the standard normal cumulative distribution function: $$ p-value = 2 \cdot \frac\{1}\{\sqrt\{2\pi}} \int \limits \_\{-\infty}^\{-|Z|}\{e^\{-t^2/2}dt} $$ ### Welch's t-test For smaller sample sizes, Welch's t-test is preferred because it produces lower false positive rates in cases of unequal sizes and variances. In Pulse, Statsig automatically applies Welch's t-test when the degrees of freedom $\nu \< 100$. Statsig computes the t-statistic (also known as t-score) identically to the two-sample z-statistic above. Statsig computes the degrees of freedom $\nu$ using: $$ \nu = \frac\{\left(var(\overline X\_t) + var(\overline X\_c)\right)^2}\{\frac\{var(\overline X\_t)^2}\{N\_t - 1}+\frac\{var(\overline X\_c)^2}\{N\_c - 1}}\ := \frac\{var(\Delta\overline\{X})^2}\{\frac\{var(\overline X\_t)^2}\{N\_t - 1}+\frac\{var(\overline X\_c)^2}\{N\_c - 1}} $$ Statsig then obtains the p-value from the t-distribution with $\nu$ degrees of freedom. ### One-sided z-test The procedure for a one-sided z-test computes the z-statistic $Z$ in the same way as the two-sided test above. The one-sided p-value comes from the standard normal cumulative distribution function, but with the following differences: $$ p-value = \begin\{cases} 1 - \frac\{1}\{\sqrt\{2\pi}} \int \limits \_\{-\infty}^\{Z}\{e^\{-t^2/2}dt} &\text\{if right-hand test}\\\\ \frac\{1}\{\sqrt\{2\pi}} \int \limits \_\{-\infty}^\{Z}\{e^\{-t^2/2}dt} &\text\{if left-hand test} \end\{cases} $$ where: * $Z$ is computed as shown in the two-sided test above. This uses the signed z-statistic, not the absolute value used in the two-sided p-value. ================================================================================ # Metric Deltas URL: https://docs.statsig.com/experiments/statistical-methods/metric-deltas Updated: 2026-07-11 ================================================================================ ## Computing metric deltas A metric delta refers to the difference in metric values between two groups, by default the test and control groups. This difference represents the impact measured in experiment results. To account for the different number of users (or units) in each group, Statsig compares the mean metric value per user, not the total. {% callout type="note" %} **Selecting Groups** All deltas are defined as the difference between a "treatment" group compared to a presumably unchanged "control" group. Statsig also enables you to compare any two groups. {% /callout %} Two different metric deltas are available in Pulse. The **absolute delta** is the difference between the two means: $$ \Delta \overline\{X}=\overline\{X}\_t-\overline\{X}\_c $$ Understanding the impact relative to the baseline value of the metric is often useful. For example, an absolute delta of +1 clicks/user has different meanings with a baseline value of 1 (+100% increase) vs. a baseline value of 100 (+1% increase). Statsig computes the **relative delta** using the mean of the control group as the baseline: $$ \Delta \overline\{X} \\%=\frac\{\overline\{X}\_t-\overline\{X}\_c}\{\overline\{X}\_c} \times 100 \\% $$ {% callout type="note" %} If you reverse the order of group comparison in Pulse to be "control" vs "treatment", then all deltas are reversed and the direction of change is inverted. {% /callout %} ## Computing means Computing group means correctly is critical for meaningful metric deltas. The methodology for calculating metric means depends on the metric type. ### Event count and sum metrics These metrics represent totals: number of times an event occurs, sum of time spent, total purchase amount, and similar values. The mean is the average user-level total during the analysis period. The mean value of the metric $X$ for a group is given by: $$ \overline\{X}=\frac\{1}\{N} \sum\_\{i=0}^N \sum\_\{d=0}^\{n\_i} X\_\{i, d} $$ where: * $N$ is the number of users in the group * $n\_i$ is the number of days during the analysis period that user $i$ was the experiment * $X\_\{i,d}$ is the metric value for user $i$ on day $d$ {% callout type="note" %} Statsig includes only user metrics recorded after a user has been exposed to the experiment in the group mean. {% /callout %} ### User accounting and event user metrics (and legacy event DAU) Event User metrics set to "Daily Participation Rate" capture the number of distinct users that have the event each day. In Pulse results, Statsig normalizes these values by the number of days the user is in the experiment. This represents the probability that a user is daily active for that event: the daily participation rate. The group mean is given by: $$ \overline\{X}=\frac\{1}\{N} \sum\_\{i=0}^N \frac\{1}\{n\_i} \sum\_\{d=0}^\{n\_i} X\_\{i, d} $$ where: * $X\_\{i,d}$ takes value 0 or 1 depending on if user $i$ has the event on a given day $d$. Statsig computes the following user accounting metrics in the same way: *DAU, WAU, MAU\_28day, L7, L14, L28* For new user accounting (*new\_DAU, new\_WAU, new\_MAU\_28day*), Statsig counts users that are new xAU at some point during the analysis window. The group mean is given by: $$ \overline\{X}=\frac\{1}\{N} \sum\_\{i=0}^N \max \left(X\_i\right) $$ Where $\max(X\_i)$ is the maximum value of the new xAU metric for user $i$. {% callout type="note" %} **event\_dau** metrics are now in legacy support only and Statsig no longer creates them for new events. Existing event\_dau metrics continue to be available for any of your new experiments and Statsig continues to compute them daily. For all new events, create an event\_user metric to measure daily active users. {% /callout %} ### Custom ratios, means, retention, and stickiness metrics These are metrics such as click-through rate, average purchase value, and sessions per user. Statsig computes them by dividing a numerator value, $X$, by a denominator value, $Y$. The mean value of a ratio metric $R$ for an experiment group is given by: $$ \overline\{R}=\frac\{\frac\{1}\{N} \sum\_\{i=0}^N \sum\_\{d=0}^\{n\_i} X\_\{i, d}}\{\frac\{1}\{N} \sum\_\{i=0}^N \sum\_\{d=0}^\{n\_i} Y\_\{i, d}}=\frac\{\overline\{X}}\{\overline\{Y}} $$ Where $N$ is the number of users in the experiment group that participate in the metric, i.e. have a non-zero denominator value. $X\_\{i,d}$ and $Y\_\{i,d}$ are the $X$ and $Y$ values for user $i$ on day $d$. Different approaches exist for dealing with ratio metrics in experiments. Statsig selected this implementation because it is statistically sound and interpretable, given that: * $R$ is the ratio of two means of independent observations: a set of user-level $X$ values and a set of user-level $Y$ values. The central limit theorem can therefore be used to separately obtain the summary statistics of $X$ and $Y$. * Statsig computes the group means in the same way as the topline metric value, making the means easier to interpret and relate to the topline metric. ### Event user one-time event For custom **event\_user** metrics with "One-Time Event" selected, Statsig computes how many users have the event at any time after the user enters the experiment. Statsig doesn't normalize this result by the number of days a user is in the experiment. The group mean is given by: $$ \overline\{X}=\frac\{1}\{N} \sum\_\{i=0}^N X\_\{i} $$ where: * $N$ is the number of users in the group * $X\_\{i}$ takes value 0 or 1 depending on if user $i$ has the event at any point after entering the experiment ================================================================================ # Standard Error & Mean Variance URL: https://docs.statsig.com/experiments/statistical-methods/variance Updated: 2026-07-11 ================================================================================ The standard error (also denoted "SE" or "std err") of the mean of each group is required to compute the confidence interval and p-value of a metric delta between those groups. You obtain the standard error of the mean by dividing the sample standard deviation of $X$ by the square root of the number of users in the group. $$ \sigma\_\{\overline X} = \frac\{\sigma\_\{X}}\{\sqrt\{N}} = \sqrt\{\frac\{var(X)}\{N}} = \sqrt\{var(\overline\{X})} $$ Standard deviation is the square root of the variance. Because variances are easier to manipulate algebraically, Statsig derives the variance for each metric type and then takes the square root to obtain the confidence intervals. Pulse displays the standard error of the mean of each group alongside the units and mean of each group. ## Computing variance The variance of the absolute metric delta is the sum of the variances of the test and control means: $$ var(\Delta \overline X) =var(\overline X\_t - \overline X\_c) = var(\overline X\_t) + var(\overline X\_c) $$ The calculation reduces to correctly computing the variance of the means for each group. ### Count and sum metrics For count and sum metrics, Statsig derives the variance of the sample mean for a given group directly from the sample variance: $$ var(\overline\{X}) = \frac\{var(X)}\{N} = \frac\{\frac\{1}\{N-1}\sum\_\{i=0}^\{N}(X\_i-\overline\{X})^2}\{N} $$ Where: * $N$ is the number of users in the group * $X\_i$ is the metric value for user $i$ * $\overline\{X}$ is the user-level average of $X$ for users in that group ### Ratio and mean metrics Ratio and mean metrics combine multiple variables $X$ and $Y$ rather than a single variable $X$. The variance of these metrics depends on both the numerator and denominator variables, which are typically correlated. The metric of interest $R$ has a group mean $\overline\{R}$ and a group variance of the mean $var(\overline\{R}$). For example, consider a *clicks per session* metric. The number of clicks and the number of sessions are two sets of observations from the same group of users, so they aren't independent of each other. To account for this correlation, Statsig obtains the variance of the mean of a ratio metric $R$ using the delta method: $$ var(\overline R) = var\left(\frac\{\overline X}\{\overline Y}\right) := \left(\frac\{\overline X}\{\overline Y}\right)^2 \cdot \left(\frac\{var(\overline X)}\{\overline X^2} + \frac\{var(\overline Y)}\{\overline Y^2} - 2 \cdot \frac\{covar(\overline X, \overline Y)}\{\overline X\cdot \overline Y} \right) $$ where Statsig computes the variance of the numerator and denominator means in the same way as for count metrics above, and the covariance is $$ covar(\overline X, \overline Y) = \frac\{covar(X, Y)}\{N} = \frac\{\frac\{1}\{N-1}\sum\_\{i=0}^\{N}(X\_i-\overline X)\cdot (Y\_i-\overline Y)}\{N} $$ ================================================================================ # Delta Method URL: https://docs.statsig.com/experiments/statistical-methods/methodologies/delta-method Updated: 2026-07-11 ================================================================================ ## Delta method for ratio metrics Statsig uses the delta method when calculating the variance for variables that have a numerator and denominator. The variance of ratio and mean metrics depends on the numerator and denominator variables, which are typically correlated. For example, consider a *clicks per session* metric. The number of clicks and the number of sessions are two sets of observations from the same group of users, so they aren't independent of each other. To account for this correlation, Statsig obtains the variance of a ratio metric *R* using the delta method: {% figure %} ![Delta method variance formula](/images/snippets/stats-methods/delta-method/167956015-cc3f9fca-2c4d-410c-bff1-3f13dd16d105.png) {% /figure %} where the variance of the numerator and denominator means are computed as described for count metrics, and the covariance is {% figure %} ![Covariance calculation formula](/images/snippets/stats-methods/delta-method/167956127-c17017ef-07b2-4f76-88c4-00539eec50a7.png) {% /figure %} ## Delta method for relative lifts Statsig may also use the delta method when calculating the confidence interval for relative lifts. The other methodology is [Fieller Intervals](/experiments/statistical-methods/methodologies/fieller-intervals). The delta method is a heuristic for Fieller Intervals that converges with a large population. ================================================================================ # Bot Traffic URL: https://docs.statsig.com/experiments/monitoring/bots Updated: 2026-07-11 ================================================================================ ## Bots and filtering A common source of frustration when monitoring gate traffic is online bot traffic from sources like search engines and AI scrapers, which makes it harder to measure how many real users are seeing your changes. Statsig has bot filtering in place to remove known bots from your exposures data, so the exposure counts and analytics you see reflect real users only. Bot filtering applies to all types of exposures data, not just feature flags. Analysis results for feature flags, holdouts, layers, and experiments all have bots filtered out. For more on bot filtering, refer to the [Statsig Blog](https://www.statsig.com/blog/guide-online-bot-filtering). After Statsig filters bot data from your exposures data, it isn't viewable in the Statsig console. Contact Slack support if you have additional questions. ### Controlling gates and experiments for bots By design, Statsig doesn't block bots from receiving your feature flags and experiments. Statsig filters their exposures from analysis data and the exposure count you see in Pulse. The API and SDK results for bots are unchanged; bots are served configs and variants following your setup. You may want to restrict what features bots see. For example, if you are testing a new homepage variant and don't want search engines to index it yet, you can do so through Segments: 1. Create a "Known Bots" Segment for your project: Create a new segment, ensuring that it will be a **conditional** segment. {% figure %} ![New conditional segment setup for capturing known bots](/images/experiments/monitoring/bots/5bc62f62-7613-41d2-bcca-6f89f13baa3a.png) {% /figure %} After creating the segment, add a new rule. Set Criteria to "Browser Name". Leave Operator as "Any Of". In the Values field, paste the following string in its entirety. (There is a copy button to the right.) When pasting, the Statsig console splits the list into individual bot names. ``` Scomplerbot, WincherBot, fixbot, keys-so-bot, MojeekBot, Gulper Web Bot, Mattermost-Bot, SerendeputyBot, uipbot, WebCrawler, HearsayPDFBot, WRTNBot, BublupBot, InsytfulBot, DingTalkBot, uk_ldfc_renderbot, crawlers, ImagesiftBot, idealo-bot, taboolabot, KlaxoonBot, SemrushBot, archiver/3.1.1 +http://www.archive.org/details/archive.org_bot, StractBot, crawler_eb_germany_2, exabot, DocBase Crawler, co Bot, Superfeedr bot, Pokey_Bot, GooglePlusBot, OtherwebBot, PubMatic Crawler Bot, SiteAuditBot, Gensparkbot, wpbot, archive.org_bot, Audisto Crawler, amazon-product-discovery-bot, Atomseobot, Googlebot-Mobile, hubspot crawler, XoviOnpageCrawler, PerplexityBot, QualifiedBot, YodaoBot, BitSightBot, GG PeekBot, SMTBot, amazonproductbot, FAST-WebCrawler, TwitterCommerceBot, WellKnownBot, PAGEFREEZER CRAWLER, dbot, htc_botdugls, RavenCrawler, oBot, notebot, ViberBot, KStandBot, scoopit-crawler, SpeechifyBot, Spider_Bot, txt Crawler, net/bot, BugBountyBot, Letianpai_Robot, by fynd.bot, discobot, LineBotWebhook, ahrefsbot, Veoozbot, tkbot, coccocbot, Googlebot-Video, Streamline3Bot, Zoombot, adbeat_bot, msnbot, Nextdoorbot, node DuckAssistBot, redditbot, Xing Bot, DocSearch Crawler, ; bot, Storebot, playwright-bot, 47_safeAreaBottom, online-webceo-bot, SmarshBot, BeeperBot, ChannelBot, BrightEdge Crawler, //boteden, Quantcastbot, SpringserveBot, IAS Crawler, managr-webcrawler, ) Bot, Dragonbot, crawler4j, tyseobotmobile, IVW-Crawler, SEBot, sap-search-web-crawler, AndersPinkBot, Dcard-link-preview-bot, Mediumbot, Light Crawler, dataforseobot, Better Uptime Bot, CCBot, es_bot, DuckAssistBot, SeznamBot, telegrambot, crawler, Jugendschutzprogramm-Crawler, SeoCherryBot, GroupMeBot, HyperMegaBotGettingOnlyHTMLsFromYourWebsite, our-crawler, Slackbot-LinkExpanding, YandexMobileBot, Web-Crawler, PaperLiBot, Swiftbot, Paqlebot, YandexRenderResourcesBot, MetaJobBot, SynologyChatBot, GenomeCrawlerd, robot, StatusCakeBot, node bitlybot, Your robot, Pharosbot, TSMbot, WalluBot, slackbot, AmazonAdBot, AspiegelBot, EzoicBot, TiggeritoBot, eventseekerBot, AwarioBot, Leikibot, Timpibot, like Gecko) bot, Quora-Bot, JobBot, googlebot, PetalBot, eu bot, LinkArchiver twitter bot, DF Bot, Screaming Frog Wise SEO Spider, Clickagy Intelligence Bot, BLP_bbot, bitlybot, WazzupCrawler, web-crawler, pingbot, yoozBot, triptease-bot, Plesk screenshot bot, Magus Bot, node Screaming Frog SEO Spider, YextBot, seobilitybot, tyseobot, applebot, bingbot, GetLocalBot, TwitterBot, rogerbot, Preview Service; bot, traq-ogp-fetcher-curl-bot, seesawbot, Greppr Web Crawler, ResearchBot, web-bot, iAskBot, JobboerseBot, CriteoBot, FandomOpenGraphBot, com feedbot, Amazon-Advertising-ad-standards-bot, MotoMinerBot, peer39_crawler, Discordbot, DuckDuckGo-Favicons-Bot, KeybaseBot, adsbot, Open Graph Bot, emulate-seobots, bountybot, InfobipCrawler, GoogleBot, macox bot, Google-bot, captify-crawler, Robot, aiHitBot, fedistatsCrawler, ExtendedStayBot, NetpeakCheckerBot, com/bots, Automattic Analytics Crawler, Blog Rssbot, Dubbotbot, Rightlander Crawler, ClarityBot, Cookiebot, UOrgTestingBot, AcademicBotRTU, SEMrushBot, Server Crawler, Diffbot, DiscourseBot, chatbot, VirusTotalBot, SaberBot, TZUnfurlBot, Mail.RU_Bot, Monsidobot, YandexAccessibilityBot, preview service; bot, ecoresearchCrawler, PulsePoint-Crawler, DataForSeoBot, petalbot, Xbot, LinkedInBot, GnowitNewsbot, vebidoobot, Bawaab_bot, Brightbot, ClineCrawler, ; Bot, MSIECrawler, MoodleBot, Testcrawler, AASA-Bot, GPTBot, StrapBot, 5) bot, mj12bot, screaming frog seo spider, HubSpot Crawler, COIBotParser, OcelotBot, com crawler, Pinterestbot, VelenPublicWebCrawler, Firefox superpagesbot2, Parser Robot, GrapeshotCrawler, Mediatoolkitbot, am a bot, semrushbot, SearchAtlas Bot, DiffeoBot, IBM-Crawler, spbot, DatoCmsSearchBot, SISTRIX Crawler, bountybotttt, Summalybot, ID bot, node AppleNewsBot, DotBot, TesseractBotAgent, foundeebot, BadooBot, BacklinksExtendedBot, wowLink Crawler, about-crawlers, find-seo-bot, GraphiteBot, Sidetrade indexer bot, BLEXBot, rc-crawler, FacebookBot, nerdybot, Senutobot, Facebot, //botim, WallabyupBot, TurnitinBot, XBot_Senior, node ZoominfoBot, AhrefsBot, Exabot, PhaverBot, Applebot, TermlyBot, SemjiBot, Space Unfurl Bot, Slackbot, bidswitchbot, //botsin, AdsBot-Google, Morningscore Bot, DuckDuckBot, UptimeRobot, ClaudeBot, naverbookmarkcrawler, PingdomBot, Web Crawler, PlurkBot, node GrowSEOBot, WebExplorerSearchBot, node FullStoryBot, WebwikiBot, bot, policy adbeat_bot, trendictionbot0, ezoicbot, Catrobatbot, AdsTxtCrawlerTP, com/bot, Nigooutbot, PiBot, pinterestbot, http-spiders-bot, Ocarinabot, msnbot-media, AppsFlyerBot, SeobilityBot, Impressumscrawler, SurdotlyBot, cXensebot, Amazonbot, Rankabot, 2ip bot, harsilbot, FullStoryBot, com bot, Rhobot, FreshpingBot, twitterbot, Twitterbot, Caliperbot, Googlebot-Image, osapon ) bot, yandexbot, MJ12bot, Taboolabot, ActiveComplyBot, MixrankBot, 48_safeAreaBottom, compatible; botify, LoomlyBot, Googlebot, ev-crawler, pagefreezer crawler, AwarioSmartBot, iCjobs Stellenangebote, jbot, aixnew_aibot, SemanticScholarBot, Wire LinkPreview Bot, Elastic-Crawler, UCMore Crawler, x28-job-bot, ISSCyberRiskCrawler, AnytypeBot, clever tech bot, LivelapBot, Screaming Frog SEO Spider, RyteBot, SiteGuruCrawler, XBot, SuperBot, TypetalkBot, RepoLookoutBot, obot, TimeTreeBot, siteauditbot, iASD_SpiderBot, semaltbot, PopeTech-ScanBot, SummalyBot, aka-bot, YandexBot, HatenaBlog-bot, Googlebot-News, yacybot, SiteCheckerBotCrawler, node AwarioSmartBot, turbotime, net-Robot, reurl-bot, Google-Display-Ads-Bot, node CCBot, fr_bot, CapterraBot, seo-audit-check-bot, gptbot, Testomatobot, Snap-URL-Preview (bot, robots, ZumBot, hstspreload-bot, Fedicabot, serpstatbot, Synapse (bot, NetSeer crawler, uk_ldfc_bot, PartnerOptimizer-bot, Scrapbox Bot, domainsbot, NE Crawler, startmebot, VelaBot, SeekportBot, Radius Compliance Bot, AdkernelTopicCrawler, Linkbot, AppleNewsBot, Jones Searchbot, DropboxPreviewBot, ZoominfoBot, edansbot, Baiduspider-render, Python Requests, YisouSpider, OAI-SearchBot, AliyunSecBot, Baiduspider, Bytespider, TikTokSpider, Claude-SearchBot, Qwantbot, Thinkbot, ABEvalBot, HanaleiBot, OpenindexSpider, VamosBot, StartmeBot, Bingbot, parisbot, BetaLiveCrawlBot, MetaBot, CharSiuBot, PlagAwareBot, io/bot, ShowUpCrawler, ScraperBot, Monibot, ShellBot, IbouBot, MatchboxBot, CookieYesbot, Stripebot, Clearscopebot, quillbot, Sogou web spider, Checker Spider, ``` {% figure %} ![Segment rule showing browser name filter populated with bot list](/images/experiments/monitoring/bots/97c1cdfe-9d65-42ae-acc7-ed47b583fce6.png) {% /figure %} This segment can then be reused for all your launches. 2. Apply the Segment to your Gates and Experiments: For Gates, create a new rule that controls the bot experience. {% figure %} ![Feature gate rule ensuring known bots fail the rollout](/images/experiments/monitoring/bots/d6b51af0-ecfc-49c4-9e48-73bd276836ef.png) {% /figure %} For experiments, create a Conditional Override that forces units in this segment to receive whatever version you want. {% figure %} ![Experiment conditional override mapping bot segment to control variant](/images/experiments/monitoring/bots/97ec1bcd-6f03-4b51-b0fe-9859f11559b1.png) {% /figure %} ### Opting out of bot filtering Statsig configures bot filtering at the project level. Admins can opt out of filtering through their console settings. {% figure %} ![Bot filtering settings interface](/images/experiments/monitoring/bots/4c171000-6733-4136-a383-4cfd2b738ccb.png) {% /figure %} ### Suggesting new bots to Statsig If you have discovered bots that Statsig doesn't include in the default set, or you have internal bots your company manages that you want applied to all bot filtering, reach out in Slack. ================================================================================ # One-Sample Test URL: https://docs.statsig.com/experiments/statistical-methods/methodologies/one-sample-test Updated: 2026-07-11 ================================================================================ ## One-sample tests (fixed-value test) A one-sample test compares a single sample of data against a known or hypothesized value to determine whether there is a statistically significant difference. Unlike A/B tests that compare two groups, one-sample tests evaluate whether a single group differs from a specific benchmark, target, or historical baseline. ## When to use one-sample tests One-sample tests are useful for comparing a single group against a known value: - **Single Group Events**: When only one group can trigger certain events (for example, feature usage or error types), compare against an expected baseline. - **Algorithm Testing**: Test whether an algorithm performs better than random (for example, whether a success rate differs from 50%). ## Statistical considerations One-sample tests provide a way to make statistical inferences about whether observed data differs significantly from a hypothesized value. The test helps determine whether any observed difference is due to random variation or represents a true change in the underlying process. ## Enable fixed-value baseline comparison 1. Go to the setup page of an experiment {% figure %} Experiment setup screen highlighting metrics section {% /figure %} 2. Click the metric name {% figure %} Metric name dropdown showing configure options {% /figure %} 3. Select **Use Fixed Baseline as Control** {% figure %} Fixed baseline control modal for one-sample test configuration {% /figure %} ================================================================================ # Fieller Intervals URL: https://docs.statsig.com/experiments/statistical-methods/methodologies/fieller-intervals Updated: 2026-07-11 ================================================================================ ## Fieller Intervals You can use Fieller Intervals as the methodology for calculating confidence intervals for the relative change between test and control groups. The Delta Method is an approximation for the variance of a ratio between two variables, which Statsig then uses to establish a confidence interval. Fieller Intervals are an exact solution for the confidence interval. In most cases, Fieller Interval results are very similar to results from the Delta Method. Because Fieller Intervals are more accurate, Statsig recommends using this methodology. ## Calculation ### 1: Determine if a Fieller interval is well-defined Before applying Fieller’s Theorem, verify that the denominator of the relative lift metric $\overline\{X\_C}$ is significantly distinct from 0. Calculate the parameter $g$: $$ g = \frac\{Z\_\{\alpha/2}^2 \cdot \mathrm\{var}(X\_C)}\{(n\_C-1) \cdot \overline\{X\_C}^2} $$ Where: $Z\_\{\alpha/2}$ is the critical value associated with the desired confidence level $\mathrm\{var}(X\_C)$ is the variance of the control group metric values $n\_C$ is the number of units in the control group $\overline\{X\_C}$ is the mean of the control group metric values When $g$ \< 1, the control mean is significantly different from 0, and you can use Fieller intervals. ### 2A: Apply Fieller interval formula Since the control and test group results are independent of each other, you can drop the covariance terms in Fieller's Theorem. $$ CI(\\% \Delta \overline\{X} ) = \frac\{1}\{1-g} \left( \frac\{\overline\{X\_T}}\{\overline\{X\_C}} \pm \frac\{Z\_\{\alpha/2}}\{\overline\{X\_C}} \sqrt\{ \frac\{\overline\{X\_T}^2}\{\overline\{X\_C}^2} \cdot \frac\{\mathrm\{var}(X\_C)}\{n\_C-1} + (1-g)\frac\{\mathrm\{var}(X\_T)}\{n\_T-1} } \right) - 1 $$ ### 2B: Edge case: control mean not statistically distinct from zero In rare cases (less than 5% of observed metric comparisons on Statsig), g $\geq$ 1, which means the control group’s mean isn’t statistically distinguishable from 0. When $\overline\{X\_C}$ isn't statistically different from zero, the denominator of the relative lift calculation is unstable. This means that the confidence interval for the percent difference between test and control is unbounded. When the confidence interval is unbounded, Statsig surfaces the relative lift observed during the experiment. $$ \\% \Delta \overline\{X} = \frac\{\overline\{X\_T}-\overline\{X\_C}}\{\overline\{X\_C}} $$ ## Enable Fieller intervals in Statsig Configure the relative confidence interval methodology in your *Experimentation Settings* at the organization level. Changing this setting only affects experiments created after the change. {% figure %} ![Experimentation settings configuration interface](/images/snippets/stats-methods/fieller-intervals/9c8b90f8-1f0b-472f-883d-c3f30bd78696.png) {% /figure %} In many cases, the results are effectively the same as using the [Delta Method](/experiments/statistical-methods/methodologies/delta-method), but Fieller Intervals are more reliable for experiments with small sample sizes or noisy denominators. Statsig strongly recommends using Fieller Intervals. In the experiment scorecard, Fieller Intervals appear as shown: {% figure %} ![Experiment scorecard with Fieller intervals](/images/snippets/stats-methods/fieller-intervals/1cb4ebb6-0425-49bf-a319-48794d25ce96.png) {% /figure %} ================================================================================ # Stratified Sampling URL: https://docs.statsig.com/experiments/advanced-setup/stratified-sampling Updated: 2026-07-11 ================================================================================ ## What is stratified sampling Stratified sampling involves dividing the entire population into homogeneous groups called strata (plural for stratum). Random samples are then selected from each stratum. For example, if you had XS and XL customers and randomized them into two groups (Control and Test), you would want both Control and Test to be balanced across XS and XL customers. You can also stratify based on a metric such as Revenue/User. With large numbers, randomization typically solves this balance problem. However, in B2B scenarios and other relatively low-volume or high-variance scenarios, stratified sampling ensures this balance. Statsig supports both automated and manual stratified sampling. In tests where a tail-end of power users drive a large portion of an overall metric value, stratified sampling meaningfully reduces false positive rates and makes results more consistent. In Statsig's simulations, this approach produced around a 50% decrease in the variance of reported results. ## Automated stratified sampling ### How it works The Statsig SDKs use a *salt* to randomize or bucket experiment subjects ([learn more](/faq#how-does-bucketing-within-the-statsig-sdks-work)). When you enable stratified sampling, Statsig tries n different salts (100 by default) and evaluates how balanced your groups are. Statsig evaluates this balance based on either a metric you select or an attribute you provide describing your experiment subjects, then picks and saves the best salt. [Learn more](https://statsig.com/blog/introducing-stratified-sampling). {% figure %} ![Stratified sampling algorithm diagram](/images/snippets/stats-methods/stratifiedSampling/99f72b83-9f14-45a3-aa6e-ffcbd6211ec7.png) {% /figure %} The selection space for the salts is large enough that stratifying multiple experiments on the same metric will not result in overlap. In Statsig's simulations, the groups were as independent as expected, which matched the literature on this topic. ### Enabling stratified sampling You can enable this on an experiment under Advanced Settings on the experiment setup page. There are two ways to stratify on Statsig. If you choose a metric to stratify on, Statsig uses that metric to balance the groups. {% figure %} ![Stratified sampling metric selection interface](/images/snippets/stats-methods/stratifiedSampling/0cfc499d-4fdf-44a8-ba2a-3537ba5bb904.png) {% /figure %} If you instead choose an attribute or a classification (for example, S, M, L, XL), Statsig uses that to balance the groups. * On Statsig Cloud, you'll upload a CSV (in Early Access) * On Statsig Warehouse Native, you'll use Entity Properties {% figure %} ![Entity properties configuration for stratified sampling](/images/snippets/stats-methods/stratifiedSampling/102a839f-37fd-4443-807a-4b269f137490.png) {% /figure %} After you select the Stratify button, Statsig analyzes a set of salts and picks the best one. {% figure %} ![Stratification analysis results interface](/images/snippets/stats-methods/stratifiedSampling/412f5c78-8c4f-4f16-88d3-60d3d3555ffd.png) {% /figure %} ## FAQ and best practices * **What population is used when balancing?** * When evaluating salts, Statsig computes balance using pre-experiment data for the entire targeted population of the experiment’s unit type (for example, all `userID`s or all `customerID`s) over the selected lookback window. There is no filtering on exposure because the experiment has not started yet. * **How are new units handled after stratification?** * Units that were not present in the pre-experiment data are still assigned deterministically by the chosen salt, i.e., effectively at random with respect to the balancing metric. They do not influence the salt selection and may introduce some drift from the initial balance. * **Should I use stratified sampling for every experiment?** * Not necessarily. Stratified sampling is most useful when you expect imbalance due to heterogeneous units (for example, “whales”) or skewed metrics. The tradeoff is time and compute cost that scales with the number of units and adds steps before starting an experiment. If you do not expect meaningful imbalance, a standard random split is generally recommended. * **Does salt evaluation assume 100% allocation? What about running at less than 100%?** * Yes. All candidate salts are evaluated assuming 100% of the targeted population is allocated. If you run the experiment at an allocation below 100%, random sampling of that subset can reintroduce imbalance (for example, by chance, some high-impact units may fall disproportionately into one arm). For the period you care most about inference, prefer 100% allocation to preserve the intended balance. Lower allocations are best used briefly for safe rollouts rather than for the full experiment duration. * **Across candidate salts, is it the same set of users being evaluated?** * Yes. Candidate salts are assessed over the same targeted population; only the randomization induced by the salt changes. * **How long does stratification take?** * Duration depends on the number of units and the metric/source being queried. There is no fixed SLA; larger populations take longer. ## Manual assignment for stratified sampling When setting up an experiment, you can configure overrides (for example, force user X or Segment A into Control, force user Y or Segment B into Test). Overrides are intended for testing; overridden users are excluded from experimental analysis in Pulse results. To use manual assignment for stratified sampling, select the *Include Overrides in Pulse* checkbox. This includes manually overridden users in each variant in all metric lift analyses. You can configure 100% of experiment participants into your test variants manually, or configure some subset of participants into variants manually and randomly assign the remainder. {% callout type="note" %} While you can add overrides for an ID type that is different than the ID type of the experiment, those ID evaluations will not be resolved to the id type of the experiment and will not contribute to pulse results. {% /callout %} When you use the Statsig SDK for assignment, it handles randomization. When you control assignment of users, you're responsible for making sure users are balanced across experiment groups. {% figure %} ![Manual assignment override configuration](/images/snippets/stats-methods/stratifiedSampling/230964234-8cc81f66-f4f8-4f37-b6df-6d36d0d7ab98.png) {% /figure %} ## Related resources [Morgan and Rubin 2012](https://projecteuclid.org/journals/annals-of-statistics/volume-40/issue-2/Rerandomization-to-improve-covariate-balance-in-experiments/10.1214/12-AOS1008.full) walks through the history, the philosophy, and the proofs of re-randomization, especially how re-randomization reduces the randomization variance of the difference in means. It's worth noting that "Standard asymptotic-based analysis procedures that do not take the re-randomization into account will be statistically conservative" was called out in the paper. However, to maintain consistent and comparable results across different methods, Statsig stays conservative with the t-test. [Lin & Ding 2019](https://arxiv.org/abs/1906.11291) is another interesting read for your reference. ================================================================================ # Frequentist Sequential Testing URL: https://docs.statsig.com/experiments/advanced-setup/sequential-testing Updated: 2026-07-11 ================================================================================ ## What's the problem with looking early in a "standard" A/B test Traditional A/B testing best practices (t-tests, z-tests, etc.) require that the readout of experiment metrics occur only once, when the target sample size is reached (that is, when your design duration has been reached and you have the sample size you need). This approach is called a "Fixed Horizon Test": when designing an experiment, you set the number of units you want to observe and commit to analyzing results only after the dataset is complete. Continuous experiment monitoring ("peeking") for the purpose of decision making results in inflated false positive rates (the peeking problem), which can be much higher than the expected rate at your desired significance level. ## How peeking increases decision error rates Continuous monitoring leads to inflated false positives because any time you consider ending an experiment early, you risk making an incorrect conclusion. At the core of a standard hypothesis test, you decide whether to "accept the null" hypothesis or "reject the null" hypothesis and accept the alternative. Any time you look early and allow the possibility of an early decision, you are potentially rejecting the null hypothesis even when the null hypothesis is correct. ## Why early results can be misleading Metric values and p-values always fluctuate to some extent due to noise during any experiment. Results can move into and out of statistical significance because of this noise, even when there is no real underlying effect. Noisy fluctuations result from random unit assignment and unpredictable user behavior, and can't be eliminated entirely. Noise levels vary by test, depending on what you are testing and who your users are. Tests also vary over time in the amount of noise they see, as adding more users and observing them longer tends to help random fluctuations even out. Peeking introduces selection bias when it causes an experimenter to adjust the readout date. When an experimenter makes any early decision about results (for example, "is the result stat-sig; can we ship a variant early?") the chances increase that the decision is based on a temporary snapshot of results that are always fluctuating. The experimenter is potentially selecting a stat-sig result that wouldn't appear if the data were analyzed only once at the full, pre-determined completion of the experiment. In frequentist A/B test procedures, early decisions can only increase the false positive rate (declaring an experimental effect when there is none), even when the intention is to make a less-biased decision. ## How Sequential Testing works for an A/B test In Statsig's implementation of Sequential Testing, Statsig automatically adjusts p-values and confidence intervals for each preliminary analysis window to compensate for the increased false positive rate associated with peeking, as shown in the Results tab: {% figure %} ![Sequential testing results visualization](/images/experiments/sequential_testing_example.png) {% /figure %} *In this example, the confidence intervals for each metric are expanded using the "wings" or "tabs". This is a quick visual indicator that sequential testing is enabled and shows how much the intervals have been expanded.* {% figure %} ![Statsig results table highlighting sequential testing adjusted confidence interval](/images/experiments/advanced-setup/sequential-testing/c233dc78-fc61-46cb-8068-e657853a3a1d.png) {% /figure %} *In this example, the sequential testing adjustment determines whether the indicated result is declared stat-sig.* The goal of Sequential Testing is to enable early decision making when observations are sufficiently strong to outweigh random fluctuations, while limiting the risk of false positives. Although peeking is typically discouraged, regular monitoring with sequential testing is valuable in some cases: * Unexpected regressions: When experiments have bugs or unintended consequences that severely impact key metrics, sequential testing helps identify these regressions early and distinguishes significant effects from random fluctuations. * Opportunity cost: When a significant loss may result from delaying the experiment decision (such as launching a new feature ahead of a major event or fixing a bug), sequential testing can support an early decision if key metrics show improvement. Use caution: an early stat-sig result for certain metrics doesn't guarantee sufficient power to detect regressions in other metrics. Limit this approach to cases where only a small number of metrics are relevant to the decision. {% callout type="tip" %} Sequential testing can be used anywhere you do an experimental analysis. This includes your main experimental Results page as well as any [custom queries](/experiments/interpreting-results/custom-queries). {% /callout %} ## Quick guides ### Enable sequential testing results In the **Setup** tab of your experiment, with Frequentist selected as your Analytics Type, enable Sequential Testing under the Analysis Settings section. You can toggle this setting at any time during the life of the experiment and don't need to enable it before the experiment starts. {% figure %} ![Sequential testing configuration interface](/images/experiments/enable_freq_sequential_testing.png) {% /figure %} ### Interpreting sequential testing results Click **Edit** at the top of the metrics section in Pulse to toggle Sequential Testing on/off. {% figure %} ![Pulse metrics sequential testing toggle](/images/experiments/advanced-setup/sequential-testing/191135447-5e094892-49e5-485e-8186-18732888662c.png) {% /figure %} When enabled, Statsig applies an adjustment to results calculated before the target completion date of the experiment. {% figure %} ![Sequential testing confidence interval visualization](/images/experiments/advanced-setup/sequential-testing/191135645-0042dced-3e8f-479f-8f63-c814dfbd4923.png) {% /figure %} The dashed line represents the expanded confidence interval resulting from the adjustment. The solid bar is the standard confidence interval computed without any adjustments. If the adjusted confidence interval overlaps with zero, the metric delta isn't stat-sig at the moment, and the experiment should continue as planned. Sequential testing is a reliable way to make an early decision, particularly for early detection of regressions. Early decision-making often results in underpowered lift estimates with a high degree of uncertainty. If making the correct decision is important, use statistically significant sequential testing results. If accurate measurement is important, wait for full power as estimated by your pre-experimental power calculation. Statsig doesn't calculate statistical power on post-hoc experimental results (refer to section "Post-hoc Power Calculations are Noisy and Misleading" in [Kohavi, Deng, and Vermeer, A/B Testing Intuition Busters](https://bit.ly/ABTestingIntuitionBusters)). ## How Statsig implements Sequential Testing ### Two-Sided Tests #### Confidence Intervals Statsig uses mSPRT based on the approach proposed by Zhao et al. in this [paper](https://arxiv.org/pdf/1905.10493.pdf). The two-sided Sequential Testing confidence interval with significance level $\alpha$ is given by: $$ CI^\*(\Delta \overline\{X}) = \Delta \overline\{X} \pm Z^\*\_\{\alpha/2} \cdot \sqrt\{V} $$ where * $Z^\*\_\{\alpha/2}$ is the z-critical value, modified for sequential testing: $$ Z^\*\_\{\alpha/2} = \sqrt\{\frac\{(V+\tau)}\{\tau}\left(-2\ln(\alpha/2)-\ln(\frac\{V}\{V+\tau})\right)} $$ * $V$ is the standard variance of the delta of means when computing [variance](/experiments/statistical-methods/variance). It can be obtained from the sample variance of the test and control group means: $$ V = var(\Delta \overline X) \= var(\overline X\_t) + var(\overline X\_c) \= \frac\{var(X\_t)}\{N\_t} + \frac\{var(X\_c)}\{N\_c} $$ * $\tau$ is the mixing parameter given by: $$ \tau \=(Z\_\{\alpha/2})^2\cdot\frac\{var(X\_t)+var(X\_c)}\{N\_t+N\_c} $$ * $Z\_\{\alpha/2}$ is the z-critical value used in the non-sequential test, for the desired significance level (1.96 for the standard $\alpha = 0.05$) Statsig has validated that this parameter satisfies the expected False Positive Rate and provides enough power to detect large effects early. More details on this analysis are available [here](https://www.statsig.com/blog/sequential-testing-on-statsig). #### p-Values To produce p-values for sequential testing that are consistent with the expanded confidence intervals above, modify the [p-value methods](/experiments/statistical-methods/p-value). The goal is to evaluate the mSPRT test so that the Type I error remains approximately equal to $\alpha$, and so that the sequential testing p-value is consistent with the expanded confidence interval. (I.e. A CI that includes 0.0% should have p-value ≥ $\alpha$, and one that excludes 0.0% should have p-value \< $\alpha$.) The observed z-statistic (i.e. z-score) remains unchanged. Instead of evaluating $Z$ on a standard-normal distribution $N(0, 1)$, Statsig evaluates against another normal distribution $N(0, \sigma^2)$ with mean of zero and standard deviation $\sigma$. For a two-sided test, to limit the probability of an observed $Z$ exceeding $Z^\*\_\{\alpha/2}$ (assuming the null hypothesis to be true) to $\alpha$, you can find the unknown parameter by solving for $\sigma$: $$ \sigma=\frac\{Z\_\{\alpha/2}^\*}\{\sqrt\{2} \cdot erf^\{-1}(1-\alpha)} $$ where $erf^\{-1}$ is the [inverse error function](https://en.wikipedia.org/wiki/Error_function#Inverse_functions). From here we can compute the two-sided sequential testing p-value as: $$ \text\{p-value}^\* = 2 \cdot \frac\{1}\{\sqrt\{2\pi}} \int \limits \_\{-\infty}^\{-|Z|} \frac\{e^\{- \frac\{t^2}\{\{2\sigma^2}}}}\{\sigma}dt $$ where $Z$ is the observed z-statistic (i.e. z-score) as usual. ### One-Sided Tests Statsig modifies each step for one-sided sequential testing. $$ CI^\*(\Delta \overline\{X}) = \begin\{cases} \left\[\Delta \overline\{X} - Z^\*\_\{\alpha} \cdot \sqrt\{V}, \quad +\infty \right) & \text\{if right-sided test} \\\\ \\\\ \left(- \infty, \quad \Delta \overline\{X} + Z^\*\_\{\alpha} \cdot \sqrt\{V} :\right] & \text\{if left-sided test} \\\\ \end\{cases} $$ $$ \text\{p-value}^\* = \begin\{cases} 1 - \frac\{1}\{\sqrt\{2\pi}} \int \limits \_\{-\infty}^\{Z} \frac\{e^\{- \frac\{t^2}\{\{2\sigma^2}}}}\{\sigma}dt \quad \text\{if right-sided test} \\\\ \\\\ \frac\{1}\{\sqrt\{2\pi}} \int \limits \_\{-\infty}^\{Z} \frac\{e^\{- \frac\{t^2}\{\{2\sigma^2}}}}\{\sigma}dt \quad \text\{if left-sided test} \\\\ \end\{cases} $$ where * $Z^\*\_\{\alpha}$ is the one-sided test z-critical value, modified for sequential testing: $$ Z^\*\_\{\alpha} = \sqrt\{\frac\{(V+\tau)}\{\tau}\left(-2\ln(\alpha)-\ln(\frac\{V}\{V+\tau})\right)} $$ * $V$ is the same as for two-sided tests. * $\tau$ is the mixing parameter given by: $$ \tau \=(Z\_\{\alpha})^2\cdot\frac\{var(X\_t)+var(X\_c)}\{N\_t+N\_c} $$ * $Z\_\{\alpha}$ is the one-sided z-critical value used in the non-sequential test, for the desired significance level (1.645 for the standard $\alpha = 0.05$) * $\sigma$ is solved via: $$ \sigma = \begin\{cases} \frac\{Z\_\{\alpha}^\*}\{\sqrt\{2} \cdot erf^\{-1}(1 - 2 \alpha)} & \text\{if right-sided test} \\\\ \\\\ \frac\{- Z\_\{\alpha}^\*}\{\sqrt\{2} \cdot erf^\{-1}(2 \alpha - 1)} & \text\{if left-sided test} \end\{cases} $$ * $Z$ is the (signed) observed z-statistic as usual (i.e. z-score) ================================================================================ # Bayesian Experiments URL: https://docs.statsig.com/experiments/advanced-setup/bayesian Updated: 2026-07-11 ================================================================================ ## How Bayesian testing works in Statsig Experiments are frequentist by default. To switch to Bayesian mode, go to Advanced Settings. {% figure %} ![Bayesian experiment configuration interface](/images/experiments/advanced-setup/bayesian/c9c01a57-fe13-47a9-b734-20d6e8d715a4.png) {% /figure %} You can't modify the experiment type after the experiment starts. {% figure %} ![Bayesian experiment type selection interface](/images/experiments/advanced-setup/bayesian/be912632-6200-4408-977c-92f48dfdd7bc.png) {% /figure %} Deep-dive analysis reflects Bayesian statistics. {% figure %} ![Bayesian deep dive analysis interface](/images/experiments/advanced-setup/bayesian/c9214142-d11f-48c8-92a4-53581bbc498c.png) {% /figure %} ### Informed Bayesian Bayesian experiments let you specify a prior belief on the relative average treatment effect. Statsig combines the prior distribution with the observed data to display prior-adjusted results. To enable this, select the "use informative priors" option. {% figure %} ![Informative priors configuration interface](/images/experiments/advanced-setup/bayesian/0aa0a52c-4f97-42af-82dd-4d26dd1de7c0.jpg) {% /figure %} ### Drawing the correct prior distribution from historical data When using Bayesian with informative priors, your organization must understand what influence the priors have over your experimental results and must have established a reliable prior based on domain knowledge. Use these patterns to derive priors: 1. You can use the $AVG(\text\{average treatment effect})$ of past experiments with a similar setup and population as your prior mean. You can use the standard deviation, or a multiple of it, as the prior standard error. 2. You can also use the $AVG(\text\{observed standard error})$ as your prior standard error. ### Implementation details Denote $\mathcal\{N}(ATE\_\{prior}, STE\_\{prior}^2)$ as the prior distribution, where $ATE\_\{prior}$ is the average treatment effect and $STE\_\{prior}$ is the standard error. Similarly, $\mathcal\{N}(ATE\_\{observed}, STE\_\{observed}^2)$ as the observed distribution. The posterior distribution is then calculated as $$ ATE\_\{post} = \frac\{ \frac\{ATE\_\{prior}}\{STE\_\{prior}^2} + \frac\{ATE\_\{observed}}\{STE\_\{observed}^2} }\{ \frac\{1}\{STE\_\{prior}^2} + \frac\{1}\{STE\_\{observed}^2} } $$ $$ STE\_\{post}^2 = \frac\{1}\{ \frac\{1}\{STE\_\{prior}^2} + \frac\{1}\{STE\_\{observed}^2} } $$ If the prior is not specified, the $\mathcal\{N}(ATE\_\{prior}, STE\_\{prior}^2)$ is represented as $\mathcal\{N}(0, \infty)$. ### Bayesian statistics glossary Bayesian A/B tests use different terminology from the frequentist framework. These terms are often more intuitive for communicating results to non-technical audiences. | Term | Definition | |---|---| | Credible Interval | The interval that contains the true parameter at the given probability | | Chance to Beat | The probability that the test is better than control | | Expected Loss | The average potential risk if you ship the test variant | ================================================================================ # Sequential Probability Ratio Tests URL: https://docs.statsig.com/experiments/advanced-setup/sprt Updated: 2026-07-11 ================================================================================ ## What is SPRT? The **Sequential Probability Ratio Test** (SPRT) is an advanced methodology for running AB tests that differs from the traditional Null Hypothesis Significance Test (commonly called [Frequentist](/experiments/statistical-methods/p-value) analysis). SPRT can meaningfully improve time to decision for your experiments, including detecting unwanted metric regressions much faster. SPRT results are also easier to share with stakeholders who are not familiar with P-values and significance levels. SPRT has no penalties for peeking; there is no need for sequential testing plans, alpha spending, or CI-penalties because SPRT is built as a sequential test methodology from the start. {% figure %} ![SPRT experiment results scorecard](/images/experiments/sprt/sprt_scorecard.png) {% /figure %} ### Concepts SPRT introduces a few key concepts that differ from standard Frequentist tests. At its core, SPRT relies on the **Likelihood Ratio (LR)** and Upper and Lower decision boundaries, **A** and **B**. The Likelihood Ratio estimates the relative difference in the likelihood of two outcomes: - **Numerator**: What you observe is due to an alternative hypothesis (you set) being correct. - **Denominator**: What you observe is due to the null hypothesis being correct. The Upper and Lower decision boundaries are determined by your joint tolerances for Type I and Type II errors. - **A**: If LR exceeds this upper threshold, accept the Alternative Hypothesis. - **B**: If LR is less than this lower threshold, accept the Null Hypothesis. - When LR falls into the range between these thresholds, no decision can be made and you should continue collecting data. {% figure %} ![SPRT Hover Card](/images/experiments/sprt/sprt_hover_card.png) {% /figure %} An LR of 5.8, for example, indicates that what you observed is 5.8x more likely under the alternative hypothesis than under the null hypothesis. The Likelihood Ratio aligns with how most people think about comparing options. Rather than reporting P-values and significance levels, you can report a result such as "_With an LR of 3.5, it's 3.5x more likely that the feature worked._" ## Why SPRT? - **Faster decisions:** SPRT allows you to reach conclusions more quickly, potentially reducing experiment run time. - **Intuitive results:** Instead of p-values, SPRT uses the Likelihood Ratio, a more intuitive measure of evidence for or against your hypotheses. - **Sequential analysis:** Statsig continuously evaluates data as it's collected, allowing for early stopping when sufficient evidence is reached. There's no penalty for “peeking” in SPRT experiments. - **Clear outcomes:** SPRT enables you to confidently accept either the Null or Alternative hypothesis, rather than just “rejecting the null.” - **Data-informed:** Statsig’s implementation uses your past data and power analysis to inform the likelihood calculations and decision thresholds. ## Comparing SPRT to other analysis methods | Category | Frequentist | Bayesian | SPRT | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Test Statistic | P-value: _Probability of observing the results that are as extreme as the sample data if the null hypothesis is true_ | Posterior Probability: _Probability of Test better than Control given the observed data and your prior information_ | Likelihood Ratio: _Comparing the goodness of fit of two competing statistical models_ | | Decision Threshold | Alpha (Industry standard 5%) | Posterior Probability and Credible Intervals | Upper & Lower Decision Boundary decided based one the alpha and beta you picked | | Decision Framework | Reject/Fail to Reject the Null based on if p-value > 5% | Whether chance to beat control exceeds the pre-set decision threshold | Accept the Null Hypothesis, Accept Alternative Hypothesis, Or Continue based on the comparison of calculated likelihood ration with Upper/Lower Decision boundary | | Allows Peeking | Yes, but with Sequential Testing Penalties | Yes, Unlimited | Yes, Unlimited | | Requires Pre-Setup | Yes, requires sample size calculation based on historical metric mean and MDE | Optional, but you can define prior distribution per metric if you have previous knowledge which can accelerates the experiment or correct surprising results | Yes, requires historical information about each metric as well as MDE | | Allows 1- and 2-Sided tests | Yes, per metric | Yes, per metric | Yes, per metric | ## How to use SPRT in Statsig **Enabling SPRT:** Select SPRT as your analysis method when setting up an A/B test in the Statsig console. {% figure %} ![SPRT power analysis configuration interface](/images/experiments/sprt/sprt_power_analysis.png) {% /figure %} **Interpreting Results:** The experiment Results tab shows the latest likelihood ratio for each metric and indicates when a decision boundary has been reached, letting you accept the null or alternative hypothesis with confidence. {% figure %} ![SPRT experiment results dashboard](/images/experiments/sprt/sprt_results_readout.png) {% /figure %} ## Computing SPRT Results Statsig uses an updated version of Hajnal's two-sample t test ([Schnuerch and Erdfelder](https://martinschnuerch.com/wp-content/uploads/2020/08/Schnuerch_Erdfelder_2020.pdf), as modified by Derek Ho of Atlassian) in its SPRT calculations. The traditional ratio test using t- or F-distributions (Schnuerch and Erdfelder equations 8 & 10) can be shown to simplify to a ratio of standard-normal distributions. $$ \begin\{split} \{LR} &= \frac \{f(t^2| \{df}, \Delta)} \{f(t^2| \{df}, 1)} \\\\ &= \frac \{\phi(|z\_\{m}|; \theta, 1)} \{\phi(|z\_\{m}|; 0, 1)} \\\\ \end\{split} $$ where: - $\phi(x; \theta, 1)$ is the PDF of a normal distribution of shape $\mathcal\{N}(\theta, 1)$ evaluated at $x$ - $z$ is the observed Z-statistic between the groups $$ z = \frac \{\Delta \bar\{X}} \{\sigma\_\{\Delta\bar\{X}}} \= \frac \{\bar\{X}\_B - \bar\{X}\_A} \{\sigma\_\{\Delta\bar\{X}}} $$ $$ \sigma\_\{\Delta\bar\{X}}=\sqrt\{\frac\{var(X\_A)}\{N\_A}+\frac\{var(X\_B)}\{N\_B}} $$ - $\theta$ is derived from **Cohen's d** set prior to the experiment for the particular metric being considered $$ \theta = \frac \{\delta} \{\sqrt\{ \frac\{1}\{N\_A} + \frac\{1}\{N\_B} }} $$ - $N\_A$ and $N\_B$ are the number of observed units for each group Since log likelihood ratios are a more convenient scale for reporting, taking the natural log of the LR value above simplifies the equation further: $$ \begin\{split} LLR &= ln(LR) \\\\ &= |z\_m \cdot \phi\_m | - \frac\{1}\{2}\{|\phi\_m|}^2 \end\{split} $$ ### Power analysis and setting Cohen's d SPRT requires that a value of [**Cohen's d**](https://en.wikiversity.org/wiki/Cohen%27s_d) be set prior to the start of the experiment for each metric being evaluated. Setting the parameter requires three components: - **MDE**: A Minimum Detectable Effect to be measured, in units of percent - **Mean**: A baseline average value for the metric, $\overline\{X}$ - **Standard Deviation**: A baseline standard deviation for the metric, $\sigma\_\{X}$ With these values, you can compute Cohen's d parameter for each metric: $$ \delta = \frac\{\text\{MDE\\%} \cdot \overline\{X}}\{100 \cdot \sigma\_\{X}} $$ You can automate this process using Statsig's built-in query tooling. If you have a past experiment that ran on a similar set of units expected in the upcoming experiment, configure that experiment as a **Baseline Experiment** and a query automatically pulls the relevant metric parameters. You can also input all three parameters manually. ### Estimating the decision sample size Cohen's d is used to compute experimental results after the experiment starts, but it can also estimate experiment duration in advance. Because SPRT allows you to look at results as often as desired, this is not the same as a "required sample size" in traditional frequentist testing. The **Decision Sample Size** is an estimate of the number of samples sufficient for an SPRT result for a metric to exceed either threshold and accept one of the hypotheses. Given: $$ A=ln\left(\frac\{1-\beta}\{\alpha}\right) $$ $$ k=\frac\{n\_\{ec}}\{n\_\{et}}=\frac\{\text\{units expected in control}}\{\text\{units expected in treatment}}=\frac\{\text\{\\% units expected in control}}\{\text\{\\% units expected in treatment}} $$ $$ n\_\{et} = \frac\{A}\{\frac\{1}\{2}\left(\frac\{k}\{1+k}\right)\delta^2} $$ Then, the total number of expected units at decision time is: $$ n\_e=n\_\{et}+n\_\{ec}=n\_\{et}(1+k) $$ ## Related resources - [Original SPRT Paper (Wald, 1945)](https://projecteuclid.org/journals/annals-of-mathematical-statistics/volume-16/issue-2/Sequential-Tests-of-Statistical-Hypotheses/10.1214/aoms/1177731118.full) - [The Sequential Probability Ratio t Test (Schnuerch & Erdfelder, 2020)](https://martinschnuerch.com/wp-content/uploads/2020/08/Schnuerch_Erdfelder_2020.pdf) - [A two-sample sequential t-test (Hajnal, 1961)](https://www.jstor.org/stable/2333131) ## FAQ **Can I use SPRT for all experiments?**\ SPRT is best suited for experiments where you want faster, sequential decisions and are comfortable with likelihood-based inference. For some experiment types, traditional methods may still be preferable. **How does SPRT affect experiment duration?**\ SPRT can reduce experiment duration, especially when there is strong evidence for or against an effect. However, if the effect is small or data is noisy, the test may run longer. **What are the limitations?**\ SPRT requires careful setup of thresholds and assumptions. It isn't a drop-in replacement for all frequentist methods, and may not be suitable for all experiment types. **Is SPRT the same as Sequential Testing?** SPRT is different from the Sequential Testing option. [Sequential Testing](/experiments/advanced-setup/sequential-testing) adjusts your Frequentist analysis method to allow repeated looks (i.e. "peeking"). SPRT is a completely separate experimental procedure and decision framework. They both allow for continuous "sequential" looking at experiment results, but otherwise they are separate methods for designing and running an A/B test. ================================================================================ # Ending an Experiment URL: https://docs.statsig.com/experiments/ending/ending-experiment Updated: 2026-07-11 ================================================================================ When an experiment ends, one of two situations typically applies: * Your experiment has statistically significant results and it’s time to ship an experience (control or test) to all your users. * Your experiment has a problem and you need to stop it. ## Making a decision To ship any of the groups in your experiment, including control, to all your users, [Make a Decision](/experiments/ending/make-decision) on the experiment. This ships the parameters of the selected variant to all users, and if the experiment is in a layer, Statsig updates the layer defaults to reflect your shipped experience. ## Stopping an experiment If your experiment has a problem and you need to stop it, there are two options. * If the problem means you no longer want to run this test, [Abandon the experiment](/experiments/ending/abandon). This puts the experiment in a finished state and all users fall back to defaults in code. You can still Reset it later if you decide to revisit the experiment. * If one of the groups has a bug you plan to fix and you want to restart the experiment later, Reset the experiment. Alternatively, you can [disable a group](/experiments/implementation/disable-group) with the bug if you want to keep the other test groups running. Resetting puts the experiment into an unstarted state. Every user receives the default experience (either the value defined in code or the layer default). The "salt" used to randomize a user’s group also changes. When you start the experiment again, Statsig randomly assigns users to a group that isn’t necessarily the same group they were in before the reset. This ensures that a group that didn’t perform well due to a bug or bad experience in the previous run isn’t negatively affected after you address the issue. ## Archiving an experiment After you make a decision on an experiment, or when you abandon or reset it, you can archive the experiment. Archiving preserves the experiment’s history and results and makes the experiment read-only. If you later decide to make the experiment active again, unarchiving the experiment makes it editable but doesn’t automatically restart it. If the unarchived experiment was previously part of a layer and you intend to include it again, you must manually reset allocation from that layer. ================================================================================ # Make a Decision URL: https://docs.statsig.com/experiments/ending/make-decision Updated: 2026-07-11 ================================================================================ Making a decision for an experiment ships the winning group to all your users. After you make the decision, the variant that users see depends on whether you're using a **targeting gate** for your experiment. Experiment results remain accessible after you make a decision, but they stop updating. The last day of metrics is the day you make a decision on the experiment. ## Experiments with no targeting gate When you ship a group in an experiment with no targeting gate, the parameter values from the shipped group become the default values for all your users going forward. If the experiment uses parameters from a layer, the layer's parameters take on the shipped group's parameter values as their defaults. These are the values that all your users see going forward. For example, suppose you have a **Demo Layer** configured with a parameter, **a_param**. Its default value is set to _layer_default_ as shown below. {% figure %} Layer configuration showing default parameter value before experiment {% /figure %} To run an experiment in **Demo Layer**, create **Demo Experiment** as shown below. {% figure %} Experiment creation dialog in Demo Layer with control and test groups {% /figure %} You set up **Demo Experiment** with two groups: **Control** and **Test**, intending to experiment with new values for the layer-level parameter, **a_param** as shown below. {% figure %} Experiment parameter table comparing control and test values for a_param {% /figure %} Now if you decide to ship the **Control** group for the **Demo Experiment**, **a_param** takes the value set for the **Control** group as its default: _experiment_one_control_ {% figure %} Layer defaults updated to use control group value experiment_one_control {% /figure %} On the other hand, if you decide to ship the **Test** group, **a_param** takes the value set for the **Test** group as its default: _experiment_two_test_ {% figure %} Layer defaults updated to use test group value experiment_two_test {% /figure %} ## Experiment with a targeting gate or targeting rules When you ship a group in an experiment configured with a targeting gate or targeting rules, you can decide whether to continue targeting after shipping. - If you _discontinue_ targeting, the parameter values from the shipped group become the default values for all your users going forward. If the experiment uses parameters from a layer, the layer's parameters take on the shipped group's parameter values as their defaults. - If you decide to _continue_ targeting with a **targeting gate**, Statsig adds an override to the experiment layer so that: - all users who **pass** the targeting gate see the shipped group's parameter values - all users who **fail** the targeting gate see the default value (layer-level parameter defaults or the defaults you set for the parameter in your code) - If you decide to _continue_ targeting with **targeting rules**, the experiment is only shipped to users who pass the inline targeting rules as set in the experiment setup. {% callout type="info" %} **Shipping with Targeting On** If you decide to continue targeting, shipping a group doesn't update the default value of any layer parameters. {% /callout %} For example, suppose **Demo Experiment** is in **Demo Layer**, which has a parameter called **targeted_layer_param** with a default value of _targeted_layer_default_value_. When you ship **Demo Experiment** and discontinue targeting, **targeted_layer_param** takes on the value from the **Control** group, _targeted_layer_control_, as its default. {% figure %} Targeted layer parameter default switching to control value when targeting disabled {% /figure %} If you continue targeting, **targeted_layer_param** acquires an override so that: - all users who **pass** the targeting gate see the _overridden_ value of the parameter - all users who **fail** the targeting gate see the _default_ value of the parameter In this case, the default value of **targeted_layer_param** in **Demo Layer** doesn't change. Users who pass the targeting gate aren't eligible for future experiments run in this layer. Shipping experiments with targeting on is not recommended, especially when the experiment is in a layer. {% figure %} Layer override showing targeted users receiving shipped parameter while others keep default {% /figure %} ## Rolling out an experiment group Rolling out an experiment group is an option when you have decided the winning variant but want to avoid a sudden, large shift of traffic into the winning variant group. Use automated rollouts to schedule gradual rollout phases, which increase your shipped group size to the target percentage by reallocating users from all other groups proportionally. ### Setting up rollouts To set up rollouts, open the make decision form and select the winning group. From here you can use automated rollouts or ship with rollout. {% figure %} ![Make decision rollout options interface](/images/experiments/ending/make-decision/549864e9-0509-4b44-a588-fd10ace1e365.png) {% /figure %} The ship with rollout option immediately updates the shipped group size. Manual rollouts clear any automated rollout phases. {% figure %} ![Ship with rollout configuration screen](/images/experiments/ending/make-decision/f200b795-0442-49ed-8c13-e220b3a77a81.png) {% /figure %} Alternatively, you can set up automated rollouts, which opens the following dialog to populate with rollout phases: {% figure %} ![Automated rollout phases configuration dialog](/images/experiments/ending/make-decision/6fab7c0e-2566-4d14-bdb6-b94102fb08ac.png) {% /figure %} Configure each phase of your Scheduled Rollout. To add phases to your rollout, select **Add Phase** and configure as many phases as needed. Each scheduled rollout phase includes: - Rollout date - Rollout time\* - Pass percentage {% callout type="note" %} Rollout times are available in 15 minute increments. Additionally, each configured phase represents a discrete increase to the next rollout percentage, not a gradual rollout amortized over the course of the entire phase. {% /callout %} After saving, you can preview the rollout and commit the schedule: {% figure %} ![Rollout schedule preview and commit interface](/images/experiments/ending/make-decision/f1fd04b3-e6a0-4e32-91a8-fb24279ab923.png) {% /figure %} ### Resizing logic During each phase, the rollout group is resized to the desired percentage, and all other groups are scaled proportionally in the following way: You have _n_ groups with sizes: G₁, G₂, ..., Gₙ Their total sum is: 100 = G₁ + G₂ + ... + Gₙ. Now, suppose you **set one group** (say the k-th group) to a **new size** Gₖ′. Let Delta = Gₖ′ − Gₖ. Because the **grand total** must remain 100, you need to **adjust the remaining groups** proportionally. Let T = 100 - Gₖ Then, for each group i ≠ k, Gᵢ′ = Gᵢ − (Delta × Gᵢ / T). In other words, each group other than k is decreased (or increased, if Delta \< 0) by its fraction of T. ### Notes 1. Experiment results are frozen to a snapshot of when the rollout decision is made. 2. Rolling out a group to 100% doesn't fully ship the experiment; configurations such as experiment/layer allocation, targeting, and overrides don't change. To fully ship the experiment, go through the usual flow without specifying any rollouts. 3. Groups can be rolled out and back, but the rollout percentage can't be lower than the group's original size. {% callout type="info" %} This is currently an Early Access feature. {% /callout %} ## Shipping with a holdback Shipping with holdback lets you release an experiment variant (the “shipped” group) to most of your users while keeping a percentage in the control group for ongoing comparisons. 1. Make a decision and select a group to ship: From the Make Decision dropdown, choose which variant you want to ship. 2. Turn on the Ship with holdback option and specify the percentage of users you want to keep in the control (holdback) group. {% figure %} Ship with holdback dialog specifying control percentage in Make Decision modal {% /figure %} 3. Allocation of users: The control (holdback) group is set to your specified percentage. The remaining users are assigned to the shipped experience. 4. Splitting the shipped group: The shipped group is divided into two segments: Test and Launched. - The size of Test segment has the same percentage allocation as the holdback group for an [equal sized comparison](/experiments/holdouts-introduction#how-to-read-holdouts) of 50:50. - The Launched segment no longer appears in the pulse results, but users in this segment continue to receive the shipped experience. {% figure %} Pulse results display separating test and launched segments when shipping with holdback {% /figure %} 5. Continue monitoring the pulse results that evaluate the Test segment vs. Control (holdback). 6. When you decide to end the holdback, you can make a decision to ship the variant to everyone, which includes users in holdback. {% callout type="note" %} Some users currently in control will move to the shipped group to achieve the desired allocation. However, any user who has previously been in a test or shipped group won't be reassigned to control. New users (those who have never been exposed to the experiment) are assigned based on the allocation percentages of each group. {% /callout %} The new pulse results (Test segment vs. Holdback) start when you ship with holdback. Your original experiment's results from the point of the holdback decision are retained and remain accessible. From the experiment history, you can see the new log of the experiment decision and a **View Results Snapshot** button where you can view the read-only snapshot of original results. ![Snapshot of original experiment when using ship with holdback](/images/experiments/ending/make-decision/ship_with_holdback.png) Shipping with holdback maintains a dedicated, stable control group alongside a representative test segment of the shipped experience, making it easy to measure ongoing performance and user behavior after launch. {% callout type="info" %} This is currently an Early Access feature. {% /callout %} ================================================================================ # Stop Assignments URL: https://docs.statsig.com/experiments/ending/stop-assignments Updated: 2026-07-11 ================================================================================ ## Stopping new user assignments in an experiment After running an experiment for some time with users split across control and treatment groups, you may want to stop enrolling new users and analyze only the users who have been exposed so far. The Stop Assignment option lets you do this. {% callout type="info" %} You will need to configure [Persistent Assignment](/server/concepts/persistent_assignment) for this feature to work. If you don't configure this, people already exposed to the treatment groups will no longer get the treatment experience. Persistent Assignment is required even if you use stable identifiers. {% /callout %} ## What Stop Assignment does - Sets experiment allocation to 0%: no new users are enrolled in the experiment. - Preserves existing user assignments: users already exposed continue to receive their control or treatment experience (only if Persistent Assignment is configured). - New users aren’t checked against the experiment and instead get the project’s default experience. To ship a specific variant to all new users, make a decision on the experiment or set this in code. ## Enabling Stop Assignment option You must first enable the **Stop Assignment** option in Project Settings for it to appear in the **Make Decision** modal. To enable this option, go to *Settings > Project Info* and toggle on the **Enable stop new user assignments for experiments** setting. {% figure %} Project settings toggle to enable stop new user assignments {% /figure %} ## How Stop Assignment works To stop assignment for an experiment, select the Make Decision dropdown as shown below. {% figure %} ![Make Decision dropdown highlighting Stop Assignment option](/images/experiments/stop_assignment.png) {% /figure %} When you stop assignments for an experiment: - After you stop assignment, the experiment stops enrolling new users. Statsig stops performing checks on new users. - Previously exposed users continue receiving their consistent control or treatment experience. Configure [Persistent Assignment](/server/concepts/persistent_assignment) to preserve the user variants. - Analysis continues as the experiment records new data points for already-exposed users. Stop Assignment is an irreversible decision. After you make this decision, you cannot resume assigning users into your experiment. ================================================================================ # Conclude Experiment & Defer Decision URL: https://docs.statsig.com/experiments/ending/conclude-experiment-defer-decision Updated: 2026-07-11 ================================================================================ ## How Conclude Experiment and Defer Decision works The "Conclude Experiment and Defer Decision" feature stops any further user allocation and data collection on an ongoing experiment. Use this feature when you want to decouple running an experiment from making a ship decision. ### Benefits - **Focused decision making**: Decoupling the experiment from the decision gives you time to evaluate results, weigh options, and review findings with stakeholders across teams. - **Flexibility in outcomes**: Decide whether to ship the control, implement the test, or abandon/restart the experiment based on comprehensive insights. {% figure %} ![Conclude Experiment](/images/experiments/ending/conclude-experiment-defer-decision/conclude_experiment.png) {% /figure %} ### Enabling this option You must first enable the "Conclude Experiment and Defer Decision" option in Project Settings for it to appear in the Make Decision modal. To enable this option, go to *Settings > Experimentation* in the **Product Configuration** section and toggle on the **Enable decision type - Conclude Experiment and Defer Decision** setting. {% figure %} Screen Shot 2024-12-04 at 12 10 28 PM {% /figure %} ### Key details When you conclude an experiment: - After you conclude the experiment, no new users are enrolled. - All previously exposed users start receiving the default experience. - Statsig stops further data collection on the experiment and concludes results. - After concluding the experiment, you can still decide to ship control vs test variant, reset, or abandon the experiment. ### Reloads after experiment conclusion You may notice slight changes in exposure and metric data if you trigger another reload after concluding an experiment. This doesn't mean new users are still being exposed. Depending on the timestamp of when the experiment was concluded versus when the latest exposure data landed in your warehouse in the previous reload, additional exposures from that time window may appear in the refreshed data. ### Summary The "Conclude Experiment and Defer Decision" feature lets you make data-driven decisions without the risk of diluting results by continuously adding new participants or new data from existing participants. This structured approach ensures that all stakeholders are aligned before you make a decision. ================================================================================ # Abandon an Experiment URL: https://docs.statsig.com/experiments/ending/abandon Updated: 2026-07-11 ================================================================================ When your experiment has an issue or you need to stop it for any reason, you can abandon it. Abandoning an experiment puts it into the unstarted state, which gives every user the default experience. {% figure %} ![Experiment abandon interface](/images/experiments/ending/abandon/abandon.png) {% /figure %} When you abandon an experiment, the "salt" the experiment uses to randomize a user's group also changes. When you restart the experiment, Statsig randomly assigns users to a group that isn't necessarily the same group they were in before. This ensures that a group that didn't perform well due to a bug or bad experience in the previous run isn't negatively affected after you address the issue in the new run. ================================================================================ # Disable a Group URL: https://docs.statsig.com/experiments/implementation/disable-group Updated: 2026-07-11 ================================================================================ ## Disabling a group in an experiment When you start an experiment with multiple test groups and find that one group is performing poorly or creating bad user experiences, you can disable that group while keeping the other test groups running. ### What to expect when a group is disabled * Users already assigned to the disabled group begin to receive the default experience. * Statsig can assign new users to the disabled group, and those users receive the default experience. * Statsig continues tracking exposures for the disabled group. * Sample Ratio Mismatch (SRM) isn't triggered from the disabled group. * Statsig continues to calculate experiment results for the disabled group. {% callout type="info" %} Statsig continues showing the group and logging exposures so that you can verify that user experiences have recovered after the group was disabled. {% /callout %} ### How to disable a group * Log into the Statsig console at https://console.statsig.com * On the left-hand navigation panel, select **Experiments** * Select the experiment where you want to disable a group * Click the **...** menu in the top right corner * Select **Disable A Group** * Select the experiment groups you want to disable, then select **Confirm** {% figure %} ![Experiment overflow menu showing Disable a Group option](/images/experiments/implementation/disable-group/disable_group.png) {% /figure %} {% figure %} ![Disable group dialog listing experiment groups with toggles](/images/experiments/implementation/disable-group/disable_experiment_groups.png) {% /figure %} ## Reenable a group To reenable a previously disabled group, use the **...** menu and select **Disable or Enable A Group**. {% figure %} ![Menu option to disable or enable a group in experiment controls](/images/experiments/implementation/disable-group/2fa3cb71-9a46-46f1-bb11-b8e7f07df9c6.png) {% /figure %} {% figure %} ![Modal showing reenable group selections](/images/experiments/implementation/disable-group/a56e7f14-53e9-4d41-8266-d70de1ec3ebf.png) {% /figure %} {% callout type="info" %} Users in any reenabled group may have experienced multiple treatment experiences over the course of the experiment. The metric data collected for the group may reflect this. {% /callout %} ================================================================================ # How to Read Experiment Results (Formerly "Pulse") URL: https://docs.statsig.com/experiments/interpreting-results/read-results Updated: 2026-07-11 ================================================================================ ## Read Experiment Results To read the results of your experiment, go to the **Results** tab. There you'll find your experiment hypothesis, **exposures**, and **Scorecard**. ### Exposures {% figure %} ![Exposures chart showing cumulative users per experiment group](/images/snippets/pulse/read-results/7a80e74e-140d-4c36-a78e-c9b626e1fee5.png) {% /figure %} At the top of the Results page is the Exposures Chart. Exposures are the unique experimental units enrolled in the experiment: typically unique users, or devices for device-level experimentation. The timeline shows when the experiment started and how many exposures were enrolled each day. Use this chart to verify the rate at which users entered each group, the total number exposed, and whether the target ratio matches what you configured in experiment setup. ### Scorecard The experiment **Scorecard** shows the metric lifts for all Primary and Secondary metrics you set up at experiment creation. #### Immediately post-experiment start For up to the first 24 hours after starting your experiment (before the daily metric results run), Statsig calculates the **Scorecard** section hourly. This applies only to Statsig Cloud; for WHN projects, you must reload results on demand or set up a daily schedule. This real-time scorecard lets you confirm that exposures and metrics are being calculated as expected and debug your experiment or gate setup if needed. {% callout type="note" %} Do **not** make any experiment decisions based on real-time results data in this first 24-hour window after experiment start. Call experiments only after the experiment has reached target duration, as set by your primary metric(s) reaching experimental power. For more information about target duration, refer to [Target Duration](/experiments/create-new#target-duration). {% /callout %} Data in this early window is intended for diagnostics, not decision-making. The real-time view differs from daily results in the following ways: - Metric lifts don't have confidence intervals - No time-series view of metric trends - No projected topline impact analysis - No option to apply more advanced statistical tactics, such as CUPED or Sequential Testing All of these are available in daily Results, which start showing in the next daily run. #### Post-first day scorecard {% figure %} ![Experiment scorecard table displaying metric lifts and confidence intervals](/images/snippets/pulse/read-results/8b855f5c-d26f-4185-ac31-5108fbebe18e.png) {% /figure %} The daily results run calculates the difference between comparable randomization groups (for example, test and control) across your metric suite and applies a statistical test to the results. Refer to [Statsig's stats engine](/experiments/statistical-methods/introduction) for details. For every metric, Statsig shows you: - The calculated relative difference (Delta %) - The confidence interval - Whether the result is statistically significant - Positive lifts are green - Negative lifts are red - Non-significant results are grey The formula for calculating lift is: Delta(%) = (Test - Control) / Control Confidence intervals are reported at the selected significance level (95% by default). In a typical two-sided Z-test, we show the confidence interval as +/- 1.96 \* standard error. Statsig automatically applies 99.9% winsorization to event_count, event_count_custom, and sum metrics. Winsorization caps extreme outlier values to reduce their impact on experiment results. For metrics added to the **Scorecard** or **Monitoring Metrics** sections of your experiment or gate, you can also apply optional statistical treatments such as CUPED (pre-experiment bias reduction) and sequential testing adapted confidence intervals. Refer to [the stats engine documentation](/experiments/statistical-methods/introduction) for more details. - **Experiment results are computed for the first 90 days**: By default, Statsig computes experiment results for only the first 90 days of your experiment. Statsig notifies you by email as you approach the 90-day cap. At that point, you can extend the compute window by 30 days at a time. If the experiment runs beyond the compute window, Statsig stops adding new users to the experiment's results, but analysis for users already exposed continues until you make a decision on the experiment. {% callout type="note" %} This experiment result calculation window only affects whether a user is included in the experiment's analysis, and doesn't affect the treatment each user receives. New users still receive the experience for the group they get randomized into. {% /callout %} ### Experiment results views There are several views for Scorecard metric lifts: - **Cumulative results (default view)**: Displays the aggregate difference between experiment groups and visualizes the corresponding confidence intervals. - **Table view**: Displays the same data as the cumulative view but in a table format with additional fields. - **Daily results**: Shows the difference between experiment groups aggregated based on days since start of experiment. - **Days since exposure**: Shows the difference between experiment groups aggregated based on days since exposure to the experiment. Cumulative results includes a detailed view on hover, where you can additionally view the raw statistics used in the metric lift calculations, as well as topline impact. {% figure %} ![Cumulative results view with hover details](/images/snippets/pulse/read-results/856c7750-df56-45d9-b253-6b63f336cac7.png) {% /figure %} ### Dimensions There are two ways to break down a given Scorecard metric: by a **User Dimension** or by an **Event Dimension**. #### User dimensions User Dimensions are user-level attributes that are either part of the user object you log, or additional metadata that Statsig extracts. Examples include operating system, country, and region. You can create [custom "explore" queries](/experiments/interpreting-results/custom-queries) to filter or group by available user dimensions. For example, view results for users in the US, or for users on iOS grouped by country. Go to the **Explore** tab to draft a custom query. {% figure %} ![custom queries](/images/snippets/pulse/read-results/e3afb526-8f9d-465e-af33-ea9575ac69e7.png) {% /figure %} #### Event dimensions Event Dimensions are the value or metadata logged as part of a custom event used to define the metric. To see results for a metric broken down by categories specific to that metric, [specify the dimension](/metrics/metric-dimensions) in the **value** or **metadata** attributes when you log the source event. For example, when you log a "click" event, you can also log the target category using the **value** attribute. Statsig automatically generates results for each category in addition to the top-level metric. To see breakdowns for all categories within a metric, click on the (+) sign next to the metric. {% figure %} ![dimension button](/images/snippets/pulse/read-results/65cbe2a0-d269-4385-a606-c825ff2e8e05.png) {% /figure %} {% figure %} ![dimension results view](/images/snippets/pulse/read-results/f557dac6-e29f-4cb5-bd6a-19fc2b226193.png) {% /figure %} ### Significance level settings You can adjust these settings at any time to view Scorecard results at different significance levels. - **Apply Benjamini-Hochberg Procedure per Variant**: Reduces the probability of false positives by adjusting the significance level for multiple comparisons. Refer to [Benjamini-Hochberg Procedure](/experiments/statistical-methods/methodologies/benjamini-hochberg-procedure) for details. - **Confidence Interval**: Changes the confidence interval displayed with the metric deltas. Choose lower confidence intervals (for example, 80%) when tolerance for false positives is higher and fast iteration with directional results is preferred over longer experiments with greater certainty. - **CUPED**: Toggle CUPED on or off using the inline settings above the metric lifts. This setting applies only to **Scorecard** metrics; Statsig doesn't apply CUPED to non-Scorecard metrics. - **Sequential Testing**: Applies a correction to p-values and confidence intervals to reduce false positive rates when evaluating results before the target completion date, which mitigates the increased false positive rate caused by early result evaluation. Toggle Sequential Testing on or off using the inline settings above the metric lifts. This setting is available only for experiments with a set target duration. {% figure %} ![analysis settings](/images/snippets/pulse/read-results/d2d7405a-9e86-4317-8f32-51b369c66699.png) {% /figure %} ### Restarting results {% figure %} Restart results banner {% /figure %} If your experiment has stopped computing results, you can resume updates by clicking the Restart button. Understand how Restart behaves before proceeding: - A Restart isn't a [Reset](/experiments/ending/ending-experiment#stopping-an-experiment) of your experiment. A Restart doesn't re-salt (re-randomize) units in your experiment, and all users continue to receive the same group assignments. - Statsig begins computing experiment results from the restart point, so your metric results start over. Old results may still be available in timeseries and explore query views, but Statsig doesn't carry them forward or update them. - Your Cumulative Exposures chart updates based on new exposures, but the duration of the pause in computations affects whether the chart starts over from zero or your exposure count includes past exposures. To avoid needing a Restart, extend experiments actively while they are running. Look out for email alerts from Statsig and check your experiments regularly. ================================================================================ # Slicing by User Properties URL: https://docs.statsig.com/experiments/interpreting-results/userproperties Updated: 2026-07-11 ================================================================================ Statsig lets you slice results by user properties. Common examples include breaking down results by a user's home country, subscription status, or engagement level. {% figure %} ![Pulse results sliced by user properties](/images/experiments/interpreting-results/userproperties/60ad9a4f-8e85-42a6-8c36-147fc6c85873.png) {% /figure %} For Statsig Cloud, Statsig captures and freezes user properties from the properties set on the user's first exposure. Statsig Warehouse Native also supports reading user properties from a warehouse table (Entity Properties). You can run custom queries on experiments to slice by user properties at any time. ## Pre-computed user properties User properties that are frequently used to slice results can be pre-computed when using Statsig Warehouse Native. Configure these properties to be pre-computed on the experiment setup page, under the advanced settings. You can also configure team-level defaults or pre-configure them on an experiment template. {% figure %} ![Experiment setup page with pre-computed user properties configuration](/images/experiments/interpreting-results/userproperties/196bd217-dd29-4b63-9f1b-d08639e0d36d.png) {% /figure %} After configuring pre-computed properties, you can also apply filters to all metrics on your results. {% figure %} ![Metrics results with user property filters applied](/images/experiments/interpreting-results/userproperties/8b5c6dcc-feac-46c9-a6fa-331daafc4864.png) {% /figure %} ================================================================================ # Pulse URL: https://docs.statsig.com/experiments/interpreting-results/drill-down Updated: 2026-07-11 ================================================================================ ## Metric tooltip When you hover over a metric in Pulse, Statsig shows a tooltip with key statistics and deeper information. {% figure %} ![UI for metric hover card in experiments](/images/metric_hover_card.png) {% /figure %} * **Group**: The name of the group of users. For Feature Gates, the "Pass" group is considered the test group while the "Fail" group is the control. In Experiments, these are the variant names. * **Units**: The number of distinct units included in the metric. E.g.: Distinct users for user\_id experiments, devices for stable\_id experiments, etc. * **Mean**: The average per-unit value of the metric for each group. * **Total**: The total metric value across all units in the group, over the time period of the analysis. ### Calculation details | Metric Type | Total Calculation | Mean | Units | |------------------|-------------------------|----------------|----------------| | event\_count | Sum of events (99.9% winsorization) | Average events per user (99.9% winsorization) | All users | | event\_user | Sum of event DAU (distinct user-day pairs) | Average event\_dau value per user per day. Statsig calls this "Event Participation Rate" because it represents the probability a user is DAU for that event. | All users | | ratio | Overall ratio: sum(numerator values)/sum(denominator values) | Overall ratio | Participating users | | sum | Total sum of values (99.9% winsorization) | Average value per user (99.9% winsorization) | All users | | mean | Overall mean value | Overall mean value | Participating users | | user: dau | sum of daily active users | Average metric value per user per day. The probability that a user is DAU | All users | | user: wau, mau\_28day | Not shown | Average metric value per user per day. The probability that a user is xAU | All users | | user: new\_dau, new\_wau, new\_mau\_28day | Count of distinct users that are new xAU at some point in the experiment | Fraction of users that are new xAU | All users | | user: retention metrics | Overall average retention rate | Overall average retention rate | Participating users | | user: L7, L14, L28 | Not shown | Average L-ness value per user per day | All users | ### p-Value In Null Hypothesis Significance Tests, the p-value is the probability that such an extreme difference arises by random chance when the experiment has no actual effect. A low p-value indicates the observed difference is unlikely to be due to random chance. In hypothesis testing, a p-value threshold determines which results are due to a real effect and which are plausibly due to random chance. ([p-value calculation](/experiments/statistical-methods/p-value)) ### Reverse power Reverse power is the smallest effect size that an experiment can reliably detect in its current state (some studies refer to this value as ex-post MDE). Statsig calculates it from the sample size and standard error from the control group. Importantly, reverse power does *not* depend on the observed effect size. In practice, reverse power answers such questions like: given how the test actually played out, what is the smallest effect we have sufficient power (typically 80%) to detect? For a two-sided test, the reverse power for a given metric X is computed using the following equation: $$ Reverse Power = \frac{(Z_{1-\beta} + Z_{1-\alpha/2})}{\overline{X}_{\text{control}}}\times \sqrt{\frac{\mathrm{var}(\Delta \overline{X})}{N_{\text{control}}}} \times 100\\% $$ For a one-sided test, the reverse power for a given metric X is computed using the following equation: $$ Reverse Power = \frac{(Z_{1-\beta} + Z_{1-\alpha})}{\overline{X}_{\text{control}}}\times \sqrt{\frac{\mathrm{var}(\Delta \overline{X})}{N_{\text{control}}}} \times 100\\% $$ * $\overline{X}_{\text{control}}$ is the mean metric value across control users * $var(Δ\overline{X})$ is the population variance of delta * $N_{\text{control}}$ are the observed number of units in the control group * $Z_{1-\beta}$ is the standard Z-score for the selected power. Typically ${1-\beta}$ = 0.8 and $Z_{1-\beta}$ = 0.84 * $Z_{1-\alpha/2}$ and $Z_{1-\alpha}$ are the standard Z-scores for the selected significance level in a two-sided test and in a one-sided test. You can enable reverse power as an optional feature. To manage it, go to **Settings > Product Configuration > Experimentation > Organization** and toggle it on or off. ## Detailed view Select **View Details** to access in-depth metric information. The detailed view contains three sections: * **Time Series**: How the metrics evolve over time * **Raw Data**: Group-level statistics * **Impact**: How the experiment impacts the metric ### Time series In this view, select and drag to zoom in on different time ranges. The drop-down offers three types of time series: **Daily**: The metric impact on each calendar day without aggregating days together. This is useful for assessing day-over-day variability and the impact of specific events. This is the recommended time series view for Holdouts, because it highlights the impact over time as new features are launched. {% figure %} ![Daily metric impact visualization interface](/images/snippets/pulse/metric-drill-down/f42453a6-e413-47d5-b176-bb3e9b49a915.png) {% /figure %} **Cumulative**: Shows the cumulative metric impact from the start of the experiment over time. This is useful for observing trends and seeing how your confidence interval changes over time. {% figure %} ![Cumulative metric lift visualization interface](/images/snippets/pulse/metric-drill-down/b2e9aea9-98fb-4e29-bccf-abae356d0173.png) {% /figure %} **Days Since Exposure**: Shows the metric impact based on how long a user has been in the experiment. Daily data for each user is aligned by the day the user entered the experiment (Day 0, Day 1, and so on), not by calendar date. This lets you distinguish early (novelty) effects from long-term effects. This view also shows pre-experiment data, which identifies biases between groups before the experiment started. Such biases can arise from random chance or from an issue in the random assignment process. {% figure %} ![Days since exposure metric visualization interface](/images/snippets/pulse/metric-drill-down/17eeb054-43d8-424c-b43a-85c682bfcfb8.png) {% /figure %} ### Raw data This view shows the group-level statistics needed to compute the metric deltas and confidence interval. These include Units, Mean, and Total (described above), as well as the Standard Error of the mean (Std Err). Refer to the [statistical calculations reference](/experiments/statistical-methods/introduction) for details. ### Impact {% figure %} ![Experiment impact metrics interface](/images/snippets/pulse/metric-drill-down/171754168-624f740e-bdd6-46ea-a2e3-8d70e637ff75.png) {% /figure %} * **Experiment Delta (absolute)**: The absolute difference of the Mean between test groups i.e. Test Mean - Control Mean. Statsig shows the p-value to indicate whether the observed absolute difference is statistically significant. * **Experiment Delta (relative)**: Relative difference of the Mean i.e. 100% x (Test Mean – Control Mean) / Control Mean. * **Topline Impact**: The measured effect that experiment is having on the overall topline metric each day, on average. Computed on a daily basis and averaged across days in the analysis window. The absolute value is the net daily increase or decrease in the metric, while the relative value is the daily percentage change. * **Projected Launch Impact**: An estimate of the daily topline impact Statsig expects if a decision is made and the test group is launched to all users. This takes into account the layer allocation and the size of the test group. Assumes the targeting gate (if there is one) remains the same after launch. Refer to [topline and projected impact calculations](/experiments/statistical-methods/topline-impact) for details. **FAQs about topline impact** *Why is the projected launch impact smaller than the relative experiment delta?* An experiment can impact only a subset of the user base that contributes to a topline metric. The relative experiment delta observed is effectively diluted when measured against the topline metric value. For example: consider a top-of-funnel experiment on the registration page. Among users who visit this page, the treatment leads to more sign-ups and a 10% lift in daily active users (DAU). However, the topline DAU metric includes other user segments outside of the experiment, such as long-term users who don't visit the registration page. A 10% lift in the test vs. control comparison may amount to only a 1% increase in overall DAU. *How can the topline impact be higher than the experiment delta?* The topline impact can be higher or lower than the experiment delta because Statsig computes the two values differently and they have different meanings. Experiment deltas are based on unit-level averages: Statsig computes the mean value of the metric for each user across all days, then averages it to obtain the group mean. Statsig computes the topline impact daily based on the total pooled effect from all users, then averages it across days to show the daily impact. Statsig computes topline impacts this way because most metrics are tracked on a daily basis and the topline value is an aggregation across all users rather than a user-level average. For experiment analysis, best practice is for the analysis unit to match the randomization unit, so metrics are aggregated at the unit level first before computing experiment deltas. ================================================================================ # Exporting Pulse Reports URL: https://docs.statsig.com/experiments/interpreting-results/export Updated: 2026-07-11 ================================================================================ ## How to Export Pulse Data in Statsig Cloud {% callout type="tip" %} **Warehouse Native users**: You're viewing the Cloud docs for this page. Metrics and experiments behave differently in Warehouse Native. Read [How to Access Pulse Data in Warehouse Native](/experiments/interpreting-results/access-whn). {% /callout %} {% figure %} ![Finding Export Report](/images/snippets/pulse/export/a2d68701-6828-47d2-8fde-b44a5cea4abb.png) {% /figure %} To export Pulse Results for Feature Gates and Experiments, go to the relevant "Pulse Results" page and click **Export Report**. Exporting results can take up to 10 minutes. Statsig sends you a notification and an email when the report is ready, and a link appears under *Project Settings > Reports*. You can export results only if your Pulse screen has results. {% figure %} ![Export Pulse Report Menu](/images/snippets/pulse/export/5af19e59-f2b7-492b-9dc2-9439e447dbcc.png) {% /figure %} ================================================================================ # Best Practices and Avoiding False Positives URL: https://docs.statsig.com/experiments/interpreting-results/best-practices Updated: 2026-07-11 ================================================================================ The following suggestions help you interpret Pulse in a scientifically sound way: 1. Have a hypothesis in mind before viewing Pulse. What metrics do you expect to shift due to the change you made? What else could have happened? What are signs that something went wrong? 2. Establish a small set of key metrics directly related to your hypothesis that would establish that the experiment worked. More than a handful of key metrics usually indicates an ill-defined hypothesis or unfocused experimentation. Examining too many metrics increases the false positive rate (seeing results when only statistical noise exists). 3. Avoid cherry-picking results. Don't selectively pick three metrics that look good while ignoring two that don't. Also avoid picking "good" or "bad" numbers with no connection to your hypothesis. Context matters: statistically significant results should have a plausible explanation (a false positive is a plausible explanation). 4. Multiple independent effects that are consistent with a plausible story lend credibility to the observed effects, even with borderline p-values. 5. Expect to see false positives and be suspicious of statistically significant results with borderline p-values. For example, a 95% confidence interval (5% significance level) is expected to produce one statistically significant metric out of twenty due purely to random chance. This number increases when you include borderline metrics (for example, p = 0.06). 6. Look beyond your hypothesis. What other effects can you find? Are there tradeoffs? Are there unexpected behaviors? These can reveal information about your users and how they interact with your product, and are often the source of follow-up experiments and new ideas. ================================================================================ # Reconciling Results Between Experimentation Platforms URL: https://docs.statsig.com/experiments/interpreting-results/reconciling-experiment-results Updated: 2026-07-11 ================================================================================ ## Why results differ between platforms The same data can yield very different interpretations in experiment results due to the wide variety of analysis methodologies available. One advantage of modern experimentation platforms is ensuring consistency and transparency in experimental analysis within your organization. This guide covers common gaps between platforms and how to identify and resolve them. ## General approach When companies evaluate an experimentation vendor, differences in results between their in-house platform and the vendor's platform are common during Proof-of-Concept (POC) validations. You can typically resolve these gaps by working through the following hypotheses in order: 1. The metric source data is being read or joined to exposure data differently, invalidating downstream steps. 2. Advanced statistical features available on the vendor side but not in-house are working as intended, most often reducing the influence of outliers or pre-experiment bias. 3. There is a misunderstanding of how a metric definition works, or how an advanced configuration on a metric or experiment behaves. By working through these in order, data teams can quickly understand and address gaps, or decide whether the vendor's approach is acceptable. ## Joining data Based on observational data, differences in experiment results most often stem from how exposure data is joined with metric data. At the end of this section, there is a basic check for confirming this isn't occurring. ### ID formats In some cases, IDs are logged in different formats to different places. For example, the binary ID `4TLCtqzctSqusYcQljJLJE` maps to the UUID `a0fb4ef0-9d9e-11eb-9462-7bfc2b9a6ff2`, so a company might have the binary ID in their production environment while their data users work with the equivalent UUIDs. Exposures logged using the binary ID can't join with metric data using the UUID, and results are empty. Check samples for both the metric source and the assignment source or diagnostic logstream to confirm that the identifiers are in the same format. You can use ID Resolution to bridge ID type gaps, but it isn't intended to solve this scenario. ID Resolution helps connect identifiers across logged-out/logged-in sessions, or other scenarios where users commingle their identifiers by switching identifiers during the experiment. ### Timestamps Analyze metric data only after a user has been exposed to the experiment. Pre-experiment data should have no average treatment effect, so its inclusion dilutes results. #### Statsig Cloud Statsig Cloud uses a date-based join between exposures and metric data. Experiments include metric data from the whole of the first exposure date for each experimental unit. While some pre-experiment metric data can be included, the average treatment effect of this dilution should be null. This is illustrated in the SQL snippet below: ``` WITH metrics as (...), exposures as (...), joined_data as ( SELECT exposures.unit_id, exposures.experiment_id, exposures.group_id, metrics.timestamp, metrics.value FROM exposures JOIN metrics ON ( exposures.unit_id = metrics.unit_id AND metrics.date_id >= exposures.first_date_id ) ) SELECT group_id, SUM(value) as value FROM joined_data GROUP BY group_id; ``` Statsig's exposures are always in UTC. If metric data is in another timezone, adjust it to avoid filtering on the wrong comparison. Statsig does support timestamp-based joins for some Enterprise Cloud customers. Contact Statsig to learn more. #### Statsig Warehouse Native Statsig WHN uses a timestamp-based join, with an option for a date-based join for daily data. This is illustrated in the SQL snippet below: ``` WITH metrics as (...), exposures as (...), joined_data as ( SELECT exposures.unit_id, exposures.experiment_id, exposures.group_id, metrics.timestamp, metrics.value FROM exposures JOIN metrics ON ( exposures.unit_id = metrics.unit_id AND metrics.timestamp >= exposures.first_timestamp ) ) SELECT group_id, SUM(value) as value FROM joined_data GROUP BY group_id; ``` Timestamps for Statsig's exposures are always in UTC. If metric data is in another timezone, adjust it to avoid filtering on the wrong comparison. ### Exposure duplication De-duplicate exposure data before joining to ensure a single record per user. Many vendors also manage crossover users (users present in more than one experiment group), removing them from analysis or alerting when crossovers occur at high frequency. ``` SELECT unit_id, experiment_id, MIN(timestamp) as first_timestamp, COUNT(distinct group_id) as groups FROM GROUP BY unit_id, experiment_id, group_id HAVING COUNT(distinct group_id) = 1; ``` ### Data availability When comparing a platform analysis to an **existing** experiment analysis that ran in the past, the underlying data may have fallen out of retention or been deleted. Compare the table's retention policy to the analysis dates used in your original experiment analysis to confirm that the data still exists. Also confirm that your experiment in the vendor console is configured to analyze the same time range as your original analysis. ### Validation To validate the initial metric data and join, use the query provided in the [Timestamps](/experiments/interpreting-results/reconciling-experiment-results#timestamps) section, modifying it to run on both platforms. Confirm that a target metric has the same totals per group across both platforms. Warehouse Native platforms have an advantage here because the SQL dialect and source data are generally the same in both vendor code and in-house code, making comparisons simpler. Pick one metric of interest, validate that data, and resolve any differences before checking statistical and metric methodologies. ## Statistical features Choices in statistical methodologies can significantly impact experiment results. The following are common root causes for gaps in results. Always closely read the queries being run by the vendor to understand any particulars in methodology. ### Winsorization Outlier trimming, or [Winsorization](/experiments/statistical-methods/methodologies/winsorization), can dramatically alter experiment outcomes. Disable this feature in Statsig metrics when doing cross-system comparisons unless you're also applying it manually. ### CUPED [CUPED](/experiments/statistical-methods/methodologies/cuped) can significantly change variances and observed deltas, especially with high pre- and post-exposure data correlation or systematic differences in groups' pre-experiment data. You can configure CUPED at a metric level. You can also disable it for a pulse result set after running analysis. ### Ratio metrics For ratio metrics using the delta method, Statsig includes only units with a non-zero denominator. Statsig calculates ratios and means as $\bar\{u} = \frac\{\sum\_\{i=0}^\{n}(numerator\_i)}\{\sum\_\{i=0}^\{n}(denominator\_i)}$ and uses the [delta method](/experiments/statistical-methods/variance#ratio-and-mean-metrics) to correct for the cluster-based nature of these metrics. ## Metric definitions Users often misunderstand how a given metric is calculated. Refer to the comprehensive [metrics guide](/statsig-warehouse-native/configuration/metrics) for details. ## Summary Following these steps clarifies where any gaps between two experiment platforms are coming from. Statsig provides the intermediate and result datasets it uses, as well as the queries used in its analysis, making it straightforward to understand where gaps arise. If you get stuck, reach out for help. For an overview of experiment pipeline patterns, refer to the [Statsig Warehouse Native Documentation](/statsig-warehouse-native/introduction) and [Statsig Pipeline Overview](/statsig-warehouse-native/analysis-tools/pipeline-overview/). ================================================================================ # Frequently Asked Questions on Using Pulse URL: https://docs.statsig.com/experiments/interpreting-results/faq Updated: 2026-07-11 ================================================================================ This page provides answers to common questions about interpreting statistical results. ## I had a stat sig result, but it turned negative. How should I interpret this Trust the current result, as it incorporates more information about the users in your experiment. This can happen for several reasons: * Random noise, which is reduced as sample size grows * Within-week seasonality (for example, an effect that differs on Mondays), which normalizes with more data * The population that saw the experiment early is different from slower adopters. This is common: a daily user will likely see your experiment before someone who uses your product once a month. Use the time series view for more insight. * A novelty effect made the experiment appear meaningful early on, but the effect faded. For example, users might click a changed button out of curiosity, then revert to prior behavior after the novelty wears off. Use the days-since-exposure view for more insight. Best practice for timing is to pick a readout date when you launch your experiment (based on a [power analysis](/experiments/power-analysis)), and to disregard the statistical interpretation of results until then. Reading results multiple times before the readout date dramatically increases the rate of false positives. ## How should I start with interpreting results Start by using your scorecard metrics to understand whether you moved the metrics you expected to move. Bring a hypothesis about what your experiment should drive; your primary metrics should answer that hypothesis. The delta displayed is based on the observed difference between a test and control population. The error bars visualize a confidence interval: a range of probable values for the difference between groups. A future sample's 95% confidence interval has a 95% chance of containing the true value of the difference. In practice, the CI is a representative range of what the true value might be. These results are statistical interpretations, not facts: * If a result isn't stat sig, you don't have sufficient evidence to reject the null hypothesis (i.e., based on your experiment design the observed result is reasonably likely to have happened by chance). * Generally, treat these results as a lack of evidence for your hypothesis. * Underpowered tests may lead to neutral results even if a true effect exists. * If a result is stat sig, you have sufficient evidence to reject the null hypothesis (i.e., the probability that you would observe this result, or one more extreme, if the two groups' results were identical is below the pre-determined threshold you set). * Generally, treat this result as evidence for your hypothesis. * Multiple comparisons (many metrics, rerunning an experiment, or grouping by dimensions) greatly increase the chance of seeing a stat sig result when there's *not* a true effect. Be wary of interpreting results when you see those behaviors! * A test that was extremely unlikely to succeed and has a stat sig result has a high chance of being a false positive. Consider trying to reproduce the result, running a back-test, or reducing your significance level. After reviewing the scorecard results, use the all-metrics tab and custom queries to gather more information. Stat-sig movements in those views aren't necessarily statistically sound: increasing the number of metrics you examine increases the chance of a false positive. Use this section to look for unexpected large regressions and to generate follow-up hypotheses. ## Results aren't showing up for some metrics This normally happens when your company uses the SDK or event imports and also imports precomputed metrics from your data warehouse. Because these can run at different times, data availability may differ. Adjust your analysis date range to get a full view of your data. ## Our external source shows more exposure events than Statsig. Are data missing Statsig doesn't count exposures on the last day (the day you made a decision). Filter out that day when you analyze your external data. The hours that define a "day" for your project depend on which timezone your project uses. ## We log categorical metadata for a custom event, but Pulse doesn't show these breakouts. What's wrong Pulse can show experimental results for sub-groups of your metric (for example, iOS vs. Android) only when you configure your metadata as a Dimension. [Value Dimensions](/experiments/interpreting-results/read-results#value-dimensions) are the most common dimension type, as their metadata is logged directly with your custom events. Value dimensions must be defined in your [custom event setup](/metrics/metric-dimensions). ## Why do I see "No dimensions available for this time range" You may see this error when trying to view precomputed user dimensions, particularly after the first reload of the day. This happens because: * Dimensions load asynchronously in separate explore queries after the main scorecard results load. * The main experiment results appear first, while dimensions continue loading in the background. * Typically, dimensions become available within a few minutes after the main scorecard loads. If you encounter this error, wait a few minutes and refresh the page to check whether the dimensions have completed loading. ================================================================================ # Participating Units URL: https://docs.statsig.com/experiments/interpreting-results/participating-units Updated: 2026-07-11 ================================================================================ ## What are participating units In Statsig, Participating Units (sometimes referred to as participating users) are a subset of an experiment's total exposed unit count relevant to a particular metric. Participating Units are the units with a non-zero denominator value for metric types that require a numerator and denominator value. Statsig uses these units in statistical calculations for ratio-based metrics. {% figure %} ![Participating units diagram showing subset of total exposed users](/images/experiments/participating_units.png) {% /figure %} Statsig computes ratio metrics only for users that have a non-zero value in the denominator: the user must have triggered the denominator event on a given day to be included in the daily ratio. Users that don't trigger the denominator event during an experiment are excluded from the test vs. control comparison of a ratio metric. ## Example An example of this type of metric for an e-commerce company could be "Total Items Purchased per Order", measuring the average basket size. The numerator would be defined as the count of total items bought, and the denominator would be the total number of orders submitted. If an experiment were run on the checkout flow, only users who actually had at least one Order event would be included in the "Total Items Purchased per Order" ratio metric. ================================================================================ # Custom "Explore" Queries URL: https://docs.statsig.com/experiments/interpreting-results/custom-queries Updated: 2026-07-11 ================================================================================ Custom queries let you run additional experiment analyses on your existing data beyond what is in the main Results tab. Use them to gain deeper insights from your experiments and feature rollouts, debug interesting results, or narrow your results to specific sub-groups. Custom queries let you filter or group metrics by event or user dimensions, or filter to a specific set of users to see how an experiment or launch impacted those users. Custom queries are experimental analyses identical to those in the main Results tab, and all the same statistical procedures apply. Statsig computes results as p-values and confidence intervals for your metric deltas, and you can use advanced statistical methods like [CUPED](/experiments/statistical-methods/methodologies/cuped) and [Sequential Testing](/experiments/advanced-setup/sequential-testing). {% callout type="warning" %} Be careful when drawing your inferences of Custom Queries, especially when grouping by a dimension with lots of options. This can increase your chance of seeing a false-positive statistically significant result. {% /callout %} ### Dimension loading timing for precomputed user dimensions When viewing results for precomputed user dimensions (which are configured and run on a schedule), be aware that these dimensions are loaded through separate asynchronous explore queries. This means: - The main experiment results will appear first - Precomputed dimensions will continue loading in the background and become available within a few minutes - This timing gap is most noticeable immediately after the first reload of the day - If you see "No dimensions available for this time range" for precomputed dimensions, wait a few minutes and refresh to see if dimensions have completed loading {% callout type="note" %} This timing behavior only affects precomputed user dimensions that run on a schedule. User-triggered custom queries don't experience this asynchronous loading delay. {% /callout %} ### Running a custom query To run a Custom Query, navigate to the **Explore** tab within your experiment. {% figure %} Custom query explore tab interface {% /figure %} Custom Query fields: - **Metric(s):** The metric(s) you want to analyze. You can select a single metric, a few metrics, or a Metric Tag. Adding a Tag includes all the metrics within that Tag in your Custom Query. There are three "default" metric selections included as shortcuts: - "Scorecard Metrics", all metrics included in your experiment setup's Primary and Secondary Metrics sections - "Primary Metrics" - "Secondary Metrics" - **Metric Filter:** With metrics selected, you can filter metrics by either Event or User dimensions using the "Add Filter" dropdown. For example, if you wanted to look at your experiment results for Canadian users only, you could filter to "Country = CA". {% figure %} ![Metric filter dropdown selecting Country equals CA](/images/snippets/pulse/custom-queries/f56b4259-4d18-44d1-9646-9d7aa2f8f847.png) {% /figure %} - **Group By:** You can group your Custom Query results by either an Event or User dimension. Whereas Custom Query filters can be applied at the _per-metric_ level, the Group By action is at the _query_ level (so all included metrics will have whatever Group By you select applied to them). - **Time Range for Metric Data:** The date range you're running your analysis on. By default this is the "Full date range" of your experiment data. - **(Advanced) ID List Segment filters:** You can choose an ID-list based [Segment](/segments/overview), and Statsig calculates results only for users who are in that segment. This can be useful if you forgot to log an important user dimension that you want to filter to, or realized that you only care about a sub-population that you've defined in your own data warehouse. - Careful! This option can easily lead to erroneous and biased results. Make sure the segment is defined based on the user's status _before_ they were exposed to the experiment or feature gate. - Similarly, you can choose to _exclude_ a certain ID list segment, for example if you want to exclude a set of users who have been retroactively identified as bad actors from your lifts analysis. - **(Advanced) Filter by Exposure Date:** You can also filter the results by Exposure Date which can give you more flexibility. You can choose only include or exclude a date range, or in WHN, you can additionally include/exclude users based on when they were first exposed to the experiment. - This is useful when your metrics have novelty effect, delayed impact, or specific scenarios where you only want to filter your results to certain users. Use it cautiously because it can lead to biased results. {% callout type="note" %} User groups in experiment results are based off of first-touch attribution. Statsig bases the filters and grouping on the user attributes collected at the time of first exposure in the gate/experiment/layer check. {% /callout %} {% figure %} Custom query definition form showing selected metrics and filters {% /figure %} ### Viewing a custom query in Explore These queries take a few minutes to run. Statsig sends an email when your results are ready. After the query completes, results are visible in the **Query History** section of the **Explore** interface. All historical queries across your team are stored here. You can also give your query a display name inline for easier future identification. {% figure %} Explore tab query history list {% /figure %} ### Scheduling a custom query To get a daily refresh of a given Custom Query, schedule it directly from the **Explore** tab. Author the Custom Query you want to schedule, then tap the "..." menu, then **Schedule**. The Custom Query now runs daily and lives in the **Scheduled** tab of your Metric Lifts. {% figure %} Scheduled custom queries tab displaying daily runs {% /figure %} {% figure %} Scheduled query configuration interface {% /figure %} ### Reviewing custom query results Custom query results look similar to the main Results tab because the statistical methods are the same. Statsig applies the same experimental analysis practices to custom analyses as to main Results. The key difference is that a custom query result is a snapshot in time. After the query runs, results are saved and don't update if more metric data is collected. To update your results, run a new custom query or schedule custom queries to run at a regular cadence. #### Sequential testing and custom queries If [Sequential Testing](/experiments/advanced-setup/sequential-testing) is enabled for your experiment, you can apply it to your custom query results as well. Whether and how much to adjust confidence intervals and p-values depends on the standard rules of sequential testing: if your custom query doesn't satisfy the experiment's target Days or Unique Exposures from your setup, Statsig makes sequential testing adjustments to account for the under-powered state of the experiment. Because custom queries are computed as a snapshot in time, sequential testing adjustments are computed for that specific analysis only. If you run additional custom queries with more or less data (for example, more days in the analysis or more unique users in the experiment), the sequential testing adjustments change accordingly. Custom queries that meet the configured minimum Days or Unique Exposures have no sequential adjustments applied. {% figure %} ![Custom query results table displaying sequential testing adjustments](/images/snippets/pulse/custom-queries/df7c4087-82c3-41fb-8f5d-124094ec4526.png) {% /figure %} ================================================================================ # Metric Insights and Aggregated Impact URL: https://docs.statsig.com/experiments/exploring-results/aggregated-impact Updated: 2026-07-11 ================================================================================ ## Metric Insights and Aggregated Impact Statsig's Insights page provides a clear view of how experiments and feature gates impact a specific metric. It helps answer key questions such as "How much impact have I driven?" and helps diagnose unexpected changes in metrics. Insights presents a reverse perspective of the [Pulse](/experiments/interpreting-results/read-results) view. While Pulse measures the impact of a new feature on all your metrics, Insights lets you focus on a single metric and identify which tests are impacting it the most. This is useful for assessing your team's impact and setting realistic goals. ## How to read Insights 1. Navigate to the Insights section on the Statsig console: https://console.statsig.com/ . Insights is also available in the insight tab for each metric. 2. Select a metric from the selector drop-down at the top of the page. 3. Select the ID type, time window, and other filters. 4. The page shows the relative impact, topline impact, and projected launch impact for any experiment or gate that includes this metric. 5. Statsig sums the projected launch impacts, adjusts based on false positive risk ('winner's curse'), and displays the result as the Aggregated Impact Estimate. {% figure %} ![Insights dashboard showing aggregated impact estimates for a metric](/images/snippets/pulse/aggregated-impact/430563dc-4794-4d69-a314-36c76a6fcf74.png) {% /figure %} ## How the math works Go to the [topline and projected launch impact calculations](/experiments/statistical-methods/topline-impact#computing-projected-launch-impact) for details. To estimate false positive risk and calculate Aggregated Impact, Statsig uses the methodology in this [paper](https://dl.acm.org/doi/10.1145/3534678.3539160), which is widely adopted across the industry. Specifically: $$ Aggregated Impact=\sum\_\{i}\{(1 - FPR\_i) \times Projected Launch Impact\_i} $$ The [projected launch impact](/experiments/statistical-methods/topline-impact) is an estimate of the topline impact assuming a decision is made and the test group is launched to all users. The false positive risk is calculated by the following formula: $$ FPR\_i = \frac\{\alpha\_i \times \pi}\{\alpha\_i \times \pi + (1 - \beta\_i) \times (1 - \pi)} $$ In this formula, $\alpha\_i$ is the significance level for experiment i, $\beta\_i$ is the type II error, and 1 - $\pi$ is the prior success rate based on historical experiment results. ================================================================================ # Topline and Projected Impact URL: https://docs.statsig.com/experiments/statistical-methods/topline-impact Updated: 2026-07-11 ================================================================================ The **topline impact** is the average daily effect that an experiment has on the overall metric value between two groups. This is the actual daily impact to a metric resulting from running the experiment, measured across the two groups being evaluated. The **projected launch impact** is an estimate of the daily impact expected in the metric measured globally if the test group is launched to all users (beyond those in the experiment). Statsig computes this impact relative to the expected baseline value of the metric without the experiment running. Statsig shows topline impact and projected launch impact in both absolute and relative units. Neither uses CUPED when measuring the impact of your experiment, because CUPED already adjusts for pre-exposure data: the same data that topline metrics change from. Combining them would double-count that adjustment. {% callout type="tip" %} **Example**: Take a simple example experiment with a Control group of 1000 users and a Test group of another 1000 users, which ran for 30 days. For an **event\_count** metric, the Experiment Delta is +1.0 events per user (abs). The Topline Impact for this metric is +33.33 events per day (abs). {% /callout %} ## Computing topline impact Statsig computes the topline impact over the total duration of the experiment, which gives the most accurate estimate and a tight confidence interval. The calculation depends on whether the metric represents an absolute quantity or a ratio: ### Count and sum metrics (event\_count, sum) Statsig derives the absolute topline impact directly from the experiment results. It depends on the difference in means between test and control, and the average number of users in the test group per day. $$ Impact\_\{abs}=(X\_t-X\_c) \cdot N\_t / n\_\{days} $$ Using the absolute impact and the overall metric value (as seen in the [metrics dashboard](/metrics/console)), Statsig computes the relative impact. This is the percentage change in the overall metric value over the rollup window attributed to the active experiment. $$ Impact\_\{rel}=\frac\{Impact\_\{abs}}\{Topline\_Value-Impact\_\{abs}} \times 100\\% $$ ### Ratio and mean metrics To derive the topline impact on a ratio metric, Statsig first determines the impact on the numerator (*X*) and denominator (*Y*) separately. The topline impact is the current value of the ratio metric minus the baseline value obtained by subtracting the numerator and denominator impacts: $$ Impact\_\{abs}=\frac\{Topline\_X}\{Topline\_Y}-Baseline\_Value $$ Where the baseline value is the expected value of the topline metric if the experiment wasn't running: $$ Baseline\_Value=\frac\{Topline\_X-(\bar\{X\_t}-\bar\{X\_c}) \cdot N\_t}\{Topline\_Y-(\bar\{Y\_t}-\bar\{Y\_c}) \cdot N\_t} $$ Statsig computes the relative impact for ratio metrics by dividing the absolute impact by the baseline value: $$ Impact\_\{rel}=\frac\{Impact\_\{abs}}\{Baseline\_Value} \times 100\\% $$ ## Computing projected launch impact Statsig uses the layer allocation of the experiment and the size of the test group to estimate a scaling factor *m*, which represents the increase in absolute impact expected when the test group is launched. Statsig calculates the launch factor over a rollup window as $$ m\_\{rollup}=\frac\{1}\{\sum\_\{1}^\{rollup}\{layer\_alloc \times group\_pct}} \times rollup $$ to accommodate changes in allocation during the experiment. {% callout type="note" %} The targeting gate isn't factored in. The projected impact calculation assumes that the target gate remains the same after the experiment is launched. {% /callout %} ### Count and sum metrics (event\_count, event\_dau, sum) For count and sum metrics, the projected absolute impact is the current topline impact scaled by *m*. For example, consider an experiment running with 50% layer allocation and a 50/50 test/control split, so that 25% of all users are in the test group. If the allocation changed during the experiment, Statsig uses a weighted average based on historical allocations. If the topline impact is currently +10 events per day, then launching the experiment would result in +40 events per day. $$ Projected\_\{abs}=Impact\_\{abs} \times m $$ The relative projected impact is the expected percentage change in the topline metric, relative to the baseline value of the metric without the experiment running. $$ Projected\_\{rel}=\frac\{Projected\_\{abs}}\{Topline\_Value-Impact\_\{abs}} \times 100\\% = Impact\_\{rel} \times m $$ ### Ratio and mean metrics The projected impact of ratio metrics depends on the numerator and denominator impacts, using the same scaling factor *m* to obtain the projected impact for each term: $$ Projected\_\{abs}=\frac\{Topline\_X+(m-1) \cdot (\bar\{X\_t}-\bar\{X\_c}) \cdot N\_t}\{Topline\_Y+(m-1) \cdot (\bar\{Y\_t}-\bar\{Y\_c}) \cdot N\_t} - Baseline\_Value $$ Where the first term represents the projected metric value after launch. Finally, the projected relative impact of a ratio metric is the projected absolute impact divided by the baseline value of the ratio: $$ Projected\_\{rel}=(\frac\{Projected\_\{abs}}\{Baseline\_Value}) \times 100\\% $$ ## Confidence intervals Statsig computes the confidence intervals for topline and projected impact using the same method as [confidence intervals for experiment deltas](/experiments/statistical-methods/confidence-intervals). $$ CI(Impact) = Impact \pm Z \cdot \sqrt\{var(Impact)} $$ In the case of absolute impact of count and sum metrics, the variance calculation is simply a linear combination of the test and control variances: $$ var(Impact\_\{abs})=\[var(\bar\{X\_t})+var(\{\bar\{X\_c}})] \cdot N\_t^2 $$ For projected launch impact: $$ var(Projected\_\{abs})=var(Impact\_\{abs}) \cdot m^2 $$ For ratio metrics and relative impacts, Statsig calculates the variance using the Delta method. This accounts for the correlation between numerator and denominator terms, using Taylor expansion to linearize expressions containing non-linear combinations of experiment variables. For example, the variance in the relative impact of a count metric is given by: $$ var(Impact\_\{rel})=var(Impact\_\{abs}) \cdot \frac\{(Topline\_Value - 2 \cdot Impact\_\{abs})^2}\{(Topline\_Value - Impact\_\{abs})^4} $$ ================================================================================ # Meta-Analysis URL: https://docs.statsig.com/experiments/exploring-results/meta-analysis Updated: 2026-07-11 ================================================================================ ## How meta-analysis works As teams run many experiments, patterns emerge across those experiments. Meta-analysis surfaces those cross-experiment insights. Common questions include: - How hard is a metric to move? - Are there more sensitive proxies for the metric you care about? - How are teams doing relative to each other? Statsig has built meta-analysis to be useful whether you're running 50 experiments a year or 5000. Join [Slack](https://statsig.com/slack) to help influence the roadmap. ## Experiment timeline view This view lets you filter down to experiments a team has run. At a glance you can answer questions like: 1. What experiments are running now? 2. When are they expected to end? 3. What percentage of experiments ship Control vs Test? 4. What is the typical duration? 5. Do experiments run for their planned duration, or much longer or shorter? 6. Do experiments impact key business metrics, or only shallow or team-level metrics? 7. How much do they impact key business metrics? {% figure %} ![Experiment timeline view dashboard](/images/snippets/stats-methods/meta-analysis/d1e1db49-18b4-46af-93f7-4c0e58c5bdb0.png) {% /figure %} ## Metric impact (batting average) The "batting average" view shows how easy or hard a metric is to move. Filter to a set of shipped experiments and see how many experiments moved a metric by 1% vs 10%. You can filter by team, tag, or statistical significance. Common uses include: - Validating whether a claim that the next experiment will move this metric by 15% is reasonable. - Establishing realistic goals based on past ability to move this metric. {% figure %} ![Metric batting average analysis chart](/images/snippets/stats-methods/meta-analysis/72f987a4-02ba-4937-9f3d-06892c55e4e9.png) {% /figure %} ## Metric correlation view This view lets you visualize two metrics and inspect them for correlation. Each data point represents one experiment's impact on both metrics. Often the metric you want to move isn't very sensitive and takes a while to measure. Finding more sensitive, faster-to-measure proxy metrics and running experiments on those proxies can accelerate your work. You can remove outliers, filter to a team's experiments, or download the underlying dataset. In this hypothetical example, "Checkouts" is the metric you want to move, but it isn't very sensitive. "AddToCart" correlates well with "Checkouts", while "ViewItemDetail" doesn't. {% figure %} ![Metric correlation scatter plot](/images/snippets/stats-methods/meta-analysis/016285b0-cdc4-46a4-8fdf-204b20b5b0e4.png) {% /figure %} {% figure %} ![Metric correlation analysis interface](/images/snippets/stats-methods/meta-analysis/c29e5d7d-d8b6-4841-acaf-8d521a5db398.png) {% /figure %} ## Metric insights This view lets you pick a metric and see all experiments and feature rollouts that impact it. [Learn more](/experiments/exploring-results/aggregated-impact). {% figure %} 417923655-430563dc-4794-4d69-a314-36c76a6fcf74 {% /figure %} ## Knowledge Bank The Knowledge Bank is a searchable repository of experiment learnings across teams. It helps you find shipped, healthy experiments, gain context on past work, and generate ideas for new experiments. New team members can explore experiments a team ran or search by topic. The meta-analysis tools offer more structured ways to discover and review your experiment corpus. The Knowledge Bank is available when you need free-text search. {% figure %} ![Knowledge bank search interface](/images/snippets/stats-methods/meta-analysis/860430c4-a754-4ff8-aa85-8bf49a755801.png) {% /figure %} ================================================================================ # Differential Impact Detection URL: https://docs.statsig.com/experiments/exploring-results/differential-impact-detection Updated: 2026-07-11 ================================================================================ {% partial file="whn/differentialImpact.mdx" /%} ================================================================================ # Interaction Detection URL: https://docs.statsig.com/experiments/exploring-results/interaction-detection Updated: 2026-07-11 ================================================================================ ## What is interaction detection When you run overlapping experiments, the experiments can interfere with each other. Interaction Detection lets you pick two experiments and evaluate them for interaction. This helps you understand whether users exposed to both experiments behave differently from users exposed to only one. The core question is: **Does being in both experiments change the effect on a metric in a way that differs from summing the individual effects?** ## Should I worry about it? The general guidance is to run overlapping experiments. Users viewing your landing page may experience multiple experiments at the same time. This is [supported by industry experience](https://www.statsig.com/blog/embracing-overlapping-a-b-tests-and-the-danger-of-isolating-experiments) and [published research](https://www.microsoft.com/en-us/research/articles/a-b-interactions-a-call-to-relax/). Teams expecting to run conflicting experiments can avoid conflicts by making experiments mutually exclusive using [Layers](/experiments/layers-overview) (also referred to as Universes). ## How to use interaction detection To start an Interaction Detection analysis, go to **Experiment > Results > Explore > Interaction Effect Detection**. {% figure %} ![Interaction Detection interface in Statsig console](/images/experiments/exploring-results/interaction-detection/b49e62b2-ba95-4db3-8d5a-3ce3f08b620d.png) {% /figure %} Select a second experiment and the metrics to analyze. Statsig then shows a summary with three sections: * **By Groups**: unit counts based on the collective slice of group assignment * **Metric Summary Results**: estimated intervals of difference in metric lift * **Overlapping Unique Users**: an overview of the general traffic intersection of the two experiments {% figure %} ![Interaction Detection analysis results dashboard](/images/experiments/exploring-results/interaction-detection/ebbb4383-8abe-4b9b-9132-fb543e6b33ac.png) {% /figure %} Refer to this [article](https://www.statsig.com/blog/interaction-effect-detection) for additional examples. ## Methodology Assume you have two experiments A and B, each with two groups: control and test. The interaction effect calculates the overlapping impact on users exposed in both experiment A and experiment B. $$ \Delta\_\{\text\{treatment effect}} = (\overline\{X\_\{testA \cdot testB}} - \overline\{X\_\{testA \cdot controlB}}) - (\overline\{X\_\{controlA \cdot testB}} - \overline\{X\_\{controlA \cdot controlB}}) $$ $$ \Delta\_\{\text\{treatment effect}}\{\\%} = \frac\{\Delta\_\{\text\{treatment effect}}}\{(\overline\{X\_\{controlA \cdot testB}} - \overline\{X\_\{controlA \cdot controlB}})} $$ $$ variance = \frac\{Var(X\_\{testA \cdot testB})}\{n\_\{testA \cdot testB}} + \frac\{Var(X\_\{testA \cdot controlB})}\{n\_\{testA \cdot controlB}} + \frac\{Var(X\_\{controlA \cdot testB})}\{n\_\{controlA \cdot testB}} + \frac\{Var(X\_\{controlA \cdot controlB})}\{n\_\{controlA \cdot controlB}} $$ ### Intuition * The part $\overline\{X\_\{testA \cdot testB}} - \overline\{X\_\{testA \cdot controlB}}$ answers: “If someone is in A’s test, how much does changing B from control to test change the metric?” * The part $\overline\{X\_\{controlA \cdot testB}} - \overline\{X\_\{controlA \cdot controlB}}$ answers: “If someone is in A’s control, how much does changing B from control to test change the metric?” * Subtracting those two values shows whether the effect of B differs depending on whether the user is in A’s test or control group. That difference is the interaction effect. If the result isn’t statistically significant, the effect of B is the same regardless of which group the user is in for A (no significant interaction). If it is statistically significant, the effect of B depends on A’s assignment (significant interaction). ### Directionality The direction (positive or negative) of the interaction effect indicates whether the combination of the two treatments amplifies or dampens each other’s effects. The interaction effect is defined as: $Interaction Effect=(Effect of B when A=Test)−(Effect of B when A=Control)$ - **Statistically positive interaction**: B’s effect is stronger when A is also active. - **Statistically negative interaction**: B’s effect is weaker (or reversed) when A is active. ### Magnitude The magnitude (absolute size) of the interaction effect indicates how much the combined impact deviates from simple additivity. For example: * Experiment A produces a **+5%** lift on your metric of interest * Experiment B produces a **+5%** lift on your metric * The interaction effect between A and B is **–3%** This means that when both experiments are rolled out together, their overall impact is expected to be about 3% lower than the sum of their individual effects: the features interfere with each other. To obtain the most accurate estimate of the true combined impact, run a new experiment that includes both features together. Because experiments A and B may not have run over exactly the same time period or under identical conditions, differences in timing and seasonality can slightly influence the measured interaction magnitude. {% callout type="info" %} When an experiment includes more than two groups, interaction effects are evaluated **pairwise between groups**. You can view the interaction effect for specific group combinations by selecting the groups you want in the **”Select Comparison”** dropdown, as shown in the UI above. {% /callout %} ================================================================================ # Layers URL: https://docs.statsig.com/experiments/layers-overview Updated: 2026-07-11 ================================================================================ ## What are layers Layers (also known as Universes) allow you to create experiments that are mutually exclusive. Each layer has a logical representation of all your users and can have experiments created within it. Users assigned to one experiment in a layer cannot be in another experiment in the same layer. {% figure %} ![Layer concept diagram showing mutually exclusive experiments](/images/experiments/layers-concept.png) {% /figure %} You can add experiments to a layer (or create a layer) during experiment creation. {% figure %} ![Experiment creation modal with layer selection](/images/experiments/layers-create.png) {% /figure %} After you create a layer, you can manage layers on the layer management tab under Experiments. {% figure %} ![Layers overview tab listing active layers](/images/experiments/layers-tab.png) {% /figure %} {% figure %} ![Layer details page showing shared parameters](/images/experiments/layer-details.png) {% /figure %} Layers also improve engineering efficiency and iteration velocity for product teams. In a Layer, parameters exist at the Layer level and can be shared across experiments within the Layer. This allows the concept of "Experiment" to be abstracted away from the SDKs, so that users only need to work with parameters in code. Multiple experiments that change the same thing can then run and iterate without any code changes. Suppose your product has an important signup dialog with text that your team tests frequently. Some tests run in parallel, others are iterations of previous experiments. Working directly with Experiments, your code grows like this over time: ```jsx let signUpText = DEFAULT_SIGNUP_TEXT; const signUpTestV1 = statsig.getExperiment("sign_up_dialog_text_test_v1"); const signUpTestV2 = statsig.getExperiment("sign_up_dialog_text_test_v2"); const specialSignUpTest = statsig.getExperiment("sign_up_test_special_offer"); const holidaySignUpTest = statsig.getExperiment("sign_up_test_holiday"); if (signUpTestV1.get("is_in_test", false)) { // original test, added in app version v1.2 signUpText = signUpTestV1.get("dialog_content", DEFAULT_SIGNUP_TEXT); } else if (signUpTestV2.get("is_in_test", false)) { // v2 of the original test, added in app version v1.6 because we wanted to test a new copy but don't want to stop v1 signUpText = signUpTestV2.get("dialog_content", DEFAULT_SIGNUP_TEXT); } else if (specialSignUpTest.get("is_in_test", false)) { // test showing a special offer in the text, added in v2.0 signUpText = specialSignUpTest.get("dialog_content", DEFAULT_SIGNUP_TEXT); } else if (holidaySignUpTest.get("is_in_test", false)) { // test showing some holiday greetings in the dialog, added in v2.1 signUpText = holidaySignUpTest.get("dialog_content", DEFAULT_SIGNUP_TEXT); } // Then we display the text in the dialog ``` Every time you add a new test, you must change the code, and the change is only available after a new release. Working with Layers is much simpler: ```jsx let signUpText = statsig .getLayer("sign_up_tests") .get("sign_up_dialog_text", DEFAULT_SIGNUP_TEXT); // Then we display the text in the dialog ``` That is all the code you ever need. No more code changes and app releases for new tests. To add a new test, add a new experiment to the same Layer and choose `sign_up_dialog_text` as a parameter. The SDK determines which value to serve for the user based on which experiment the user is allocated to. ## getExperiment vs getLayer API {% callout type="warning" %} Even though layered experiments remain technically accessible through `getExperiment`, that API evaluates only the current experiment. Use `getLayer` so the SDK honors layer-level decisions, mutual exclusion, and shared parameters. {% /callout %} ## How exposures work with layers Calling `getLayer(LayerName)` by itself doesn't log an exposure. Statsig logs a `statsig::layer_exposure` event when you access a specific parameter within the Layer using `getLayer(LayerName).get(Parameter)`. * If Statsig assigns the user to an experiment within the Layer, the `statsig::layer_exposure` event is billable. * If the user isn't assigned to an experiment within the Layer, the `statsig::layer_exposure` event isn't billable. {% callout type="note" %} Repeated reads of the same Layer parameter for the same user within the deduplication window count as a single billable exposure. Reads of different Layer parameters may count separately. {% /callout %} ================================================================================ # Holdouts URL: https://docs.statsig.com/experiments/holdouts-introduction Updated: 2026-07-11 ================================================================================ Holdouts measure the aggregate impact of multiple features. A holdout keeps a group of users back from a set of features for measurement. While each A/B test or experiment compares control and test groups for that feature, a holdout compares the holdout group (Control) against a balanced group of users who were not held out and continued through the normal rollout or experiment behavior for the included features. ## How to use holdouts 1. Navigate to the [Holdouts section on the Statsig console](https://console.statsig.com/holdouts) (a specialized kind of experiment) and click **Create New**. 2. Enter the name, description, and unit type for the holdout. 3. Choose a global or selected holdout. A global holdout is automatically added to any new feature with the same unit type, capturing the aggregate impact of all features developed after the holdout began (individual features may be opted out as needed). A selected holdout captures the aggregate impact of a specific set of features. 4. By default, holdouts apply to a percentage of all users (Population = Everyone). To target a subset of users, apply a Targeting Gate (Population = Targeting Gate). For example, to create an iOS-only holdout, apply a Targeting Gate that passes only iOS users. 5. Set the holdout percentage between 1% and 10%. Statsig recommends a small holdout percentage to limit the number of users who don’t see new features. {% figure %} ![Holdout creation interface](/images/experiments/holdouts-introduction/81b634c7-f05e-493e-88c1-4c58d770a1d4.png) {% /figure %} {% figure %} ![Holdout configuration settings](/images/experiments/holdouts-introduction/82c06037-7a12-4335-9ebf-81156a54d798.png) {% /figure %} ## How to read holdouts Holdouts use the [same “equal variant” methodology](/feature-flags/view-exposures#gate-exposures) as Feature Gate rollouts: Statsig computes metric lifts using equal-sized groups to calculate holdout lift. Go to [“A/B Testing Intuition Busters: Common Misunderstandings in Online Controlled Experiments”](https://drive.google.com/file/d/1oK2HpKKXeQLX6gQeQpfEaCGZtNr2kR76/view) by Ron Kohavi, Alex Deng, & Lukas Vermeer for more on the advantages of this methodology. Accordingly, the Cumulative Exposures panel for a given Holdout shows total exposures of the Holdout, broken down into three groups: 1. **In holdout (Control)**: Units that were included in the Holdout and used for analysis. 2. **Not in holdout (Test); used for analysis**: Units that were not included in the Holdout and were selected for comparison against the holdout group. 3. **Not in holdout (Test); not used for analysis**: Units that were not included in the Holdout and were not used in the lift calculation. For units not included in the Holdout, Statsig generates the two "Not in holdout" groups using random sampling. Statsig sizes the group used for analysis to balance the comparison against the holdout group. Holdout metric lifts represent the cumulative impact of launched and active experiments on the Holdout group vs. the same percentage of the rest of the population continuing through normal behavior for the included rollouts and experiments. The "Not in holdout (Test); used for analysis" group isn't necessarily made up of users who saw every treatment. Those users follow the normal non-holdout behavior for each included gate or experiment. In the example below, the 1% Holdout compares metric values of users in Holdout vs. 1% of users not in Holdout. The comparison doesn't include the full remaining 99% of users. The launched features are having an overall negative effect on the "Add to Cart" metric. {% figure %} Holdout pulse results showing metric lift comparison between holdout and exposed users {% /figure %} ## Best practices 1. **Size**: Statsig recommends a low single-digit holdout percentage, such as 1%–2%, to limit the number of customers who don't see new features. 2. **Duration**: Statsig recommends operating holdouts for three to six months, then releasing the holdout. Prolonging the holdout period may increase software complexity, because you must maintain a functioning product with no new features for a longer period. 3. **Back testing**: Occasionally you may want to turn off a set of already-released features to measure their effectiveness. Statsig doesn't recommend this approach because it turns off features that users are already using. However, when a "back measurement" is critical, you can use Holdouts to turn off a set of features and automatically compute their impact. ## Unit ID types By default, holdouts are based on User ID. To use a different ID type, select it from the drop-down menu during holdout creation. {% callout type="note" %} Holdouts can only be applied to Experiments and Feature Gates that use the same randomization unit. If a team plans to run experiments on both User ID and Stable ID, two separate holdouts are required to evaluate the cumulative impact of each type of experiment. {% /callout %} {% figure %} ![Holdout unit ID type selection](/images/experiments/holdouts-introduction/cb2125f0-7fe2-4fbe-b6a3-3405dc300715.png) {% /figure %} ## Holdout effects on gates & experiments SDK methods ### Feature flags/gates - For users in holdout, gates always return `False`. ### Experiments - For users in holdout, if the experiment _isn't in a Layer_, calls to get experiment parameters always return the "default value" passed in code. - For users in holdout, if the experiment _is in a Layer_, calls to get experiment parameters return the values defined in the Layer defaults in the Statsig console. {% callout type="note" %} When you ship an experiment in a layer, this would normally update the layer defaults. However, users in the holdout will _not_ see those defaults. Instead, the layer has a separate set of default parameters only for held-out users: {% /callout %} {% figure %} ![Layer Holdout Params](/images/experiments/layer_holout_params.png) {% /figure %} ## Ending a holdout To end a holdout and allow users in the holdout group to see all held-out features, disable the holdout. Disabling it stops tracking the effects of those features, but Statsig retains the results for future reference. Alternatively, delete the holdout if it was created by mistake or if you no longer need to keep the results. ================================================================================ # Feature Flags URL: https://docs.statsig.com/feature-flags/overview Updated: 2026-07-11 ================================================================================ **Feature Gates**, commonly known as feature flags, allow you to toggle the behavior of your product in real time without deploying new code. Developers often use them to turn on certain features for a small percentage of the total user base. This approach enables safer, gradual software releases because you can monitor the impact on system behavior. Feature Gates also let you limit application behavior for a specific set of users, such as dogfooding [environments](/guides/using-environments). {% figure %} ![Feature Gates hero image](/images/feature-flags/featuregates.png) {% /figure %} ## When to use #### Use when you need to * Schedule gradual feature rollouts to safely deploy new code * Set up dev staging environments before code hits production, like dogfooding * Have an emergency disable switch that lets you immediately turn off a particular code branch for users in production * Modify the user experience based on attributes like username, email, or other identifiers * Change app behavior based on context like device, browser type, version, and other environment attributes #### Not recommended if * You need to return structured or multi-value data based on targeting rules and conditions: use a [Dynamic Config](/dynamic-config/overview) instead. * You want to test complex hypotheses beyond simple A/B tests and launch impact: set up an [Experiment](/experiments/overview) instead. ## How it works 1. [Create a Feature Gate](/feature-flags/create) with [targeting rules](/feature-flags/conditions) in the Statsig console. 2. To have the Feature Gate impact users, integrate the [Statsig SDK](/sdks/getting-started) into your product code. The SDK queries the gate value at runtime and returns a true/false result based on user attributes, environment data, and other conditions you define. 3. [Test a Feature Gate](/feature-flags/test-gate) to confirm it behaves as expected before rolling it out. 4. For finer targeting control, set up [Feature Gate overrides](/feature-flags/overrides) to allow specific users to bypass the gate. 5. After your Feature Gate is live, [view Feature Gate exposures](/feature-flags/view-exposures) in the Statsig console to monitor who encounters your gate. 6. Set up deprecation rules and clean up old flags by [managing Feature Gate lifecycles](/feature-flags/feature-flags-lifecycle). ## Key capabilities ### Scheduled rollouts Gradually deploy a feature over time by setting up a Feature Gate as a [Scheduled Rollout](/feature-flags/scheduled-rollouts). ### Overrides and bypass lists Implement Feature Gates with [Overrides](/feature-flags/overrides) to allow a specific list of users to bypass your gate. ### Chained flag dependencies Chain Feature Gates together in parent-child or other dependent relationships so a top-level gate can enable or disable all its dependent flags at once. This is useful for global disable switches that guard sub-features. ### Built-in A/B tests Run simple A/B tests without additional setup using [Pulse](/feature-flags/view-exposures). In practice, this means treating the users who see the new feature as the "treatment" group, and the users who are gated (and therefore don't see the new feature yet) as the "control". ### Feature Gate testing [Test your Feature Gates](/feature-flags/test-gate) with Statsig's built-in tools to check whether your Feature Gate is configured to target the right people. ## FAQs {% accordion-group %} {% accordion title="How are Feature Gates different from Dynamic Configs?" %} Dynamic Configs are key-value configs that let you return structured data (not just true/false) based on targeting rules. They're more for customizing behavior, tuning parameters, or supporting complex logic *beyond booleans*. Feature Gates are simpler on/off switches for gating access to a feature. Technically, you can set up a Dynamic Config as a Feature Gate. {% /accordion %} {% accordion title="When should I use Feature Gates instead of Experiments (and vice versa)?" %} Go to [Choosing Feature Flags vs. Experiments](/guides/featureflags-or-experiments) for a full guide. Use Feature Gates to measure the general impact of a feature rollout. Use Experiments when you have a more specific hypothesis or test in mind. {% /accordion %} {% /accordion-group %} ## Related tutorials * [Set up dev environments with Feature Gates](/guides/using-environments) * [Customize dev environments with Feature Gates](/guides/testing) * [Choosing Feature Flags vs. Experiments](/guides/featureflags-or-experiments) * [Best Practices for Feature Gates](/feature-flags/best-practices) ================================================================================ # Build Your First Feature URL: https://docs.statsig.com/guides/first-feature Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Statsig refers to feature flags as feature gates across the console and SDKs. The terms are interchangeable throughout this guide. {% /callout %} After your Statsig account is ready, follow the steps below to create and test-drive a new feature gate. {% steps %} {% step title="Create a feature gate" %} Navigate to the [Feature Gates page](https://console.statsig.com/gates) and click Get Started (or Create if you already have gates). {% figure %} ![Feature Gates page with Create button](/images/guides/first-feature/feature-gates-page.png) {% /figure %} Give the gate a clear name and description. For example, Mobile Registration with a note about the new mobile sign-up flow. {% /step %} {% step title="Target mobile platforms" %} New gates default to returning false until you add targeting. Click Add New Rule, choose Operating System → Any of, and select Android and iOS. Set the pass percentage to 100% and click Add Rule, then Save. {% figure %} ![Adding a mobile targeting rule](/images/guides/first-feature/add-rule.png) {% /figure %} {% /step %} {% step title="Add an internal testing rule" %} Layer on a second rule for your team. For example, use Email → Contains any of with your company domain so employees can exercise the feature regardless of device. {% figure %} ![Gate with mobile and email rules](/images/guides/first-feature/gate-rules.png) {% /figure %} {% /step %} {% step title="Generate a client API key" %} Go to [Project Settings → API Keys](https://console.statsig.com/api_keys) and copy the Client API key. Keep server secret keys on backends only, and use console API keys for programmatic configuration work. {% /step %} {% step title="Load the JavaScript SDK" %} {% callout type="tip" %} Statsig supports many platforms. Refer to [Client SDK options](/sdks/getting-started) for alternatives. This walkthrough uses the browser SDK so you can experiment directly in DevTools. {% /callout %} Paste the snippet below into the browser console on any site to load the SDK from jsDelivr: ```js const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/@statsig/js-client@3/build/statsig-js-client+session-replay+web-analytics.min.js'; document.head.appendChild(script); ``` {% figure %} ![Injecting the SDK via DevTools](/images/guides/first-feature/add-sdk-script.png) {% /figure %} {% /step %} {% step title="Initialize and check the gate" %} Replace YOUR\_SDK\_KEY with the client key from Step 4 and run: ```js const client = new window.Statsig.StatsigClient('YOUR_SDK_KEY', {}); await client.initializeAsync(); ``` Then call: ```js client.checkGate('mobile_registration'); ``` You should see false because the current session is not mobile and doesn’t use the employee email domain. {% /step %} {% step title="Simulate a mobile environment" %} Enable the mobile device toolbar in Chrome DevTools. {% figure %} ![Chrome DevTools mobile device toolbar icon](/images/guides/first-feature/devtools-mobile-toolbar.png) {% /figure %} Re-evaluate the user to pick up the new environment and re-check the gate: ```js await client.updateUserAsync({}); client.checkGate('mobile_registration'); ``` The gate should now return true for the mobile profile. {% figure %} ![Gate returning true for mobile rule](/images/guides/first-feature/mobile-rule-true.png) {% /figure %} {% /step %} {% step title="Test the employee backdoor" %} Switch DevTools back to the desktop view and update the user with a company email: ```js await client.updateUserAsync({ email: 'teammate@statsig.com' }); client.checkGate('mobile_registration'); ``` The gate passes again because of the email rule. {% figure %} ![Gate returning true via email rule](/images/guides/first-feature/email-rule-true.png) {% /figure %} {% /step %} {% step title="Flush exposures and inspect diagnostics" %} ```js client.flush(); ``` Open the gate’s Diagnostics tab to confirm each exposure, including the failing desktop check, mobile pass, and employee pass. {% figure %} ![Diagnostics exposure stream showing recent checks](/images/guides/first-feature/diagnostics-stream.png) {% /figure %} {% /step %} {% /steps %} ## Use the gate in production Wrap feature logic in a gate check so only targeted users see the experience: ```js if (client.checkGate('mobile_registration')) { show(mobileRegistrationPage); } else { show(oldRegistrationPage); } ``` Your feature gate is ready for production. ================================================================================ # When to Use Feature Gates vs. Experiments? URL: https://docs.statsig.com/guides/featureflags-or-experiments Updated: 2026-07-11 ================================================================================ {% callout type="info" %} In Statsig, feature flags are called feature gates. The terminology is interchangeable throughout this guide. {% /callout %} Both feature gates and experiments create control/test groups. Use this guide to pick the right tool for your launch and measurement goals. *** ## Quick Guidance * **Choose a feature gate** when you want to roll out a feature gradually or monitor impact as you ramp. * **Choose an experiment** when you need to compare multiple variants and quantify the lift across metrics. *** ## Key Differences ### Variants * **Feature gate** → Two experiences only: pass vs. fail. * **Experiment** → Any number of variants. When viewing gate exposures you’ll see three buckets: Pass, Fail, and Fail – Not in Analysis. Statsig uses only the balanced subset of the fail group for metric comparisons. Learn more in the [gate exposure methodology](/feature-flags/view-exposures#gate-exposures). ### Return Values * **Feature gate** → Boolean (`true`/`false`) so your application toggles code paths. * **Experiment** → JSON config that describes the variant (colors, copy, thresholds, etc.). ### Ramping knobs * **Feature gate** → Adjust Pass % to send more traffic to the new experience. You can go beyond 50/50 (e.g. 99% vs 1%). * **Experiment** → Adjust Allocation % to enroll more users, but splits cap at 50/50. After Statsig assigns a user, neither control reshuffles existing users. You can safely ramp without re-bucketing. {% figure %} ![Pass% versus Allocation% controls](/images/guides/featureflags-or-experiments/pass-vs-allocation.png) {% /figure %} *** ## When Experiments Shine Use experiments when you need: 1. **Multiple variants or personalization** – compare more than two options or tailor experiences using contextual bandits or layers. 2. **Stable identifiers and custom IDs** – analyze behavior before signup with stable IDs, or use custom IDs for sessions, workspaces, or geography. 3. **Isolated universes** – run parallel experiments safely by placing them in their own layers. *** ## When Feature Gates Shine Feature gates are great for: * **Safe rollouts** – gradually increase exposure while observing metrics. * **Targeting audiences** – use gates as pre-filters before enrolling users in an experiment. In experiment setups, gates often act as targeting criteria. The flow looks like this: 1. **Targeting gate** picks the eligible audience. 2. **Allocation %** (experiment) decides how much of that audience participates. 3. **Split %** distributes participants across variants. After you choose a winner, lift the targeting gate and let the winning variant reach everyone. *** ## Summary: choosing the right tool * Start with a **feature gate** if you have a single variant to launch carefully. * Reach for **experiments** when you need quantitative comparisons across variants. * Combine both when you want precise audience control plus rigorous measurement. For more detail, refer to: * [Feature gate exposures](/feature-flags/view-exposures) * [Experiments overview](/experiments/overview) * [Layers for mutual exclusion](/experiments/layers-overview) ================================================================================ # Create a Feature Gate URL: https://docs.statsig.com/feature-flags/create Updated: 2026-07-11 ================================================================================ ## In the Statsig console ### Create a new feature gate 1. Log into the Statsig console at https://console.statsig.com. 2. On the left-hand navigation panel, under **Feature Management**, select **Feature Gates**. 3. Click on the **Create** button. 4. Enter the name and description of the Feature Gate you want to create. Name your gate based on what you are rolling out, such as "Zippy Home Page" for a new homepage. {% figure %} ![Feature gate creation form](/images/feature-flags/create/a14bc4f3-b768-4e6c-a84a-7bae449b2c7c.png) {% /figure %} 5. Click **Create** to complete creating your Feature Gate. 6. You have created a new Feature Gate without any evaluation rules or conditions. {% figure %} Newly created feature gate without rules {% /figure %} ### Add a rule to your feature gate By default, a Feature Gate returns `false` when there are no rules configured. All users are blocked from seeing the feature until you set rules for who gets to "pass". The following steps walk through adding evaluation rules or conditions for a Feature Gate. 1. In Statsig console, under **Feature Management**, select **Feature Gates**. 2. Select the feature gate where you want to add a targeting rule 3. Click the **+ Add New Rule** button. {% figure %} ![Add rule button interface](/images/feature-flags/add-rule.png) {% /figure %} 4. Name your rule with something descriptive that other teammates will understand, such as "Mobile Users Only". 5. Configure your Feature Gate with the following options: * **Environment(s)** - The [staging environment(s)](/guides/using-environments) you want your gate to apply to * [**Criteria**](/feature-flags/conditions) - Specific evaluation conditions for the rule. Read more details about criteria and condition evaluation [here](/feature-flags/conditions). * **Split %** - The percentage of users who meet the criteria that you want to pass or fail the gate check. The Fail % is automatically calculated based on the Pass % you set * [**Overrides**](/feature-flags/overrides): A list of users you want to always bypass your gate (an allow list) 6. Statsig doesn't auto-save changes to Feature Gates and targeting rules. Select **Save** at the bottom right when you are ready for your changes to take effect. ## In your code The previous steps covered setting up Feature Gates in the web console. For these gates to impact the behavior of your application, you also need to update your product code. The Statsig SDK handles the evaluation so that gate decisions take effect in real time. ### Initialize the Statsig SDK If you have not already initialized the Statsig SDK, follow the [Installation Steps](/client/React#installation) in the language of your choice. ### Check a feature gate Use the `checkGate` function in the Statsig SDK. You can find a code snippet for any particular gate by selecting the code snippet button on that gate's page in the Statsig console and selecting the SDK you want to use. {% figure %} ![Gate code snippet button](/images/feature-flags/create/b52add6e-4352-4be4-b902-56d77a33d28b.png) {% /figure %} Here's an example in [React](/client/React#basics-check-gate): ```tsx const { client } = useStatsigClient(); return (
Gate is {client.checkGate("example_gate") ? "passing" : "failing"}.
); ``` In this example, the Statsig SDK checks from a client app whether any rules named "Example Gate" are set. The text "Gate is passing" is rendered for users who pass your gate conditions, and "Gate is failing" for users who fail the gate conditions. {% callout type="tip" %} Statsig offers over 20 client and server-side SDKs. Go to the full list of [SDKs](/sdks/quickstart#all-sdks) to find the one that best fits your needs. {% /callout %} ## Common feature gate setups ### Kill switches Set up a kill switch by setting an `Everyone` criteria's Pass percentage to 100%. To completely disable the feature for all users after code deployment, set the Pass percentage to 0%. ### OS-based flags You can target users based on common application properties such as the operating system that the application is running on. For example, to target iOS users only, you can create a rule with the "Operating System" criteria, the "Any of" operator, and then listing "iOS" in the text field. ### Internal employees only You can target users based on known user attributes. For example, you can use the user's **Email** attribute and the **Contains any of** operator, then enter the email domain of your company to target only internal employees. ### Defined user segments You can also target users in a defined user [Segment](/segments/overview). ### Parent/child flags You can target users based on their eligibility of other Feature Gates, enabling powerful chaining, hierarchy, and dependencies of flags. [Flag lifecycle management](/feature-flags/feature-flags-lifecycle) makes it easy to manage, deprecate, and maintain dependent flags. ================================================================================ # Feature Gate rule criteria URL: https://docs.statsig.com/feature-flags/conditions Updated: 2026-07-11 ================================================================================ Statsig feature gates contain a list of rules that Statsig evaluates in order from top to bottom. This page describes how Statsig evaluates rules and lists all currently supported conditions. ## Rule evaluation Statsig evaluates the rules you create in the order they're listed. For each rule, the **criteria** or **conditions** determine which users *qualify* for the Pass/Fail treatments. The Pass percentage further determines the percentage of *qualifying* users that Statsig exposes to the new feature. The remaining *qualifying* users see the feature disabled. For rules set up as shown below, the following flowchart illustrates how Statsig evaluates them. {% figure %} ![Example Rules Gate](/images/conditions/example-rules-gate.png) {% /figure %} {% figure %} ![Feature gate rules evaluation flowchart](/images/conditions/rules-evaluation-flowchart.png) {% /figure %} After a user qualifies based on the condition in a given rule, Statsig doesn't evaluate subsequent rules for that user. Statsig then assigns the qualifying user to either the Pass or Fail group of that rule. In the example, the third rule for **Remaining Folks** captures all users who don't qualify for the previous two rules. Removing this third rule means only users in pools 1 and 2 qualify for this feature gate and for analysis, not your total user base. ### Client vs server SDKs All of the following conditions work on both client and server SDKs. Client SDKs handle several of these conditions automatically: if you don't provide a userID, client SDKs rely on an auto-generated stable identifier persisted to local storage. If you don't set an IP or User Agent (UA), the client SDK infers these attributes from the request. On mobile, the client SDK automatically passes your app version and locale to the server so Statsig can evaluate conditions using these attributes without you setting them explicitly. ### Stability Evaluations at a given percentage are *stable* with respect to the unitID. For example, if the gate/config/experiment/layer has a unit type of "userID", and userID = 4 passes a condition at a 50% rollout, they will always pass at that 50% rollout. The same applies for `customIDs`, if the unit type of the entity is that `customID`. Want to reset that stability? Refer to "Resalting" below. ### Resalting Gate evaluations are stable for a given gate, percentage rollout, and user ID. This stability is based on a salt associated with the feature gate. To reset a gate and reshuffle users, select "resalt" from the dropdown menu in the top right of the feature gate details page. {% figure %} ![Resalt UI](/images/conditions/resalt-ui.png) {% /figure %} ### Partial rollouts A 0% rollout means "off for all users matching this rule" and a 100% rollout means "on for all users matching this rule." Each rule lets you specify a percentage of qualifying users who pass (and see the new feature). Specifying a number between 0% and 100% creates a random allocation of users in Pass/Fail or test/control groups for a simple A/B test. Use this to validate that a new feature doesn't regress existing metrics as you roll it out. Statsig recommends a 2% → 10% → 50% → 100% rollout strategy. Each progressive rollout generates its own Pulse Results, as shown below. {% figure %} ![Metric Lifts](/images/conditions/metric-lifts.png) {% /figure %} ### User object fields Evaluation uses the set of properties defined in the [StatsigUser object](/sdks/user). There are a set of reserved top-level fields, but these keywords are reserved and recognized in the `custom` and `privateAttributes` maps as well. For example, if you set `user.country`, `user.custom.country`, or `user.privateAttributes.country`, Statsig uses that value to evaluate a country condition (checked in that order: top level, then custom, then privateAttributes, case-insensitively). If `user.country` isn't defined but `user.custom.COUNTRY` is, Statsig uses `user.custom.COUNTRY` to evaluate the country condition. ## Supported conditions ### User ID Usage: Simple lists of User IDs to explicitly target or exclude from a gate. Supported Operators: `Any of, none of` Example usage: Add yourself (or a small group like your team) when you start building a new feature. Or exclude your designer until the feature is ready for review. {% figure %} ![User ID condition example](/images/conditions/user-id-condition-example.png) {% /figure %} ### Email Usage: Target based on the email of the user Supported Operators: `any of, none of, contains any of, contains none of` Example: Show new feature to people in the Statsig company with an authenticated @statsig.com email address {% figure %} ![Email condition example](/images/conditions/email-condition-example.png) {% /figure %} ### Everyone Usage: Percentage rollout on the remainder of users that reach this condition. Think of it as "everybody else" - there could be a dozen other rules/conditions above it, but for everyone else, what percentage do you want to pass? Supported Operators: `None. Percentage based only.` Example usage: 50/50 rollout to A/B test a new feature. Or 0% to hide the feature for all people not matching a set of rules. Or 100% to show the feature to the remaining users who did not meet a condition above. {% figure %} ![Everyone 50/50 condition example](/images/conditions/everyone-5050-condition-example.png) {% /figure %} ### App Version Usage: Users on a particular version of your app/website pass. Particularly useful for mobile app development, where a feature may not be fully ready (or may be broken) in a particular app version. Supported Operators: `>=, >, <, <=, any of, none of` Example: Turn off a feature for all users on app versions 3.0.0 through 3.1.0 as it was broken. {% figure %} ![Disable broken versions example](/images/conditions/disable-broken-versions-example.png) {% /figure %} ### Browser Version Usage: A particular version of a browser, parsed from the user agent. Should likely be combined with the browser name condition. Supported Operators: `>=, >, <, <=, any of, none of` Example: Turn off a feature for old versions of chrome which don't support a certain API {% figure %} ![Browser Name and Version Example](/images/conditions/browser-name-version-example.png) {% /figure %} ### Browser Name Usage: A particular browser, parsed from the user agent: ('Chrome', 'Chrome Mobile', 'Edge', 'Edge Mobile', 'IE', 'IE Mobile', 'Opera', 'Opera Mobile', 'Firefox', 'Firefox Mobile', 'Mobile Safari', 'Safari'). Often combined with the Browser Version condition. Supported Operators: `>=, >, <, <=, any of, none of` Example: Turn off a feature for old versions of chrome which don't support a certain API To test: The Browser Name is inferred from the `userAgent`, but if you need to set it explicitly, you can set `browserName` in the user object. {% figure %} ![Browser Name and Version Example](/images/conditions/browser-name-version-example.png) {% /figure %} ### OS Version Usage: A particular os version the user is on, parsed from the user agent. Should likely be combined with the Operating System condition. Supported Operators: `>=, >, <, <=, any of, none of` Example: Turn off a feature for versions of macOS which don't support a certain API {% figure %} ![OS name and version example](/images/conditions/os-name-version-example.png) {% /figure %} ### Operating System Usage: A particular operating system, parsed from the user agent: ('Android', 'iOS', 'Linux', 'Mac OS X', 'Windows'). Often combined with the OS Version condition. Supported Operators: `any of, none of` Example: Turn off a feature for versions of macOS which don't support a certain API To test: The OS is inferred from the `userAgent`, but if you need to set it explicitly, you can set `deviceOS` in the user object. {% figure %} ![OS name and version example](/images/conditions/os-name-version-example.png) {% /figure %} ### Device Model Usage: The device model of the mobile device the user is on. Supported Operators: `any of, none of, is null, is not null, contains any of, contains none of, regex` Example: Turn off a feature for older device models that your app doesn't support. To test: The Device Model is automatically inferred by the SDK, but if you need to set it explicitly, you can set `deviceModel` in the user object. {% figure %} ![Device Model Example](/images/conditions/device-model-example.png) {% /figure %} ### Country Usage: A 2 letter country code, either passed as the user's country or determined by the IP address. Supported Operators: `any of, none of` Example: Show a cookie consent banner for users accessing your service from the EU. {% figure %} ![Country example](/images/conditions/country-example.png) {% /figure %} ### IP address Usage: An IP address string Supported Operators: `any of, none of` Example: Show a different about us page on www.statsig.com to people in a certain IP address range {% figure %} ![Matching IP Addresses example](/images/conditions/matching-ip-addresses-example.png) {% /figure %} ### Passes Target gate Usage: The condition passes if the referenced gate passes for the given user Supported Operators: `gate_id` Example: Only show feature X to people who also see feature Y: Only show a toggle to turn off ranking to people who also pass the new ranking algorithm gate. {% figure %} ![Passes Target Gate](/images/conditions/passes-target-gate.png) {% /figure %} ### Fails Target gate Usage: Inverse of passes target gate: the condition passes if the referenced gate returns false for the given user Supported Operators: `gate_id` Example: Only show a new UI element to people who are not using the redesigned UI. {% figure %} ![Fails Target Gate](/images/conditions/fails-target-gate.png) {% /figure %} ### User in Segment Usage: The condition passes if the user passes the rules defining the referenced segment. Refer to the [segments guide](/guides/using-environments) for more information on segments. Supported Operators: `segment_id` Example: Only show a new UI element to people who are not using the redesigned UI. {% figure %} ![In Segment](/images/conditions/in-segment.png) {% /figure %} ### User not in Segment Usage: The condition passes if the user fails the rules defining the referenced segment. Refer to the [segments guide](/guides/using-environments) for more information on segments. Supported Operators: `segment_id` Example: Only show a feature to people who are not in the premium/paid tier. {% figure %} ![Not In Segment](/images/conditions/not-in-segment.png) {% /figure %} ### Environment Tier Usage: The condition passes if the evaluation is happening in the given environment tier (development/staging/production). Refer to the [environments guide](/guides/using-environments) for more information on environments. Supported Operators: `development/staging/production/(other)` Example: Only show a feature on development builds {% figure %} ![Development Environment Condition](/images/conditions/development-environment-condition.png) {% /figure %} ### Custom Field Usage: Specify the key in the custom object to fetch the value used on the left-hand side of a comparison. Supported Operators: * string: `any of, none of, contains any of, contains none of` * number: `any of, none of, less than, greater than` * version: `any of, none of, less than, greater than, less than or equal to, greater than or equal to` * date: `before, after` Example: Only show a feature to users who have turned on dark mode, as marked by the custom object having "darkmode": true. {% figure %} ![Custom Field](/images/conditions/custom-field.png) {% /figure %} ### Unit ID Usage: Select custom ids to explicitly target or exclude from a gate. Supported Operators: `any of, none of, is null, is not null, contains any of, contains none of, regex` Example: Add yourself (or a small group like your team) when you start building a new feature. {% figure %} ![Unit ID example](/images/conditions/unit-id-example.png) {% /figure %} ### Time Usage: Evaluate a gate relative to the current time Supported Operators: `after time, before time` Value: time (displayed and input on Console in your browser's local time, but converted to a unix timestamp for evaluation) Example: Show the labor day sale banner on labor day {% figure %} ![Time condition](/images/conditions/time-condition.png) {% /figure %} ### Private attributes Usage: Any user field or custom field condition can be used through a private attribute. To target an email without logging it, create an email condition and put `"email": "xyz@email.com"` in the `privateAttributes` dictionary. For custom fields, create a custom field condition and put the key/value pair in `privateAttributes` instead. Statsig uses `privateAttributes` for evaluation only and doesn't store or include them in log events. Supported Operators: dependent on the condition Example: Only show a feature to 20 somethings, as marked by the privateAttributes object having "age": "20-29". {% figure %} ![Private attributes](/images/conditions/private-attributes.png) {% /figure %} ## FAQs {% accordion-group %} {% accordion title="Could users switch between control (fail) and test (pass) groups?" %} After a user is exposed, they are included in the analysis going forward. They saw the new feature and were affected. If the feature gate rules are modified, or the user's attributes change so that the user no longer qualifies, they stop receiving the new feature. However, they continue to be counted for analysis. After you roll out the feature, all users see the new feature; if you turn off the feature gate, all users see the control (feature disabled). In either case, Statsig performs no further analysis. {% /accordion %} {% accordion title="How do overrides work? Are users included in overrides also included in the analysis?" %} When you add user IDs in the **Pass** or **Fail** lists of your feature gate, these users see the appropriate treatment but aren't included in the analysis. {% /accordion %} {% /accordion-group %} ================================================================================ # Feature Gate overrides URL: https://docs.statsig.com/feature-flags/overrides Updated: 2026-07-11 ================================================================================ ## Override results of a feature gate During development, you may need to explicitly state which users should pass or fail a given feature gate. Overrides let you do this. Overrides are based on user IDs and you can set them to pass or fail for a given user ID. When Statsig evaluates a gate, if the user ID has an override, Statsig returns the overridden result immediately before evaluating any rules. ### Adding an override - Log into the Statsig console at https://console.statsig.com - On the left-hand navigation panel, select **Feature Gates** - Select the feature gate where you want to add an Override - Click the **Add Override** button {% figure %} ![Feature gate page with Add Override button highlighted](/images/feature-flags/overrides/148461864-4a5efd47-2e40-478f-81e5-49edfc5ea2e9.png) {% /figure %} - Select either 'Pass List' or 'Fail List' from the tabs in the dialog {% figure %} ![Manage overrides dialog showing pass and fail lists](/images/feature-flags/overrides/148462020-417c007f-d492-46f7-8d25-d7a5178b4fe2.png) {% /figure %} - For users you want to pass the gate, add them to the 'Pass List' - For users you want to fail the gate, add them to the 'Fail List' {% callout type="info" %} A user can only exist on one of these lists at a time. {% /callout %} - After you have added the user IDs, select **Save** ### Deleting an override If you add an override but later decide it isn't needed, you can remove it so Statsig evaluates the rules normally. - Log into the Statsig console at https://console.statsig.com - On the left-hand navigation panel, select **Feature Gates** - Select the feature gate where you want to add an Override - Click the **Edit Overrides** button {% figure %} ![Edit overrides button within feature gate settings](/images/feature-flags/overrides/148469394-9f86db83-26f6-4a5b-809f-8a53f0b4f1fa.png) {% /figure %} - Select the trashcan icon next to the user ID you want to remove from a list. {% figure %} ![Override list with trash icon to remove user ID](/images/feature-flags/overrides/148463292-6740a09f-337f-441a-9cfa-a547a5dc6183.png) {% /figure %} - After you have updated the lists, select **Save** ### Testing an override After you add your override, you can test it in the **Test Gate** window by adding `userID` as a property of the user object. {% figure %} ![Test gate console showing user object with override result](/images/feature-flags/overrides/148462648-e7da94bb-a681-4b0a-ad53-1254c6abe623.png) {% /figure %} Users that pass see "PASS (User ID Override)" {% figure %} ![Test gate output indicating PASS due to user ID override](/images/feature-flags/overrides/148462641-57b544c1-bbdb-48cf-bb7f-967b0b20fc63.png) {% /figure %} Users that fail see "FAIL (User ID Override)" {% figure %} ![Test gate output indicating FAIL due to user ID override](/images/feature-flags/overrides/148462634-de69b838-f7b1-431c-9119-64ce123ad218.png) {% /figure %} ================================================================================ # Test your Feature Gate URL: https://docs.statsig.com/feature-flags/test-gate Updated: 2026-07-11 ================================================================================ There are three ways to test your feature gate and validate that it's working as expected with the rules you have created: 1. Using the built-in **Test Gate** tool in the Statsig console 2. Using the prototype Javascript **Test App** available in the Statsig console 3. Using the **Diagnostics** tab in the Statsig console ## Option 1: Use the Test Gate tool To validate your feature gate using the built-in Test Gate tool: * Log into the Statsig console at https://console.statsig.com * On the left-hand navigation panel, select **Feature Gates** * Select the feature gate that you want to validate * At the bottom of the page, the **Test Gate** window lists all properties available in the rules you have created, as shown below: {% figure %} ![Test Gate interface showing property fields](/images/feature-flags/test-gate/129104501-9e7349ae-31fe-47ea-97da-0520fd3d7e1b.png) {% /figure %} * Click in the window and edit the value of the Email property to include the users you want to target. For example, type jdoe@example.com as shown below. When the email domain matches "@example.com", the feature gate check succeeds and the window shows PASS. Otherwise, the gate fails and the window shows FAIL. {% figure %} ![Test Gate showing PASS result for email validation](/images/feature-flags/test-gate/129104434-0f09087d-80da-4a62-84ac-c51e607e72a1.png) {% /figure %} ## Option 2: Use the Statsig Test App To validate your feature gate using the Test App: * Log into the Statsig console at https://console.statsig.com * On the left-hand navigation panel, select **Feature Gates** * Select the feature gate that you want to validate * At the bottom of the page, select **Check Gate in Test App** at the top right of the Test Gate window (shown by the red arrow). This opens a new browser window with a prototype JavaScript client that initializes and calls the Statsig `checkGate` API. {% figure %} ![Check Gate in Test App button location](/images/feature-flags/test-gate/138148684-581bb8d5-86ba-4aef-b24d-44e540fa91f1.png) {% /figure %} ## Option 3: Use the Diagnostics tab To validate your feature gate using a live log stream: * Log into the Statsig console at https://console.statsig.com * On the left-hand navigation panel, select **Feature Gates** * Select the feature gate that you want to validate * Click on the **Diagnostics** tab (next to the Setup tab) * Scroll down to the **Exposure Stream** panel, where you can see a live stream of gate check events as they happen: {% figure %} ![Exposure Stream panel showing live gate check events](/images/feature-flags/test-gate/138149819-5082d7e5-f7ee-42e8-b1ac-f57d9732e68f.png) {% /figure %} * In the **Event Count by Group** panel shown below, you can also validate that your application records events as expected for users who are exposed to the new feature (or not). If you have started recording a new event type to test the impact of a new feature, validate that these events appear as more users are exposed to the feature. {% figure %} ![Event Count by Group panel showing feature exposure metrics](/images/feature-flags/test-gate/141017409-f750c1c6-4c54-4140-bc4d-a3b83f1568fc.png) {% /figure %} ================================================================================ # Viewing Feature Gate exposures URL: https://docs.statsig.com/feature-flags/view-exposures Updated: 2026-07-11 ================================================================================ ## Gate exposures To see the number of users exposed to a feature gate: * Log into the Statsig console at https://console.statsig.com * On the left-hand navigation panel, select **Feature Gates** * Select the feature gate that you want to test * Click on the **Pulse Results** tab * The **Cumulative Exposures** panel shows total exposures of a feature gate, broken down into three groups: 1. Units that passed the feature gate and were used for analysis. 2. Units that did not pass the feature gate and were used for analysis vs. the "Pass" group. 3. Units that did not pass the feature gate and were *not* used for analysis vs. the "Pass" group. {% figure %} Feature gate cumulative exposures view {% /figure %} ## Balanced gates Statsig balances gates and holdouts by default in Cloud, and as an opt-in option for Warehouse Native holdouts (on the Setup page). This reduces false positives at the cost of some sample size. This is a best practice, and Statsig defaults to a balanced analysis. Go to section 7 of [AB Testing Intuition Busters](https://drive.google.com/file/d/1oK2HpKKXeQLX6gQeQpfEaCGZtNr2kR76/view) for more discussion. Statsig achieves this sampling during analysis by downsampling the larger arm proportionally to match the smaller group. For example, a `(100 - large_group_pct)/(large_group_pct)` sampling rate is applied to the larger group using an unbiased hashing approach, keeping the user pool consistent. Statsig rotates the hash salt across different gates and holdouts. ## Metric lifts The **Metrics Lifts** panel shows how your feature is performing based on lifts in any business metrics added to the list of **Monitoring Metrics** for your gate. Statsig uses an equal variant comparison (for example, 10% vs 10%) for calculating metric lifts for gate rollouts. Read more about the advantages of this methodology in ["A/B Testing Intuition Busters: Common Misunderstandings in Online Controlled Experiments"](https://www.researchgate.net/publication/361226478_AB_Testing_Intuition_Busters_Common_Misunderstandings_in_Online_Controlled_Experiments) by Ron Kohavi, Alex Deng, & Lukas Vermeer. In the example below, the rises in *product view count* and *purchase event count* are statistically significant, suggesting this feature positively impacts the number of product views, but may actually be negatively impacting conversions to purchases. {% figure %} Metric lifts showing feature performance {% /figure %} ## Bots & filtering {% callout type="info" %} Information on Bots & Filtering has been moved to its [own page](/experiments/monitoring/bots) {% /callout %} ================================================================================ # Pre-Post Results URL: https://docs.statsig.com/feature-flags/pre-post-results Updated: 2026-07-11 ================================================================================ {% callout type="info" %} **Cloud only feature** This feature is available only on Statsig Cloud. As Statsig works on a WHN solution, reach out to Statsig if you're interested in being an early customer. {% /callout %} ## How Pre-Post Results work in Statsig Pre-Post Results is an analysis mode for Feature Gates that lets you estimate the impact of feature rollouts when traditional A/B testing isn't possible. By comparing key metrics before and after a feature gate is rolled out from 0% to 100% of users, you can identify the directional impact of your features in production. {% figure %} ![Pre-Post Results Interface](/images/pre-post-results.png) {% /figure %} Pre-Post is particularly valuable for: * **Emergency rollouts** - Features that need to ship immediately without time for a slow rollout * **Infrastructure changes** - Backend improvements or technical features that affect all units/pods/users and can't be partially rolled out * **Retroactive analysis** - Understanding the rough impact of features that were already rolled out without experiments * **Regulatory or ethical features** - Changes that can't be withheld from a control group ## Pre-Post Results aren't experiments Pre-Post analysis measures the change in a metric around a specific point in time, among a specific set of exposed units. It doesn't meet the requirements of a proper A/B test or feature gate partial rollout. Pre-Post analyses are best understood as snapshot measurements around the time you launched your feature. Because many other factors can occur at the same time, there's no guarantee that your results are due to your feature launch. Correlation doesn't equal causation. Experiments remain the standard for measuring feature impact. Run launches as an Experiment or partial Feature Gate rollout when accuracy, validity, and extensibility are important. ## When does Statsig calculate Pre-Post Results? Statsig calculates Pre-Post Results for targeting rules that meet specific rollout conditions: 1. The targeting rule started at 100% pass rate or was rolled out from 0% to 100% in a single step 2. The rollout happened in the last 30 days When you select a qualifying rule in the Metrics Impact tab, Statsig automatically switches to Pre-Post Results mode and displays a banner indicating you're viewing Pre-Post analysis. ## How Pre-Post Results calculates feature impact Pre-Post Results uses the following approach to estimate feature impact: 1. **Identify the participating units** - Find all users who were exposed to the feature after the 100% rollout 2. **Collect pre/post-rollout data** - Gather metric values for these users from the periods before and after the rule change 3. **Bucket metric data into discrete periods** - Statsig automatically groups metric data into buckets of a consistent duration. 4. **Calculate the difference** - Compute the mean metric values for both pre and post periods, treating each bucket as a unique observation, then calculate the delta (difference) between them This method ensures the same users are compared before and after the feature rollout. ## Supported metric types | Metric type | Supported | |-------------|-----------| | Event Count | ✅ Yes | | Event Count Custom | ✅ Yes | | Event User | ✅ Yes | | Sum | ✅ Yes | | Mean | ✅ Yes | | Funnel | ❌ No | | Ratio | ❌ No | | Participation Rate | ❌ No | ## Best practices When using Pre-Post Results, follow these guidelines: * Focus on metrics that are directly related to your feature's intended impact and have sufficient volume. The more directly a metric responds to the feature launch, the easier it is to detect a sudden change. * Correlation doesn't equal causation. Consider other changes, seasonal effects, or external events that might influence your metrics during the analysis period. * Use Pre-Post Results as one data point alongside qualitative feedback, user research, and business context to make informed decisions. * If you can partially roll out a feature to less than 100% of users, do so. This lets you measure the metric impact for users seeing the feature compared to users not seeing the feature, and arrive at true causation. ## Known limitations * **30-day window** - Only rollouts from the last 30 days are supported * **No control group** - Results show correlation, not definitive causation * **External factors** - Other changes during the analysis period can influence results * **Metric type restrictions** - Some advanced metric types aren't yet supported. ================================================================================ # Measuring multiple rollout stages URL: https://docs.statsig.com/feature-flags/multiple-rollout-stages Updated: 2026-07-11 ================================================================================ ## Continuous analysis For gate rules that roll out with a pass percentage ≤ 50% and without any rollback, Statsig consolidates data collected at earlier and later stages of the rollout into one analysis. This produces a more complete analysis. Valid continuous analysis rollouts include: 10% → 20% 10% → 20% → 50% Invalid continuous analysis rollouts include: 10% → 5% (rollback) 10% → 70% (exceeds 50%) When a rollout is no longer valid for continuous analysis, Statsig analyzes the new rollout step separately from previous steps. ## Compatibility with other gate features ### Balanced gates Gate rules that have multiple rollout stages are also [balanced](/feature-flags/view-exposures#balanced-gates) using downsampling. In cases where pass percentage ≤ 50%, Statsig uses the same hashing as the pass/fail and takes an equal percentage of the other group. When pass percentage > 50%, Statsig uses an orthogonal random hashing to sample the larger group at a rate of $\frac\{1-p}\{p}$ where p is the fraction of users in the larger group. In the case of a gradual rollout, this prevents bias based on enrollment time period. ### CUPED/CURE CUPED/CURE is available only when there have been no rollbacks and when the rollout percentage hasn't exceeded 50% within a gate rule. Analysis is no longer continuous after a rollback or after 50% is exceeded, because earlier treatments bias the pre-exposure data of a given unit. ================================================================================ # Scheduled Rollouts URL: https://docs.statsig.com/feature-flags/scheduled-rollouts Updated: 2026-07-11 ================================================================================ Feature Gates provide a safe, controlled feature rollout. Scheduled Rollouts add a time-based scheduling layer to Feature Gates, letting you pre-set any rollout schedule you want and execute it automatically. This is useful when a feature launch happens in another timezone or when your team follows a standard ramp-up schedule with every feature release. Scheduled rollouts apply at the Feature Gate **rule** level for maximum flexibility. Not all rules in your Gate need to include a scheduled rollout. Set it up only for the rules that need it. ## Set up a Scheduled Rollout To set up a Scheduled Rollout on a rule in your Feature Gate, tap the "…" in the upper right-hand corner of the rule you want to schedule a rollout for. {% figure %} ![Feature Gate rule menu options](/images/feature-flags/scheduled-rollouts/199850775-42528d6c-b8f1-4e5d-9774-bc1b576c2916.png) {% /figure %} Select **Edit Rule or Rollout**, and then select **Schedule Automated Rollout**. {% figure %} ![Schedule Automated Rollout selection](/images/feature-flags/scheduled-rollouts/199851487-2e2aba51-30d5-4fef-933f-b31c0e78dd57.png) {% /figure %} From here, configure each phase of your Scheduled Rollout. The upper right-hand corner shows your current pass percentage, which reflects the baseline pass percentage you entered for your rule and which you can change using **Edit Rule**. To add phases to your rollout, select **Add Phase** and configure as many phases as you need. Each scheduled rollout phase includes: * Rollout date * Rollout time\* * Pass percentage {% callout type="note" %} Rollout times are available in 15 minute increments. Additionally, each configured phase represents a discrete increase to the next rollout percentage, not a gradual rollout amortized over the course of the entire phase. {% /callout %} {% figure %} ![Scheduled rollout configuration interface](/images/feature-flags/scheduled-rollouts/199851781-60606e6b-d653-408a-a3ba-399e32d582b0.png) {% /figure %} As you build your Scheduled Rollout, a preview of the phases appears below the configuration wizard. This preview is also available to viewers of your Feature Gate when they hover over a rule. {% figure %} ![Scheduled rollout phases preview](/images/feature-flags/scheduled-rollouts/199851974-c95ea9d2-6d04-4c3e-b9e5-f5d5ea3d85b3.png) {% /figure %} {% figure %} ![Rollout phases hover preview](/images/feature-flags/scheduled-rollouts/199851640-007d63d5-7b9e-4002-93af-132af24416a1.png) {% /figure %} ## Execute a Scheduled Rollout After configuration, each phase of a Scheduled Rollout executes automatically on the specified schedule. Statsig sends Scheduled Rollout notifications to the Feature Gate creator, any editors, and anyone following the Feature Gate. Statsig sends notifications through: * Email * Console * (Optional) Slack: to configure Slack notifications, go to *Account Settings > Notifications*. ================================================================================ # Safeguards URL: https://docs.statsig.com/feature-flags/safeguards-overview Updated: 2026-07-11 ================================================================================ Safeguards continuously monitor critical metrics and automatically intervene in your Feature Gate rollouts when risk thresholds are exceeded. This ensures faster recovery from issues, eliminates the need for constant manual checks, and protects your users from unintended impact. {% callout type="info" %} Safeguards is available on the Pro and Enterprise billing tiers. {% /callout %} ## When to use Safeguards Use Safeguards when you want to: * Limit the impact of a feature on critical business or performance metrics * Automate rollout progression to more users based on how your metrics are performing * Maintain system stability by automatically responding to API errors, latency spikes, or infrastructure issues ## How Safeguards work Safeguards listen to alert types that you have created in your project, such as Rollout Alerts and Topline Alerts. When any alert fires due to metric regressions, Safeguards automatically pause your rollout, roll it back, or finish a rollout based on your settings. **Pre-requisite:** You must create at least one Rollout Alert or Topline Alert before configuring a Safeguard on a Feature Gate. Go to the [Alerts](/product-analytics/alerts-overview) documentation for setup instructions. ## Two types of safeguards A Safeguard can use two types of alerts to take an action on your Feature Gate: | Alert type | Definition | Use case | |---|---|---| | Rollout Alert | Monitors the regression of metric delta between users who pass and fail your Feature Gate | Use when you want to ensure your Feature Gate isn't causing negative drift for users receiving the new flag variation. Works only on partially rolled out rules (pass rate between 0% and 100%). | | Topline Alert | Monitors absolute metric values regardless of Feature Gate assignment | Use when you want to take action on your Feature Gate when system metrics breach thresholds, regardless of confirming causation. Works on fully rolled out (0% or 100%) and partially rolled out rules. | ## Get started with Safeguards Follow these tutorials to start using Safeguards: * [Create a new Safeguard](/feature-flags/safeguards-create) * [Manage an existing Safeguard](/feature-flags/safeguards-manage) ================================================================================ # Create a Safeguard URL: https://docs.statsig.com/feature-flags/safeguards-create Updated: 2026-07-11 ================================================================================ To create a Safeguard on a rule, follow these steps: 1. Go to a Feature Gate's **Setup** tab 2. Pick a targeting rule for which you want to monitor regressions 3. Click the three-dot (...) menu and choose **'Safeguard Rule'** {% figure %} ![Create Safeguard on a rule](/images/safeguards/create-safeguard.png) {% /figure %} 4. Choose the action to take when alerts fire: - **Rollback to 0%** - Assigns default value to all users. - **Roll out to 100%** - Assigns Pass value to all users. - **Pause Rollout** - Stops scheduled rollout progression (only available with an active Scheduled Rollout policy). {% figure %} ![Select an action](/images/safeguards/choose-safeguard-action.png) {% /figure %} 5. (Optional) Set how long to monitor alerts for safeguarding the rule: | Alert type | Evaluation period | Evaluation start time | | ------------- | ----------------- | ----------------------------------------------- | | Topline alert | Choose your own | Starts when the safeguard is created | | Rollout alert | Fixed (90 days) | Starts whenever targeting rule's pass % changes | {% figure %} ![Set evaluation period](/images/safeguards/evaluation-period.png) {% /figure %} {% callout type="tip" %} **Recommended:** Monitor topline alerts for crashes, errors, latency, and similar issues _for a few days_ after a Feature Gate rollout to confirm stability. A 14-day evaluation period is a good starting point. {% /callout %} 6. Select one or more alerts to monitor: - Rollout alerts - For feature-specific regression detection - Topline alerts - For system-wide health monitoring {% figure %} ![Add alerts for your Safeguard](/images/safeguards/add-alerts.png) {% /figure %} 7. Click **Save** ================================================================================ # Manage a Safeguard URL: https://docs.statsig.com/feature-flags/safeguards-manage Updated: 2026-07-11 ================================================================================ ## View a Safeguard To view an existing Safeguard, tap the blue pill on your Feature Gate's targeting rule. You can see how your Safeguard is defined and make changes. You can add or remove alerts, change the action, or adjust the evaluation period. {% figure %} ![View an existing Safeguard](/images/safeguards/view-safeguard.png) {% /figure %} ## When a Safeguard triggers When a safeguard is triggered because of an alert: * The configured action executes automatically (rollback/pause/complete). * A banner appears on the targeting rule with the action taken, timestamp, and diagnostic link. * Further rule modifications are blocked until the alert is resolved. * Statsig sends notifications per your alert configuration. {% figure %} ![Safeguard is triggered on a rule](/images/safeguards/safeguard-rule-banner.png) {% /figure %} ================================================================================ # Managing Feature Gate lifecycles URL: https://docs.statsig.com/feature-flags/feature-flags-lifecycle Updated: 2026-07-11 ================================================================================ A feature can go through different phases throughout its lifecycle: it may be tested by a few users, recently rolled out to everyone, or stable enough that the toggle is no longer needed. A feature gate should clearly reflect the phase the feature is in, for a few important reasons: * **Prevent incidents**: Avoid scenarios where old code for a deprecated feature is accidentally touched or repurposed. This has had real business consequences, as in [how Knight Capital lost half a billion dollars](https://www.statsig.com/blog/lose-half-a-billion-dollars-with-bad-feature-flags-knight-capital). * **Maintain a healthy codebase**: Dead references to flags increase the volume of code your team navigates daily and can slow down new developer onboarding. * **Reduce mental load**: The feature gate catalog shows the status of every feature, so you can see at a glance what next steps are needed (for example, launch or end a feature) without tracking everything manually. ## Managing feature gate lifecycles Statsig makes it easy for your feature gates to reflect the phase your feature is in by using **status**. A gate can be in one of four statuses: {% figure %} ![Feature gate status options interface](/images/feature-flags/feature-flags-lifecycle/216169028-44ca6ce1-1300-44b6-9156-55ea52cd85ea.png) {% /figure %} | Status | What it represents | Implication | |-------------|-----------------------|---------| | In Progress | This feature is being rolled out and tested. | Default status when you create a gate. | | Launched | This feature has been rolled out to everyone. | The gate always returns **TRUE** and stops generating billable exposure events, so you stop incurring costs. The gate reference is likely safe to clean up from the codebase. | | Disabled | This feature has been rolled back from everyone. | The gate always returns **FALSE** and stops generating billable exposure events, so you stop incurring costs. The gate reference is likely safe to clean up from the codebase. | | Archived | This feature is no longer referenced in code or checked; history on the gate is preserved. | The gate has received 0 checks for the last 7 days and will receive no more checks. | ## When to update feature gate lifecycles There are 3 points throughout the gate's lifecycle when you should take action, either in the console or in your codebase: {% figure %} ![Feature gate lifecycle workflow diagram](/images/feature-flags/feature-flags-lifecycle/216169769-23062719-92da-4ae5-a437-935b2ef5b05c.png) {% /figure %} **1) The gate has been fully rolled out or rolled back, and you are ready to skip rule evaluation and assign a default value** * Go to the feature gate page and click the "..." menu in the upper right corner to select **Launch** or **Disable**: * **In Progress → Launch**: when the gate has been rolled out to 100% for a period of time (recommended: more than 30 days) and you are comfortable with the gate always returning TRUE. * **In Progress → Disable**: when the gate has been rolled back to 0% for a period of time (recommended: more than 30 days) and you are comfortable with the gate always returning FALSE. * To find all gates that are good candidates to be **Launched** or **Disabled** (rolled out to 100% or rolled back to 0% more than 30 days ago): * Go to Feature Gates catalog * Click on filter icon: * Status = In Progress * Pass Rate = 100% AND 0% * In the search bar: "Modified: `", {}); ``` Now, fetch the config and construct the banner: ```js const bannerConfig = client.getDynamicConfig("banner_config"); const text = bannerConfig.get("text", null); const backgroundColor = bannerConfig.get("backgroundColor", "black"); const color = bannerConfig.get("color", "white"); const fontSize = bannerConfig.get("fontSize", 14); if (text == null) { return; } const banner = document.getElementById("homepageBanner"); const bannerText = document.createElement("p"); banner.style.display = "block"; bannerText.innerHTML = text; banner.style.color = color; banner.style.fontSize = fontSize + "px"; banner.style.backgroundColor = backgroundColor; banner.appendChild(bannerText); ``` This JavaScript relies on the HTML page having a homepageBanner div: ```html
``` With a small amount of JavaScript, this example integrates with the Statsig SDK and uses the Dynamic Config. You can now add a new rule to the Dynamic Config and return a different banner to a different group of users without updating the website. Here is an example of the result when viewing the webpage in Chrome on a Mac: {% figure %} ![UI for mac users](/images/guides/first-dynamic-config/b82e808a-99b5-4922-90f7-727d9e5d942a.png) {% /figure %} This example shows one approach to using Dynamic Config with your app, website, or backend service. ================================================================================ # Segments URL: https://docs.statsig.com/segments/overview Updated: 2026-07-11 ================================================================================ ## What is a segment? A segment defines a reusable set of users. You can define a segment based on common user attributes such as location, client device, browser, client application version, or user IDs. ## When to use segments Segments are ideal for targeting a commonly identified set of users across features or dynamic configs. For example, you can create a segment representing your team members or company employees to test a new feature before launching to external users. You can also create different segments for English-speaking and Spanish-speaking countries to deliver a localized experience to each segment. To get started, go to the Statsig guide to [create your first segment](/guides/first-segment). The following tutorials show how to perform common tasks with segments. * [Create a segment](/segments/create-new) * [Create rules for a segment](/segments/add-rule) * [Use a segment](/segments/implement) ================================================================================ # Create a segment URL: https://docs.statsig.com/segments/create-new Updated: 2026-07-11 ================================================================================ To create a segment: * Log in to the Statsig console at https://console.statsig.com * On the left-hand navigation panel, select **Segments** * Click the **Create** button * Enter a name and description for your segment * Select the type of segment you want to create: **Conditional** or **ID List**, and click **Create** (Conditional is selected by default) {% figure %} ![Segment creation interface](/images/segments/create-new/e2dd3e0e-a003-4772-8426-cb02738f4017.png) {% /figure %} For **Conditional Segments**, you can **[Add Rules](/segments/add-rule)** to complete the segment's definition. For **ID List Segments**, you can **[Add an ID List](/segments/add-id-list)** to complete the segment's definition. ================================================================================ # Adding Rules URL: https://docs.statsig.com/segments/add-rule Updated: 2026-07-11 ================================================================================ ## Create a rule for a segment A rule defines the criteria for which users are included in a segment. To add a rule to a segment: * Log in to the Statsig console at https://console.statsig.com * On the left-hand navigation panel, select **Segments** * Select the segment where you want to add a rule * Click the **Add New Rule** button * Select the criteria to target a set of users. For example, select **Email** with the **Contains any of** operator, then enter your company's email domain to target only internal employees. {% figure %} ![Segment rule configuration interface](/images/segments/add-rule/129094975-a78b8caf-ac0d-4b14-96f7-adba2fe73f1f.png) {% /figure %} * Click the **Add Rule** button * Click the **Save Changes** button at the top right of the **Rules** section ================================================================================ # Adding ID Lists URL: https://docs.statsig.com/segments/add-id-list Updated: 2026-07-11 ================================================================================ ## Adding ID lists ### What is an ID list? An ID List enables you to define a reusable audience segment using user identifiers like `userID`, `stableID`, or `organizationID`. You can manage ID Lists by manually adding or removing IDs, uploading CSVs, or replacing the entire list. ### Creating an ID list segment 1. Navigate to the **Segments** section in the [Statsig Console](https://console.statsig.com). 2. Click **Create New Segment**. 3. Toggle the segment type from **Conditional** to **ID List**. {% figure %} ![Create New Segment](/images/segments/add-id-list/af7c5cb3-adc2-4e88-a411-b5844a541e49.png) {% /figure %} 4. Select the **ID type** you want to build from. {% figure %} ![Create New Segment](/images/segments/add-id-list/958b7545-7f23-4559-8a66-99af01d39952.png) {% /figure %} ### Managing IDs Once inside your ID List segment, you have a variety of options: * **Manual Entry**: Enter IDs directly into the input box. {% figure %} ![Create New Segment](/images/segments/add-id-list/ee94e6bc-475b-47a0-96a7-5ddba398d58c.png) {% /figure %} * **Upload CSV**: Import a list of IDs from a CSV. * **Bulk Actions**: Choose how your input affects the existing list: * **Add**: Add IDs to the list * **Remove**: Remove matching IDs from the list * **Replace**: Clear all IDs and replace with a new list You can also sync an ID list segment from sources such as [Amplitude Cohorts](https://help.amplitude.com/hc/en-us/articles/4789303290011), [Segment Audiences](/integrations/data-connectors/segment#syncing-statsig-segment-id-lists-with-segment-personas-audiences), or a custom source using the [Console API](/console-api/introduction). There's a hard limit of 10 million IDs across all ID Lists in your project. ### Keep ID lists small ID Lists with fewer than 1,000 IDs are synchronized in the same process as feature gates, experiments, and conditional segments, providing high consistency and reliability. ID Lists over 1,000 IDs use a separate synchronization process for scalability, which means they aren't included in the main initialization path (for server SDKs or the local evaluation version of client SDKs) and change propagation takes longer. Keep each ID List to no more than 1,000 IDs unless the longer propagation time is acceptable. ### Large ID lists To target hundreds of thousands or millions of users, the more performant approach is to set an attribute on the User object passed to the Statsig SDK. For example, pass `user_type:Paid` or `user_type:Trial` in the User object instead of maintaining large ID Lists. This requires your app to have this information available when calling the Statsig SDK. If your use case requires ID lists larger than 10,000 IDs, contact the Statsig team through Slack. {% callout type="info" %} Large ID Lists is currently in limited Early Access. Contact Statsig Support to enable. Note this is not available for Free/Pro users at this time. {% /callout %} ================================================================================ # Using a segment URL: https://docs.statsig.com/segments/implement Updated: 2026-07-11 ================================================================================ You can use a segment to target a set of users in a feature gate or a dynamic config: * Select the feature gate or dynamic config where you want to target users * Click the **Add New Rule** button * In the criteria, select the **User in Segment** option * Select the segment that you want to use and enter a name {% figure %} ![Segment targeting configuration interface](/images/segments/implement/129115596-fbaeb686-8adb-4ebe-9dcb-6dfecf0cf043.png) {% /figure %} * Click the **Add Rule** button * Click the **Save Changes** button at the top right of the **Rules** section ================================================================================ # Release Pipeline Overview URL: https://docs.statsig.com/release-pipeline/overview Updated: 2026-07-11 ================================================================================ Release Pipelines enable multi-stage rollout strategies that respect your infrastructure boundaries, providing greater control over feature deployments. ## When to use Release Pipelines Consider using Release Pipelines when you need to: * Implement infrastructure-aware deployment strategies beyond what traditional feature flags offer * Safely roll out changes in complex, distributed systems with minimal risk * Deploy progressively across environments (dev → staging → prod) * Target specific infrastructure segments with precision (e.g., prod-us-west → prod-us-east → prod-eu) * Control progression between stages with time intervals or manual approvals * Monitor each deployment stage before proceeding to the next * Enable instant rollbacks if issues arise during any phase **Especially valuable for:** * Platform engineering teams managing multi-environment, multi-region infrastructure * DevOps practitioners implementing progressive delivery strategies * SREs responsible for maintaining system reliability across complex deployments * Organizations with mission-critical services that can't afford downtime ## Key concepts | Concept | Description | |---------|-------------| | **Release Pipeline** | Defines the complete release strategy for a feature gate or dynamic config change | | **Phase** | A distinct stage in the pipeline with specific conditions targeting designated release segments | | **Trigger** | An event that starts when a feature gate or dynamic config begins using a Release Pipeline | | **Action** | Controls you can apply to an active Trigger to manage the release process | ## Get started with release pipelines {% callout type="note" %} Release Pipeline is currently only supported in Statsig's [Server Core SDKs](https://www.statsig.com/blog/introducing-statsig-server-core-v0-1-0). Legacy SDKs continue to work but won't get the full features of release pipelines. {% /callout %} Follow these tutorials to begin implementing Release Pipelines: * [Create and Manage Release Pipelines](/release-pipeline/create-and-manage) * [Trigger a Release Pipeline](/release-pipeline/trigger) * [Manage an Ongoing Release Pipeline](/release-pipeline/actions) ## Current limitations * Experiments aren’t supported in release pipeline * Resalt/Disable/Delete/Archive/Launch actions won’t trigger a release pipeline * [Scheduled rollouts](/feature-flags/scheduled-rollouts) aren’t supported to work in conjunction with release pipeline ================================================================================ # Create and Manage Release Pipelines URL: https://docs.statsig.com/release-pipeline/create-and-manage Updated: 2026-07-11 ================================================================================ ## Creating a new pipeline To create a new Release Pipeline: 1. Log into the [Statsig console](https://console.statsig.com) 2. Navigate to **Settings** > **Feature Management** 3. Under Release Pipelines, click the **Create** button 4. Enter a descriptive name for your pipeline 5. Click **Create** to proceed to the configuration page {% figure %} ![Create release pipeline interface](/images/release-pipeline/create.png) {% /figure %} ## Configuring phases Each pipeline consists of one or more phases, with each phase representing a distinct release target. ### Adding phases For each phase in your pipeline: 1. Add one or more release rules 2. Select a required **Environment** for the rule 3. Optionally add custom field conditions for more precise targeting {% figure %} ![Phase configuration](/images/release-pipeline/phases.png) {% /figure %} ### Setting phase transitions Control how your phases progress with these transition options: | Transition Type | Description | |-----------------|-------------| | **Require Review** | Requires manual approval from an authorized user before starting the phase | | **Time Interval** | Automatically proceeds to the next phase after a specified duration (in minutes) | :::note You can combine both options in a single phase. When both are used, the time interval only begins counting down after someone gives the required approval. ::: {% figure %} ![Phase transition conditions](/images/release-pipeline/condition.png) {% /figure %} ## Managing existing pipelines ### Updating a pipeline To modify an existing pipeline: 1. Click on the pipeline name from the list 2. Make your edits to any section 3. Click **Save** to apply your changes **Important:** You can't modify pipelines with active rollouts in progress until those rollouts complete or you stop them. ### Viewing pipeline references There are two ways to see which feature gates and dynamic configs are currently using a pipeline: 1. Project Settings * Navigate to Project Settings * Click on the Feature Management menu in the left-rail * Navigate to Release Pipelines section * Click on the **References** column against each Release Pipeline * This shows all feature gates and dynamic configs that are currently attached to a specific pipeline {% figure %} ![Reference](/images/release-pipeline/reference.png) {% /figure %} 2. Feature Gates / Dynamic Configs Page * Navigate to Feature Gates / Dynamic Config list view * Filter by 'Release Pipeline' current status * This shows all feature gates and dynamic configs with an ongoing release pipeline {% figure %} ![Feature gates and dynamic configs with release pipeline status](/images/release-pipeline/create-and-manage/23a3199f-4710-40ed-9bd3-14d2ae5edb49.png) {% /figure %} ## Opting out environments from Release Pipelines By default, all environments trigger Release Pipelines when changes are made. You can configure specific environments to be exempt from this behavior. When you opt an environment out from Release Pipelines: * Changes made exclusively to that environment don't trigger a Release Pipeline. * This allows quick environment-specific adjustments without starting the full release process. ### How to opt out an environment To exclude an environment from triggering Release Pipelines: 1. Navigate to **Settings** in the Statsig console 2. Under **Keys & Environments**, select **Environments** 3. Click on the environment you wish to opt out 4. Unselect the **Pipeline-required Environment** option 5. Click **Save** to apply your changes {% figure %} ![Environment opt-out setting](/images/release-pipeline/environment-opt-out.png) {% /figure %} ================================================================================ # Trigger a Release Pipeline URL: https://docs.statsig.com/release-pipeline/trigger Updated: 2026-07-11 ================================================================================ Statsig activates a Release Pipeline when you make changes to a feature gate or dynamic config that has a pipeline attached to it. ## Attaching a pipeline Before triggering a release, attach a pipeline to your feature gate or dynamic config. The Statsig console offers two methods for attaching a Release Pipeline. ### During feature creation You can select a Release Pipeline directly in the creation modal when setting up a new feature gate or dynamic config: {% figure %} ![Attaching a pipeline during creation](/images/release-pipeline/modal-attach.png) {% /figure %} ### To an existing feature For an existing feature gate or dynamic config, you can attach a Release Pipeline through the sidebar settings: {% figure %} ![Attaching a pipeline via sidebar](/images/release-pipeline/sidebar-attach.png) {% /figure %} :::note You must have at least one Release Pipeline created before it appears in the dropdown menu. Refer to [Create and Manage Pipelines](/release-pipeline/create-and-manage) for instructions on creating pipelines. ::: ## Starting a rollout When a Release Pipeline is attached, making changes to your feature gate or dynamic config automatically starts the pipeline process: 1. Make your changes to the feature gate or dynamic config 2. Click **Save** to commit the changes 3. A confirmation dialog appears, informing you that changes progress through the pipeline 4. Review the information and click **Confirm** to proceed The system then begins the rollout following the phases defined in the attached pipeline. {% figure %} ![Confirmation dialog when triggering a pipeline](/images/release-pipeline/trigger.png) {% /figure %} ## Viewing release status After you trigger a Release Pipeline, you can monitor its progress: 1. At the top of the feature gate or dynamic config page, a status banner appears 2. This banner displays the current phase and overall progress through the pipeline {% figure %} ![Status banner showing release progress](/images/release-pipeline/view-status.png) {% /figure %} For information about controlling an ongoing release, including approvals and cancellations, refer to [Managing Release Actions](/release-pipeline/actions). ## Frequently asked questions **Q: Can I attach different Release Pipelines to different feature gates?**\ A: Yes, each feature gate or dynamic config can use a different pipeline based on its specific rollout needs. However, a single feature gate or dynamic config can only have one Release Pipeline attached at a time. **Q: What happens if I need to cancel a release in progress?**\ A: You can stop an ongoing release using the actions menu in the release details view. Refer to [Managing Release Actions](/release-pipeline/actions) for complete instructions. ================================================================================ # Manage an Ongoing Release Pipeline URL: https://docs.statsig.com/release-pipeline/actions Updated: 2026-07-11 ================================================================================ ## Managing release actions After you trigger a Release Pipeline, you can control its progression using the following actions. These controls let you safely test, pause, fast-track, or halt propagation of feature gate and config changes across pipeline phases. {% figure %} ![Release pipeline management actions](/images/release-pipeline/actions/519ecada-cce9-4b70-9492-62d21190a85a.png) {% /figure %} ### Approve **What it does:** Start a phase that requires manual approval before rollout begins. Use this when your workflow requires human verification before changes move forward. **How to use it:** 1. The pipeline automatically pauses at the last completed phase awaiting approval 2. You receive a notification prompting you to take action 3. Go to the Release Pipeline status page 4. Click the ⋯ menu and select 'Approve' 5. Confirm the action. The next phase begins rolling out. ### Pause **What it does:** Stops the bake timer between two phases. Useful if you want to delay rolling out the next phase to investigate the current phase. :::note Pause doesn't stop the current phase. It stops only the timer to move to the next phase. ::: **How to use it:** 1. Go to the Release Pipeline status page 2. Click the ⋯ menu and select 'Pause' 3. Confirm to pause the bake timer ### Unpause **What it does:** Resumes a previously paused bake timer, allowing the pipeline to move to the next phase after the remaining wait time. **How to use it:** 1. Go to the Release Pipeline status page 2. Click the ⋯ menu and select 'Unpause' 3. Confirm to resume the bake timer ### Skip **What it does:** Immediately skips the current phase and moves rollout to the next defined phase. Useful for fast-tracking safe changes. **How to use it:** 1. Go to the Release Pipeline status page 2. Click the ⋯ menu and select 'Skip' 3. Confirm to resume the bake timer ### Abort **What it does:** Halts the release process. The feature gate or dynamic config reverts to its pre-release state, and no further changes propagate. **Important:** Aborting a release is irreversible. After aborting, you must trigger a new release to restart the process with any modifications. **How to use it:** 1. Go to the Release Pipeline status page 2. Click the ⋯ menu and select 'Abort' 3. Confirm to resume the bake timer ### Full roll out **What it does:** Skips all intermediate phases and releases the latest version across all environments and custom attributes once. **How to use it:** 1. Go to the Release Pipeline status page 2. Click the ⋯ menu and select 'Full Roll Out' 3. Confirm to resume the bake timer ================================================================================ # Product Analytics Overview URL: https://docs.statsig.com/product-analytics/overview Updated: 2026-07-11 ================================================================================ ## Metrics Explorer Metrics Explorer provides data-driven insights that can inspire new features. After you implement these features, you can release and measure their impact using Statsig feature gates, A/B tests, or experiments to understand how they affect core metrics. ### Charts in Metrics Explorer In Metrics Explorer, charts are the primary means of analysis, providing a detailed look at your data. You can switch between chart types from the top-left section of the interface while maintaining context on the selected events. This flexibility lets you focus on a particular metric or event while exploring it from different angles. Statsig offers the following chart types: * [Metric Drilldown](/product-analytics/drilldown) * [Funnels](/product-analytics/funnels) * [Retention](/product-analytics/retention) * [Distribution](/product-analytics/distribution) * [User Journeys](/product-analytics/user-journeys) * [Lifecycle](/product-analytics/lifecycle) Get started with Metrics Explorer charts by navigating to the **Analytics** section of the Statsig console navigation menu and selecting [Metrics Explorer](https://console.statsig.com/metrics/explore). ## Dashboards [Dashboards](/product-analytics/dashboards) let you share insights and information with your team. Any chart you build can be saved to a dashboard. You can also save a snapshot of Feature Gates, A/B tests, and experiments to dashboards. Experiment and feature gate snapshots sync automatically to a dashboard, keeping your team up to date with relevant launches as they emerge. These dashboards are auto-populated with any entity matching the tags you specify. ### Charts in Dashboards You can add any chart supported in Metrics Drilldown to dashboards. Dashboard charts aren't static. After expanding a chart using the \[ ] icon, you can modify it for further exploration without saving the change to the dashboard. To save changes, click the edit icon or button and save. To get started, navigate to **Dashboards** in the Statsig left navigation. ================================================================================ # Metric Drilldown Charts URL: https://docs.statsig.com/product-analytics/drilldown Updated: 2026-07-11 ================================================================================ The Metric Drilldown chart in Metrics Explorer is a tool for understanding customer behavior and trends within your product. Use it to analyze key metrics and user behavior over time. You can also filter to specific properties or cohorts and use Group-By to compare behaviors between groups. ## Use cases * **Trend Analysis Over Time**: Track how specific metrics evolve over time. Visualizing product data in Metrics Explorer lets you compare key performance indicators and user behavior, and understand long-term trends and short-term fluctuations. * **Identify Cohorts**: Define and explore cohorts by filtering to users who performed certain events at frequencies you define. * **Understand Feature and Experiment Impact**: Split any metric by Experiment Group or Feature Gate Group to compare metric performance across groups. Use automatically generated chart annotations for feature launches and experiment starts to correlate decisions with changing trends. * **Segmentation and Comparison**: Analyze how different user segments or product features perform to identify areas providing value or areas that need improvement. * **Filtering**: Focus on specific segments or cohorts for more targeted analysis. * **Statistical Understanding**: Understand how the average, median, or other percentile value (for example, p99, p95) of a metric changes over time. * **Dynamic Metric Creation with Formulas**: Create new metrics dynamically using custom formulas to derive ad-hoc insights. * **Flexible Visualization Options**: Choose from line charts, bar charts, horizontal bar charts, stacked bar charts, and other formats to best represent your data. * **Event Samples for Debugging**: Access a metric's underlying sample events and the user-level information attached to each event. Useful for troubleshooting trends or anomalies. * **Detailed Data Control**: Adjust data granularity from high-level overviews to detailed breakdowns. Use rolling averages to smooth data for trend analysis. * **Debug Experiments**: Break down your experiment's first exposures to understand how properties or groups (feature gates, experiments, holdouts) affect the experiment. * **View Sample Ratio Mismatch (SRM)**: See the SRM of your experiments over time and drill down into event and user metadata to understand how properties (country, browser) or groups affect experiment SRM. * **Debug Feature Gates**: Break down feature gate first exposures per rule to understand how properties or groups affect the gate. * **Analyze Dimensions Across Metrics**: Identify top or bottom performers across multiple metrics by viewing data in a table and sorting by any metric-column combination. ## Using the metric drilldown chart ## Selecting metrics and events In Metrics Explorer you can choose events, custom-metrics, auto-generated metrics, or experiment exposures to explore. You can add several metrics and events or exposures to plot on a single chart. ### Events When selecting an event, the total number of times the event occurred (Count) for a given data point (hour, day, etc.) is plotted by default. You can also choose different aggregation methods: * **Count**: Plot the number of times the event occurred within the given time range per data point. * **Unique**: Plot the number of unique ids (generally UserIDs) that performed the event in the given time range per data point. {% figure %} ![Event aggregation options interface](/images/product-analytics/drilldown/7e4740c5-88e6-4d1c-a49c-dd29dca30714.png) {% /figure %} * When viewing data on uniques (e.g. unique users) at daily granularity, you can choose to have the value of each daily data point represent the number of unique weekly users (unique users over previous 7 days). This enables you to get a sense of how weekly usage is changing day over day. {% figure %} ![Weekly unique users configuration option](/images/product-analytics/drilldown/2cec3257-b8a5-4efc-9bcd-be4d7f871c7a.png) {% /figure %} * **Average**: Plot the average of a selected event property value per data point. This works only for properties with numerical values. {% figure %} ![Average event property aggregation interface](/images/product-analytics/drilldown/f7ea5919-5288-42f0-8949-d711e17ffec7.png) {% /figure %} * **Sum**: Plot the sum of a selected event property value per data point. This works only for properties with numerical values. {% figure %} ![Sum event property aggregation interface](/images/product-analytics/drilldown/20ed372c-e1cb-4c2e-bdac-ed6dd2ba7c4e.png) {% /figure %} * **Percentiles**: Plot the value of a selected event property at the selected percentile per data point. {% figure %} ![Percentiles aggregation configuration interface](/images/product-analytics/drilldown/112d74ec-7a52-4621-8969-af79e8599714.png) {% /figure %} * **Unique Values**: Plot the count of distinct values for any property across events or users within the given time range per data point. This aggregation helps answer questions like "How many different referrers drove traffic last week?" or "How many SKUs were added to carts today?" by counting unique property values rather than event occurrences. * **Count per User**: Plot the frequency distribution of how often users perform a specific event, showing statistics like average, median, or percentile values per user within the given time range per data point. This aggregation helps analyze user engagement patterns by measuring how many times each user performed an event, then applying summary statistics across those users. ### Exposures Selecting an experiment or gate exposure plots its first exposures over your selected date-range. First exposures are the first time a unique id (set on the experiment or gate) was exposed to each of your experiment groups or each of your gate's rules. ### Understanding first exposures in feature gates When a gate is checked for a user, an exposure is created for the rule whose conditions the user met. If a user is exposed to multiple rules at different times, the first exposure from each rule is kept. Group by rule to see each rule's exposures separately. ### Metrics Selecting a custom Metric or auto-generated Metric plots the value of that metric over your selected date-range. **Viewing and modifying the metric definition** The metric definition appears directly below the metric name. You can also make ad-hoc edits to the event-based definition to plot new metrics dynamically, based on existing metric definitions. ### Comparing multiple metrics, events, or exposures You can compare multiple metrics or events by plotting them on the same chart. To add a metric, click the **+** icon and select **Metric**, then select the metric. When multiple metrics are plotted, click any metric's row in the table below the chart to scope to that metric. Click the row again to show all metrics. Use the checkboxes next to metric names in the table to scope to a custom set of metrics. ### Filtering Filters let you scope to events and metrics logged by users with specific properties. To add a filter to an event or metric, click the filter icon and select the event or user property to filter by. ### Adding formulas Formulas let you dynamically combine and transform plotted events and metrics using mathematical expressions to answer new questions. To add a formula, hover over the **+** icon in the Metrics section and select **Formula**. A free-form text field appears. Use the label of each plotted metric or event as a variable (each plotted metric is labeled with a letter). For example, if you have: * Metric A: number of unique purchasers * Metric B: total purchases You can plot purchases per purchaser as `B/A`. ## Drilling down In addition to plotting metrics, you can drill into your metrics to identify unique groups that reveal how people use your product. ### Group-By A Group-By disaggregates plotted metrics and events by a selected property or group, letting you compare how an action or user behavior correlates with a specific property. Adding a Group-By splits the plotted metrics into separate plots. By default, the top ten groups by value are shown on the chart. You can select up to 50 groups when the chart is set to daily granularity. A metric can be grouped-by event properties, user profile properties, experiment group, or feature gate group. To adjust Group-By limits, add a group-by, go to the summary table below the charts, and click the **Top X series** dropdown. Select how many groups to display (up to 50). This feature is available for line charts, stacked-line charts, bar charts, and stacked-bar charts. {% figure %} ![Group-by top series selection dropdown](/images/product-analytics/drilldown/e61fedbe-2633-4469-8120-9959968c484a.png) {% /figure %} When you have a Group-By applied, you can view the results as raw numbers, or as a percentage. **Feature gate and experiment groups** You can also select a Feature Gate or Experiment to split a metric by the different groups in the selected test. **Adding a group-by** To add a group-by, hover over the **+** icon in the *Drilldown* section and select **Group By**. Then select the property or experiment group to split the metric by. **Quickly hiding or isolating lines** When using Group By, you can isolate lines to show data for specific groups. Click the row representing a group in the table view below the chart. Clicking an already-isolated group shows all groups again. Use the checkboxes in the table view to scope to a custom set of groups. ### Define and compare event-based cohorts Building a useful group of users to analyze often requires more precision than comparing by property values. For example, you may want to understand the behavior of power users and compare them against your general user base. To support this, you can define event-based cohorts. Select an event of interest, then define a frequency criterion for how often users in the cohort performed the event: * Performed the event ***at least*** some number of times * Performed the event ***at most*** some number of times * Performed the event ***exactly*** some number of times You can also define the time window in which a user performed the event, and filter to a specific property for more granular cohort definitions. You can save cohorts to reuse them later. ## Exploring a metric drilldown chart ### Selecting chart granularity The Metric Drilldown chart supports daily, hourly, 30-minute, 5-minute, and 1-minute granularity. The granularity setting controls the interval between x-axis values. Granularities below hourly limit the analysis time window to 1 day. The default granularity is daily. To change it, select the **Daily** dropdown in the top right of the chart. When viewing data on uniques (for example, unique users) at daily granularity, you can set each daily data point to represent the number of unique weekly users (unique users over the previous 7 days). This shows how weekly usage is changing day over day. ### Setting the date range The default date range is 14 days. To change it, click the **Last 14 days** dropdown and select a quick date range or a custom range. ### Changing your unit type The default unit type is numbers. To view data as a percentage, time value, or measurement of space (bytes, bits, etc.), click the settings cog in the top right corner of the chart and select a format unit type. ### Smoothing out the data with rollups Metrics like daily usage often have seasonality effects that make longer-term trends harder to see. Rollups such as a 7-day rolling average help smooth this data. Statsig supports the following rollups to smooth out data, each of which can be rolled up over 3, 7, 14, 28, 48, or 60 data points: * **Rolling average**: Replaces each data point with the average of the preceding selected number of data points. * **Rolling sum**: Replaces each data point with the sum of the preceding selected number of data points. * **Cumulative Sum**: Replaces each data point with the sum of all preceding data points, including itself. The result is a continuously increasing total where each value represents the accumulated sum. ### Selecting the chart visualization Metrics Drilldown offers many ways to visualize your data, including: * **Line:** Useful when plotting one or more metrics over time. * **Stacked Line:** Useful when comparing groups to understand the relative proportion a certain group has of a metric or event. * **Bar:** Useful when comparing the total value of two metrics over the entire date range. * **Horizontal Bar:** Ideal for grouped data comparisons, especially when you have longer label names. Makes it easy to identify top and bottom performers across any business dimension like user types, locations, or platforms. * **Donut:** Useful for visualizing the proportional breakdown of a whole into distinct categories at a single point in time. Perfect for showing how different segments (like countries, user types, or feature groups) contribute to your total metric value. Apply a Group-By to any metric to see the breakdown as a donut chart. * **World Map:** Visualize your metrics geographically by country when using a country-based Group-By. This view makes it easy to spot geographic trends and understand how your product performs across different regions. * **Single Value:** Display key metrics at a glance for quick summaries. Perfect when you need to highlight a single important number or KPI without the complexity of a full chart. * **Data Table:** Compare multiple metrics across groups in a structured table format. Ideal for detailed analysis when you need to examine exact values and perform side-by-side comparisons of different segments. Both donut charts and world maps work with any metric when you apply a Group-By. Select your metric, add a Group-By for the property you want to analyze (such as country for geographic analysis), and choose your preferred visualization from the chart type selector. ### Zooming in To zoom in on a portion of the date range, click at one end of the range, hold the mouse button down, and drag to the other end. Release the mouse button to zoom in. To reset the zoom, click **Reset Zoom** in the top right of the chart. ### Sharing your insights When you want to share an insight, you have several options: * **Share by URL:** Copy the URL. This shares the query as currently defined. * **Create a share link:** Click the **…** button in the top right of the chart and select **Share Link**. This copies a shortened URL to the clipboard. * **Share to Dashboard:** Click the **…** button and select **Export to Dashboard** to save the chart to an existing dashboard or create a new one. Sharing a chart through a URL or shortened link captures a snapshot of the chart as defined when the link was copied. Subsequent changes aren't captured in the share link. ================================================================================ # Funnel Charts URL: https://docs.statsig.com/product-analytics/funnels Updated: 2026-07-11 ================================================================================ ## How funnel charts work Funnel Charts in Metrics Explorer show what portion of users complete each step of a journey you define through your product or service. These charts are useful for understanding user behavior, identifying bottlenecks, and developing insight-driven product changes that help users convert more successfully. ### Example use cases * **Conversion Analysis**: Monitor the progression of users through stages like sign-up, adding to cart, and purchase completion. * **Identifying Drop-off Points**: Pinpoint where users drop off, so you can target improvements at those steps. * **Comparing User Segments**: Observe how different user segments move through the funnel, highlighting variations in behavior based on demographics, user types, or other criteria. * **Product Optimization**: Determine which features or steps effectively move users to the next stage, and which require improvements. * **Experiment Analysis**: Understand the conversion rates before and after first exposure to an experiment. ## Defining a user funnel ### Step 1: Add steps to your funnel To define a funnel, select a series of events that represent different parts of a product flow you want to understand. To do so: 1. Go to **Metrics Explorer** under **Analytics** in the Navigation Bar, and switch over to the Funnel Charts view. 2. Add steps to your funnel using the **+** icon. Optionally add filters to funnel steps to target specific event or user properties. For WHN, add steps directly from your **Metric Source**. For Statsig Cloud, use events or metrics for the steps. **Combining multiple events into one step** You can combine multiple events into a single step. This is useful when more than one qualifying event represents a single meaningful portion of your funnel. Combined events use OR logic. To do this: 1. On the step, click the **…** button and select **Combine Events**. 2. Select an additional event to add. 3. Add any filters to each event in the funnel step. Repeat as needed. You can combine up to 5 events per step. {% figure %} ![Funnel step configuration interface](/images/product-analytics/funnels/2e0d34ba-cff9-4a19-9fe5-ea6ef19b1cfb.png) {% /figure %} **Filtering to the first time a user performed an event** Filtering to the first-time experience is useful because first-time behavior can affect long-term retention and may differ significantly from general product flows. You can filter funnel events to the first time a user (or other unit ID) ever performed an event: 1. Click **…** next to the event. 2. Select **Filter to First Time**. {% figure %} ![Filter to first time event option](/images/product-analytics/funnels/aa72701f-b9c2-47ae-81e8-514a1c1d971b.png) {% /figure %} **Renaming steps** When sharing a Funnel Chart (for example, by saving it to a dashboard), logged event names may not be easily readable. You can rename funnel steps for legibility: 1. Click **…** next to the event name. 2. Click **Rename Funnel Step**. 3. Enter a readable name for the step. Renaming a step doesn't change the underlying event name. The rename applies only to the funnel being configured. ### Step 2: Select a graph type Three graph types are supported for understanding conversion funnels: * **Conversion Rate**: A standard funnel view showing a step-by-step breakdown of where users drop off and how many convert through each step. * **Conversion Rate Over Time**: Shows how the overall conversion rate has changed over time. Useful for understanding how features and product changes affected the funnel conversion rate. * **Time to Convert**: Shows the distribution of time it takes users to complete the funnel. ### Step 3: Choose an ID type In general, construct funnels using a userID to understand the rate at which individual users complete each step. Some funnels may involve multiple people in an organization. For example, you may want to understand end-to-end company onboarding where different people handle purchasing, deploying, and using the service. Statsig supports individual or group analytics, letting you analyze funnel success at the user level or for whole groups such as organizations or companies. You can choose any ID type defined in your Statsig project when creating a funnel. If an experiment exposure is selected, make sure the ID type matches that of the experiment. {% callout type="info" %} Group analytics isn't a paid add-on at Statsig and is included at no extra cost for all tiers. {% /callout %} ### Step 4: Define the conversion window After you define your funnel, you can limit the analysis to users (or other unit IDs) who converted within a specified time frame. Users who start the funnel but don't convert within this time frame are counted as dropped off. ### Step 5: Drilldown To understand how conversion varies between user groups, use the **Group By** feature to split funnels by properties, experiment groups, or feature flag groups. Click **+** to the right of **Group By** and select the property, experiment, or feature flag to split the funnel conversion analysis by. ## Advanced funnel analysis ### Ordered or unordered funnels Ordered funnels are the default and the most common scenario. They require a user to complete the selected events in the specified order to be counted as converted. The user may perform other events between the specified events and still be counted as converted. For example, for an ordered funnel with events A, B, C, and D, the sequence A→B→B→A→C→D counts as converted. To check only whether a user completed all events regardless of order, toggle **Ordered** off in the advanced settings. Unordered funnels require only that the user completes the specified events within the given time range. ### Unique users (or unit ID) vs total conversions You can define funnel analysis by the total number of conversions or the number of unique users who convert. The default is Unique Users. ### Daily aggregation When enabled, this calculates funnel conversions per calendar day. A unit with funnel conversions on multiple days is counted as multiple conversions. This setting is enabled by default. ## Interpreting your user funnel ### Conversion rate vs. number of conversions At the top right of the funnel chart, the **Conversion Rate vs. Conversions** selector lets you switch the y-axis between conversion rate and number of conversions. When used with a Group By, toggling between the two views shows both relative and absolute conversion scales across different user groups. ### Conversion summary and table Under each funnel step, a summary shows: * The percentage and number of units that converted, relative to the first step * The percentage and number of units that dropped off, relative to the first step * The average time for each user to convert The conversion table shows the percentage and number of units that converted relative to both the first step and the previous step. This is useful when grouping by a property to compare conversions between user groups. {% figure %} ![Funnel conversion summary table](/images/product-analytics/funnels/073f9f20-f26f-4af4-a4e9-65d65474b97f.png) {% /figure %} ### User exploration By clicking any bar in the funnel, you can download all users in that segment who dropped off or converted as a CSV file. You can also view session streams to get an event-by-event understanding of a user's experience before and after that point in the funnel. {% figure %} ![User exploration options interface](/images/product-analytics/funnels/2c140de9-bce8-4813-91b1-8f2d8d1e9a66.png) {% /figure %} ## Conversion Drivers Conversion Drivers identify statistically significant factors that correlate with funnel conversion or drop-off at each step. The analysis surfaces event properties, user properties, and intermediary events that influence user progression through the funnel. {% figure %} ![Conversion Drivers Interface](/images/conversion-drivers-funnels.png) {% /figure %} To access Conversion Drivers: 1. Click on any funnel step 2. Select **"View Drop-Off & Conversion Drivers"** 3. Configure the analysis scope using the dropdown filters {% figure %} ![Conversion Drivers Entry Point](/images/conversion-drivers-context-menu.png) {% /figure %} ### Analysis configuration You can configure which data types to include in the analysis: * **Event Properties**: Attributes attached to events (e.g., platform, plan\_type, referral\_code) * **User Properties**: User-level attributes (e.g., country, account\_age, signup\_method) * **Intermediary Events**: Events that occurred between the selected funnel steps ### Driver metrics Each identified driver displays: * **Conversion Likelihood**: Expressed as a multiplier of the funnel's conversion rate (e.g., users with platform::Android are 1.2x as likely to convert) * **Conversion Rate**: Percentage of users with this factor who converted * **Participant Share**: Percentage of total funnel participants who had this factor ### Detailed analysis Click on any driver to access the drilldown view, which provides: * **Conversion Matrix**: Side-by-side comparison of conversion outcomes for users with and without the factor * **Correlation Coefficient**: Statistical measure of the factor's association with funnel completion (also known as the [phi coefficient](https://en.wikipedia.org/wiki/Phi_coefficient)) You can group the funnel by any identified driver by clicking the group by option. This reconfigures the funnel chart to show conversion performance segmented by the selected property. ### Use cases Conversion Drivers are useful for: * Exploratory analysis when investigating funnel performance without predefined hypotheses * Root cause analysis of conversions and drop-offs * Validating assumptions about user segment behavior * Monitoring funnel performance changes over time {% callout type="note" %} Conversion Drivers require a Pro plan subscription or Enterprise plan with the Advanced Analytics package. {% /callout %} ================================================================================ # Retention Chart URL: https://docs.statsig.com/product-analytics/retention Updated: 2026-07-11 ================================================================================ ## How Retention charts work Retention charts in Metrics Explorer help you understand how effectively your product or service maintains user interest and engagement over time. Retention charts are useful for measuring product-market fit and are critical for overall product growth. By analyzing retention patterns, you can identify whether your product or certain features resonate with your audience, and which areas need improvement. {% figure %} ![Retention chart interface showing user engagement over time](/images/product-analytics/retention/13a46b84-f1cc-4226-9876-5b314641927b.png) {% /figure %} ### Use cases * **Long-term user engagement tracking:** Monitor retention over days, weeks, or months to understand how long users stay engaged after their initial interaction or a specific trigger event. * **Stickiness of specific features:** Identify which product features users return for. * **Sub-population analysis:** Filter your retention analysis to specific groups to compare engagement across different user segments. ## Creating a retention chart You define a retention chart with a start event, a return event, and a return window. This lets you answer the question: "For users who performed the starting event, what percentage performed the return event within the given conversion window, over successive days or weeks?" ### Step 1: Define a start event The start event (or trigger event) indicates that a user has started using the product or service. Depending on the question you want to answer, this is often a top-of-funnel event such as sign-up, any active event, or an event that represents use of the feature whose retention you want to understand. To analyze the retention of a specific group of users, optionally add a filter to scope your analysis. ### Step 2: Define a return event The return event indicates that the user is getting value from your service. Depending on the question you want to answer, this can be returning to your product (another active event) or use of a specific feature. You can also add a filter to the selected event based on user or event properties. ### Step 3: Choose an ID type Statsig supports individual and group analytics. This lets you analyze the retention of individual users or of whole groups such as organizations or companies. ### Step 4: Choose a return time window Choose whether to measure daily or weekly retention. When users are not expected to engage daily to be considered "active", a weekly return window is more useful. A weekly window answers: "For users who perform the start event, how many perform the return event within 1 week?" Daily retention defines each cohort as users who performed the start event on a given calendar day. The chart then shows, over your selected time range (for example, 30 days), what percent of users in the cohort returned on each day. Weekly retention defines each cohort as users who performed the start event within a calendar week. Each cohort spans successive weeks. The chart then shows, over your selected time range, what percent of users in the cohort returned during each week. ### Step 5 (Optional): Choose a chart granularity for weekly retention charts When you select weekly retention, you can optionally change the chart granularity by selecting "Daily" from the granularity dropdown in the top left of the chart. Granularity is distinct from the Return Time Window. When you create a weekly retention chart with daily granularity, the chart shows, on a given day, what percent of the cohort performed the return event within the last week. For example, day 12 indicates what percent of the cohort performed the return event during the 7 days spanning day 6 through day 12, inclusive. ## Understanding a retention chart [Retention charts](https://www.youtube.com/watch?v=mqlHpYimik8) in Statsig include two main sections: the **retention graph (j-curve)** and the **retention table (triangle chart)**. ### Retention table (triangle chart) A retention table, or triangle chart, shows how well you retain users after their first interaction with your product or service. It tracks cohorts of users based on their first engagement date (defined by your start event) and shows the percentage of those users returning (defined by your return event) over subsequent days or weeks. The leftmost column identifies each cohort. Across the top are time intervals (days or weeks) after the initial engagement. Each cell shows the proportion of users from a cohort who were active at a specific time interval. A vertical analysis (looking down a column) lets you compare retention rates of different cohorts at the same lifecycle stage. A horizontal analysis (reading across a row) shows how a single cohort's retention evolves over time. High numbers in the first few columns suggest strong initial engagement; consistent percentages over longer intervals indicate successful long-term retention. Columns start at day/week 0, the earliest day/week in the time range. The rightmost column shows retention for the most recent day/week. Column behavior depends on the selected granularity: - **Daily retention:** Each column shows the percentage of the cohort that performed the return event on that calendar day. - **Weekly retention with weekly granularity:** Each column shows the percentage that performed the return event within that week. - **Weekly retention with daily granularity:** Each column shows the percentage that performed the return event on that calendar day or within the 6 days prior. ### Retention graph A retention graph plots the percentage of retained users against time. The X-axis represents time, segmented into daily or weekly intervals since the users' first interaction with the product. The Y-axis shows the percentage of the original users who remain active (performed the return event). **Reading the graph** The retention graph illustrates the rate at which users disengage over time. * A **declining curve** followed by a flattening is common. This indicates a drop in user interest after the first interaction, followed by stabilization among a core user group. The higher the percentage at which the curve flattens, the better the retention health of the product. * A **smiling curve** shows an initial decline in retention followed by an upward trend at later stages. This pattern often results from successful re-engagement strategies or product improvements that bring back previously inactive users. * A **continuously declining curve** indicates a consistent loss of users over time, often pointing to issues in user satisfaction or engagement. Key aspects to observe: 1. **Initial Drop-off Rate:** The steepness of the initial decline indicates how many users stop using the service after their first experience. 2. **Long-term Engagement:** The slope in the later stages of the graph shows the long-term user engagement. A flatter slope at this stage means better user retention. 3. **Trends Over Time:** Comparing multiple graphs over different time periods can reveal the impact of product changes or external factors on retention. ## Scoping to specific cohorts By default, Statsig plots only the "All cohorts" retention curve. To scope to a single cohort, click anywhere on that cohort's row in the table. To compare multiple cohorts, use the checkboxes on the leftmost portion of the table to select the cohorts whose retention curves you want to compare. ================================================================================ # Distribution URL: https://docs.statsig.com/product-analytics/distribution Updated: 2026-07-11 ================================================================================ ## How distribution charts work Distribution charts in Metric Explorer help you visualize the range of user experiences across your product. These charts are useful for understanding central tendencies, evaluating product health, and identifying outlying behavior. {% figure %} ![Distribution chart interface in Metrics Explorer](/images/product-analytics/distribution/b3b1507a-4a30-4a74-bc5c-8b3d08a1e64f.png) {% /figure %} ### Use cases * **Analyze the spectrum of experiences:** Study the distribution of event values to identify any trends. * **Measure feature engagement:** Visualize how often each user is interacting with a critical event. * **Monitor product performance:** Ensure that your product isn't experiencing performance issues like unusual latency times. ## Creating a distribution chart ### Step 1: Choose a metric or event The first step is to decide whether to use a metric or an event. The distribution of a metric shows the aggregated property value per unit ID. For example, if the metric aggregation is by count, the chart displays the number of times each user triggered the event. The distribution of an event shows the range of data stored under the **Value** property for each logged event. Statsig displays the event distribution only when the data under **Value** is numeric. ### Step 2: Refine your bucket size By default, the bucket size is 1, with a minimum value of 0 and a maximum value of 10. Buckets are always lower-inclusive and upper-exclusive. If the range exceeds the maximum bucket value, data is shown as "10+". You can adjust bucket sizes, minimums, and maximums to find the best view of your data. ## Interpreting your distribution chart ### Distribution of a metric For the distribution of a metric, the chart always represents metric values per unit ID. The X-axis represents the metric value, which can be a count of events or the sum or average of a property value, depending on the metric configuration. The Y-axis represents the unit ID, which can be users, stable IDs, or a custom ID. ### Distribution of an event The distribution of an event always displays the range of data under the **Value** property. The X-axis shows the **Value** and the Y-axis shows the number of events. ================================================================================ # User Journeys URL: https://docs.statsig.com/product-analytics/user-journeys Updated: 2026-07-11 ================================================================================ ## How User Journeys works The User Journeys chart shows the many paths users are taking through your product so you can better understand the end-user experience. {% figure %} ![User journeys sankey diagram visualization](/images/product-analytics/user-journeys/51e0c508-45cf-4839-93a0-3bf63e4e961d.png) {% /figure %} ### Use cases * **Event pathways:** Follow the most common paths users take through your product. * **Identifying drop-off points:** Pinpoint where users drop off within key product flows. * **Discovering unexpected behavior:** Uncover surprising usages and iterate on your product. ## Creating a user journeys chart ### Step 1: Choose a starting or ending event Choose whether to explore a journey that starts or ends with a specific event. Starting events are often top-of-funnel events such as sign-up or log-in, but can also be an event trigger for any new feature you introduce into your product. Ending events let you understand where users came from before reaching a specific point. You can optionally add filters to scope your analysis to a specific group. ### Step 2: Choose an ID type Statsig supports individual and group analytics. This lets you analyze the paths of registered users with User IDs, Stable IDs, or your own custom IDs. ### Step 3: Define your measurement criteria Under the "Measured as" section, choose between total conversions or unique conversions. Under total conversions, users can re-enter the flow each time they trigger the start event. Under unique conversions, each user appears in the path only once. You can also specify the observation duration. This defaults to 1 hour, but you can change the length of time users have to convert to the next event. Adjusting this value helps validate whether users are passing through flows at the pace you expect. To add more context, break the User Journey out by an event property. Select a specific event property to view the user journey expanded by its different property values (similar to a group-by). For example, you can view user journeys split by operating system. You can also filter out user paths that include a specified event. After editing the measurement criteria, click **Run Query** to apply your selections. ### Step 4: Adjust your chart visualization At the top bar of the chart, you can edit the conversion percent threshold, hide events, and expand your time window. The conversion percent threshold sets the minimum percentage of conversions needed to be considered relevant. Events below the threshold are consolidated under "Others". The **Hide Events** selector lets you filter out events you don't want to see. The time selector lets you view user journeys during the time window you care about most. ## Using User Journeys After selecting your start event, a sankey diagram shows all the next steps users took. Click any node to display the next events users triggered. Each event shows the total number of events counted and the percentage of users who triggered it. Continue clicking nodes to explore user flows through your product. ================================================================================ # Lifecycle URL: https://docs.statsig.com/product-analytics/lifecycle Updated: 2026-07-11 ================================================================================ ## How Lifecycle charts work Lifecycle charts in Metrics Explorer help you understand how usage changes over time by classifying your unique units (for example user\_id) within each time interval based on whether they used an event recently, returned after a gap, continued to use the event, or churned. {% figure %} ![Life Cycle chart interface in Metrics Explorer](/images/product-analytics/lifecycle/lifecycle_demo_v4.png) {% /figure %} ### Use cases * **Track new-user ramp after a launch:** See whether adoption is growing week over week after shipping a new feature. * **Monitor churn and reactivation:** Understand whether users are falling off and whether they return. * **Compare retention health across releases:** Spot changes in retention and reactivation patterns over time. ## Creating a lifecycle chart ### Step 1: Choose an event (or a compatible metric) Decide whether to use a metric or an event. Lifecycle is designed for a single underlying event and count-style metrics composed of a single count-type event. ### Step 2: Choose your unit (unique units) Select what you want to count uniquely (for example user\_id, stable\_id, or another unit). The chart reports how many unique units fall into each category for each time interval. ### Step 3: Choose your interval (granularity) Pick an interval unit (day / week / month) and a number of intervals per bucket (1–48). Each bar on the chart represents one interval bucket, and the chart shows one data point per bucket across the selected date range (max 1 year). {% figure %} ![Life Cycle chart interface in Metrics Explorer](/images/product-analytics/lifecycle/lifecycle_interval_selector.png) {% /figure %} ### Step 4: (Optional) Add filters Apply filters to focus on a specific segment (for example platform, country, app version, or a feature-related property). ## Interpreting your lifecycle chart Each interval bucket classifies unique units into four categories (mutually exclusive): **New:** Used during this interval and didn't use at any point earlier within the lookback window (up to 1 year before this interval). **Resurrected:** Used during this interval, didn't use in the immediately previous interval, and used earlier within the lookback window. **Recurring:** Used during this interval and the immediately previous interval. **Dormant:** Used in the immediately previous interval, but not during this interval (Statsig displays this below the axis to make churn visually apparent). ### Reading the chart **X-axis:** Time, grouped into your chosen interval buckets. **Y-axis:** Count of unique units. **Stacked bars:** Show how the composition of usage changes over time (new vs. resurrected vs. recurring), while the dormant component highlights drop-off between adjacent intervals. ================================================================================ # Dashboards URL: https://docs.statsig.com/product-analytics/dashboards Updated: 2026-07-11 ================================================================================ ## Creating a dashboard There are two ways to create a dashboard: 1. Navigate to the *Dashboards* tab and click **Create**. You can choose one of Statsig's [Dashboard Templates](#dashboard-templates) or create a custom dashboard. {% figure %} ![Dashboard creation interface](/images/product-analytics/dashboards/15696f0e-17b8-4413-a5fe-ab68f6c5c7c7.png) {% /figure %} 2. You can also create a dashboard directly from Metrics Explorer. To do this, after you finish building a chart: 1. Click **Export to Dashboard** at the top right corner of the chart. 2. Name the chart. 3. Select **Create New Dashboard** from the Dashboard Destination selector. 4. Give your new dashboard a name. {% figure %} ![Export to dashboard from Metrics Explorer](/images/product-analytics/dashboards/012d7dc0-a09b-4424-af10-ab8a714040cb.png) {% /figure %} ### Adding charts, feature gates, and experiments to a dashboard Dashboards help teams share and review product insights. You can add Metric Charts and track ongoing Pulse results from A/B tests and feature launches. There are several types of dashboard widgets you can add or create including: * **Charts:** Create a new chart directly from a dashboard or export a chart created in Metrics Explorer to a dashboard. Supported charts include: * Drilldown Charts * Funnel Charts * Retention Charts * Distribution Charts * User Journey Charts * **Text**: Annotate dashboards with context or create section headers for better readability. * **Single Value:** Highlight a hero metric with clarity by adding a single value to the dashboard. * **Experiment, Feature Flag:** Get a quick snapshot of an experiment or feature flag. * **Funnel Metrics:** Visualize custom funnel metrics. ### To add a widget to a dashboard 1. Click **Add Widget**. 2. Select the type of widget you want to add. 3. Configure the widget, for example select a chart type and then select events and metrics you want to track. 4. Save the widget to the dashboard. ## Exploring your dashboard ### Edit date ranges By default, Statsig syncs charts and widgets on a dashboard with the date range set for the entire dashboard. To update this default date range, click the pencil icon in the top right corner. In the settings panel, you can modify the dashboard's title, description, and **Default Date Lookback Range**. Statsig applies this range automatically each time you open the dashboard. Choosing **Chart Default** allows each chart to use the date range originally set when it was first added to the dashboard. {% figure %} ![Dashboard settings and date range configuration](/images/product-analytics/dashboards/a89fc3b7-48f6-40b2-a226-11c925553f14.png) {% /figure %} You can also change the date range dynamically by modifying the date picker at the top right of the dashboard. Changes to this selector synchronize all charts and widgets on the dashboard. Statsig doesn't save these changes when you reopen the dashboard. {% figure %} ![Dashboard date picker interface](/images/product-analytics/dashboards/1c038930-f9bd-4d94-9bb4-4ba84a417dda.png) {% /figure %} ### Exporting your dashboard To share a static version, print, or save your dashboard, click the settings dropdown (**...**) in the top right corner and select **Export as PDF**. Statsig generates a PDF of the dashboard and downloads it automatically within a few seconds. {% figure %} Export dashboard menu {% /figure %} ### Cloning your dashboard To duplicate a dashboard, open it, click the settings dropdown (**...**) in the top right corner, and select **Clone**. A dialog appears to confirm the clone. After cloning, Statsig takes you to the new dashboard. {% figure %} ![Dashboard clone dialog](/images/product-analytics/dashboards/abf7b681-5859-4a0e-b63e-d6fe16ea6e77.png) {% /figure %} ### Filtering your dashboard Select the filters button below the dashboard name to add a global filter. The filter applies across all eligible widgets, so you can view updated results without filtering each widget individually. You can also use free-form text to apply generic filters, such as filtering emails that contain `@gmail.com`. When you expand a dashboard widget that has a dashboard filter applied, the filter appears as an **Inherited Dashboard filter**. Any changes you make at the widget level are temporary and Statsig doesn't save them. {% figure %} ![Dashboard global filter interface](/images/product-analytics/dashboards/397d0197-632d-4f25-a8be-a5413575173f.png) {% /figure %} #### Default dashboard filters Default dashboard filters let you pin commonly used filters directly to your dashboards. These filters appear at the top of the dashboard and apply across all eligible widgets, enabling quick comparisons across dimensions like company, region, or platform, without rebuilding filters from scratch. To configure default dashboard filters: 1. Navigate to your dashboard and click the settings cog ⚙️ 2. Scroll to **Default Filters** and configure the filters you want to pin to the dashboard. 3. Click **Save**. After configuration, the pinned filters appear at the top of the dashboard. You can swap filter values to see how different users, cohorts, or properties affect the same set of charts. When you change a filter value, all charts update automatically. This allows you to reuse the same dashboard with dynamic filtering instead of duplicating dashboards or manually editing each widget's filters. ### Refreshing your dashboard widgets To ensure your dashboard data is up to date, you can refresh dashboard widgets in several ways: **Manual Refresh**: Click the refresh button shown below to refresh all dashboard widgets at once. {% figure %} ![Dashboard refresh button](/images/product-analytics/dashboards/2799df0e-2a71-454b-8fee-df0420cdf68b.png) {% /figure %} **Automatic dashboard refreshes**: Statsig can automatically refresh dashboards on a schedule. Results are cached for faster loading. You can configure a refresh frequency for each dashboard (for example, hourly or daily). Queries for that dashboard run on the specified schedule and store the results. When someone opens the dashboard, the most recent cached data appears immediately without triggering fresh queries. To configure automatic dashboard refreshes: 1. Navigate to your dashboard and click the settings cog. 2. Scroll to **Schedule Dashboard Refresh** and set the interval. 3. Click **Save**. This feature is useful for shared dashboards or recurring check-ins where fresh data should be ready without manual effort. ### Dashboard subscriptions Dashboard subscriptions send a scheduled snapshot of a dashboard to your team so stakeholders can stay up to date without opening Statsig. **What gets delivered:** A subscription delivers a snapshot of the dashboard at send time, including: * Dashboard title and link * All widgets currently on the dashboard * The dashboard’s default date range (and any pinned default dashboard filters, if configured) Depending on your workspace setup, Statsig can deliver subscriptions by email and/or Slack. **Creating a subscription:** 1. Open the dashboard you want to subscribe to. 2. Click the settings menu (**...**) in the top right corner. 3. Select **Add/Manage Dashboard Subscription**. 4. Configure your delivery channel (email, Slack), recipients (emails or Slack channel), and schedule (for example, daily or weekly). 5. Click **Save**. ### Organize your dashboard A well-organized dashboard conveys what information is most important and shows the relationship between items. You can move and resize dashboard widgets in place. Each dashboard uses a grid layout. To move a widget, click and hold empty space on the widget and drag it to the target position. To resize a widget, click and hold the bottom right edge and drag to the target size. ### View and edit a chart in a dashboard You can add any chart supported in Metrics Explorer to a dashboard. Dashboard charts are interactive, not static. To explore a chart on the dashboard, click the **[ ]** icon. When expanded, you have access to the full Metrics Explorer and can modify the query, date range, and chart title. These changes are for exploration only and don't permanently alter the chart on the dashboard. To save changes, configure the chart and click **Save** to update the existing chart, or **Save As** to create a new chart on the dashboard. {% figure %} ![Chart editing interface in dashboard](/images/product-analytics/dashboards/aa563da7-eab1-4578-a081-1ad1f343cc5c.png) {% /figure %} ## Tips ### Dashboard templates Dashboard templates reduce time to insights. Statsig compiles industry-standard metrics so your data visualization focuses on critical success indicators. You can start with a template and add additional charts as needed. Statsig offers the following templates: * Product Growth * Use Statsig's built-in metrics like DAU, WAU, Stickiness, etc. to track long-term product health * Feature Success * Input a feature usage event, like form\_submit or purchase\_completed, to evaluate the success of your feature rollout with usage and retention metrics * Optionally, you can add a feature adoption event or related feature flag to broaden the scope of your tracking * B2B SaaS Topline Metrics * Input a key feature event and user subscription events to monitor product usage, user retention and subscription conversion rates over time * Web Analytics * Available with Autocapture, track essential web metrics like page views, clicks, and sessions duration. Go to the [Autocapture guide](/guides/sidecar-experiments/measuring-experiments#using-autocapture) for more information. * Create from Gate or Experiment * Input your Gate or Experiment of interest to generate a focused dashboard with related monitoring metrics * Create from Tag * Utilize Statsig's project tagging to create and easily maintain a dashboard with the metrics, experiments, and feature flags you care about ### Finding dashboards After you create a dashboard, the *Dashboards* tab offers several ways to find it: 1. In the *Dashboards* tab, click the search box and select **My dashboards** to find dashboards you created. 2. In the *Dashboards* tab, click the filter icon to scope to dashboards with specific tags or created by specific team members. 3. In the *Dashboards* tab, search for the dashboard name. 4. Anywhere in Statsig, open global search with **cmd+k** and type the dashboard name. ================================================================================ # Users Tab URL: https://docs.statsig.com/product-analytics/users-tab Updated: 2026-07-11 ================================================================================ ## How the Users Tab works The Users section of Statsig gives you an event-by-event level understanding of how users (and other Unit IDs) use your product. This lets you diagnose issues and understand user behavior at a granular level using event logs, session streams, and session replays. It's also a central place to manage feature rollout and experiment overrides. ## Exploring the Users Tab When you open the Users tab, enter a specific ID and select the matching Unit ID type. There are also many context-specific entry points to the Users Tab within Statsig. A Users Tab query surfaces the following information: * **Properties:** Additional context for this ID, including email, country, OS, and browser. Customers with user profiles can see all user profile properties for a given ID. * **Log History & Event Details:** History of events and exposures for this ID, limited to 5000 rows. You can view these as an event log stream or a session stream. * **Overrides:** The ability to manually override an ID into a given feature gate or experiment variant. * **Session Replays:** A sample of session replays the user has triggered. ## Events ### Log Streams Under the Events tab, you can view the events and exposures this user triggered in their event log stream. From here, you can diagnose which features this user was most interested in or which experiment group they were in. {% figure %} Event log stream interface {% /figure %} ### Session Streams Switch from **Log Stream** to **Session Stream** to get a session-centric view of all events the user triggered during each session. You can refine either view by changing the time window, hiding events, and filtering to show only events or experiment/gate exposures. {% figure %} ![Session stream interface showing user events grouped by session](/images/product-analytics/users-tab/7ac4a9f8-a3d4-485b-9a8a-8ce972ba6cda.png) {% /figure %} ## Overrides Under the Overrides tab, you can override an ID into a given experiment variant or feature gate rollout. If there's already an active override for an ID on a particular entity, it appears in this section and you can modify it inline. Overrides set in the Users Tab sync to the "Overrides" section of the relevant entity, and you can edit or remove them from either interface. ## Session Replays Under Session Replays, you can navigate to a sample of session replays the user has triggered for additional insight into their behavior. For more information, refer to [Session Replays](/session-replay/overview). ================================================================================ # Infra Analytics Overview URL: https://docs.statsig.com/infra-analytics/overview Updated: 2026-07-11 ================================================================================ **[Infra Analytics](https://statsig.com/infra-analytics)** pulls in logs, metrics, and alerts so you get system-level observability in the same place you analyze product outcomes. * Collect metrics and traces through OpenTelemetry (OTEL) * Search and analyze logs to investigate issues * Create alerts tied to service health * Connect infrastructure signals to product analytics for a unified understanding of impact ## Key features * **Logs Explorer**: Debug incidents with search, filters, and visualizations * **Topline Alerts**: Catch regressions and anomalies with log/metric-based triggers * **Metrics Explorer**: Inspect infrastructure metrics alongside product metrics *** ## Set up Infra Analytics * [Set up OTEL ingestion](/infra-analytics/getting-started) to start sending logs and metrics * Traces are in limited preview: contact Statsig in Slack to request access ================================================================================ # Getting Started with OTEL + Statsig URL: https://docs.statsig.com/infra-analytics/getting-started Updated: 2026-07-11 ================================================================================ This guide helps you set up and send OpenTelemetry telemetry to Statsig so you can use Infra Analytics (Logs Explorer, Metrics Explorer, Alerts). There are two common paths: * Kubernetes/OpenTelemetry Collector: scrape logs and metrics from your cluster and export to Statsig. Go to [Open Telemetry Logs and Metrics](/server/concepts/open_telemetry) for a more complete guide. * Applications: export traces, metrics, and logs to your OpenTelemetry Collector (or traces directly from TypeScript/Node). Go to the quick starts below. {% callout type="info" %} **Endpoint & Auth** * Endpoint: `https://api.statsig.com/otlp` * Auth header: `statsig-api-key: ` {% /callout %} {% callout type="info" %} Direct trace export to the Statsig OTLP endpoint is only available for TypeScript/Node. For all other languages, send traces to your OpenTelemetry Collector and forward from the Collector to Statsig over OTLP/HTTP. {% /callout %} {% callout type="info" %} Need a deeper setup guide? Go to [Open Telemetry Logs and Metrics](/server/concepts/open_telemetry) for collector installation and configuration, and the [Traces Explorer quick start](/infra-analytics/send-traces) for language-specific trace examples. {% /callout %} *** ## Application Telemetry quick starts {% tabs %} {% tab title="Node.js" %} Install dependencies: ```bash npm install --save \ @opentelemetry/sdk-node \ @opentelemetry/auto-instrumentations-node \ @opentelemetry/exporter-trace-otlp-http \ @opentelemetry/exporter-metrics-otlp-http \ @opentelemetry/api-logs \ @opentelemetry/sdk-logs \ @opentelemetry/exporter-logs-otlp-http \ @opentelemetry/resources \ @opentelemetry/semantic-conventions ``` Initialize OpenTelemetry (e.g., `instrumentation.js`): ```js // instrumentation.js const { NodeSDK } = require('@opentelemetry/sdk-node'); const { resourceFromAttributes } = require('@opentelemetry/resources'); const { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } = require('@opentelemetry/semantic-conventions'); // import if you want to enable traces const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-http'); const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics'); const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node'); // For troubleshooting, set the log level to DiagLogLevel.DEBUG // const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api'); // diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); const statsigKey = process.env.STATSIG_SERVER_SDK_SECRET; const headers = { 'statsig-api-key': statsigKey ?? '' }; const sdk = new NodeSDK({ resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'statsig-node-service', [ATTR_SERVICE_VERSION]: process.env.VERSION || '1', env: process.env.NODE_ENV || 'development', }), traceExporter: new OTLPTraceExporter({ url: 'https://api.statsig.com/otlp/v1/traces', // or // url: /v1/traces headers, }), metricReader: new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter({ url: 'https://api.statsig.com/otlp/v1/metrics', // or // url: /v1/metrics headers, }), exportIntervalMillis: 60000, }), instrumentations: [getNodeAutoInstrumentations()], }); sdk.start(); ``` To set up application logs with OTel, use the pino or winston bridges. The example below uses [pino](https://getpino.io/#/) with [pino auto instrumentation](https://www.npmjs.com/package/@opentelemetry/instrumentation-pino). Install the pino instrumentation: ```bash npm i pino @opentelemetry/instrumentation-pino ``` ```js // instrumentation.js (continued) const { BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs'); const statsigKey = process.env.STATSIG_SERVER_SDK_SECRET; const headers = { 'statsig-api-key': statsigKey ?? '' }; const sdk = new NodeSDK({ // ... other config ... logRecordProcessors: [ new BatchLogRecordProcessor( new OTLPLogExporter({ url: 'https://api.statsig.com/otlp/v1/logs', // or // url: /v1/logs headers }) ), ], instrumentations: [getNodeAutoInstrumentations(), new PinoInstrumentation()], }); // in your application code, e.g., app.js const pino = require('pino'); const logger = pino(); logger.info('OTel logs initialized'); ``` The Statsig SDK also supports forwarding logs to Log Explorer. Go to the alternative logging example below. ```js // Requires: npm i @statsig/statsig-node-core const { Statsig, StatsigUser } = require('@statsig/statsig-node-core'); const s = new Statsig(process.env.STATSIG_SERVER_SDK_SECRET); await s.initialize(); const user = new StatsigUser({ userID: 'a-user', custom: { service: process.env.OTEL_SERVICE_NAME || 'my-node-service' }, }); // levels: trace, debug, info, log, warn, error s.forwardLogLineEvent(user, 'info', 'service started', { version: process.env.npm_package_version }); try { // your app code } catch (err) { s.forwardLogLineEvent(user, 'error', 'unhandled error', { message: String(err?.message || err), stack: err?.stack, }); } ``` Run your service: Require or import `instrumentation.js` before any other application code to ensure instrumentation sets up correctly. ```bash STATSIG_SERVER_SDK_SECRET=YOUR_SECRET \ OTEL_SERVICE_NAME=my-node-service \ node -r ./instrumentation.js app.js ``` Tip: you can configure exporters through environment variables instead of code: * `OTEL_EXPORTER_OTLP_ENDPOINT=https://api.statsig.com/otlp` * `OTEL_EXPORTER_OTLP_HEADERS=statsig-api-key=${STATSIG_SERVER_SDK_SECRET}` * `OTEL_EXPORTER_OTLP_PROTOCOL=http/json` {% /tab %} {% tab title="Next.js" %} Go to the official Next.js OpenTelemetry instructions for [pages router](https://nextjs.org/docs/pages/guides/open-telemetry) and for [app router](https://nextjs.org/docs/app/guides/open-telemetry). Install dependencies: ```bash npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/sdk-trace-node @opentelemetry/exporter-trace-otlp-http @opentelemetry/auto-instrumentations-node ``` Add `instrumentation.ts` at the app root (Next 13+): ```ts // instrumentation.ts import { NodeSDK } from '@opentelemetry/sdk-node'; import { resourceFromAttributes } from '@opentelemetry/resources'; import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'; // For troubleshooting, set the log level to DiagLogLevel.DEBUG // const { diag, DiagConsoleLogger, DiagLogLevel } = require('@opentelemetry/api'); // diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); export async function register() { const headers = { 'statsig-api-key': process.env.STATSIG_SERVER_SDK_SECRET ?? '' }; const sdk = new NodeSDK({ resource: new Resource({ [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'statsig-node-service', [ATTR_SERVICE_VERSION]: process.env.VERSION || '1', env: process.env.NODE_ENV || 'development', }), traceExporter: new OTLPTraceExporter({ url: 'https://api.statsig.com/otlp/v1/traces', // or // url: /v1/traces headers, }), metricReader: new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter({ url: 'https://api.statsig.com/otlp/v1/metrics', // or // url: /v1/metrics headers, }), exportIntervalMillis: 60000, }), instrumentations: [getNodeAutoInstrumentations()], }); sdk.start(); } ``` To set up application logs with OTel, use the pino or winston bridges. The example below uses [Pino](https://getpino.io/#/) with [Pino auto instrumentation](https://www.npmjs.com/package/@opentelemetry/instrumentation-pino). Install the pino instrumentation: ```bash npm i pino @opentelemetry/instrumentation-pino ``` ```js // instrumentation.ts (continued) import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs'; const statsigKey = process.env.STATSIG_SERVER_SDK_SECRET; const headers = { 'statsig-api-key': statsigKey ?? '' }; const sdk = new NodeSDK({ // ... other config ... logRecordProcessors: [ new BatchLogRecordProcessor( new OTLPLogExporter({ url: 'https://api.statsig.com/otlp/v1/logs', // or // url: /v1/logs headers }) ), ], instrumentations: [getNodeAutoInstrumentations(), new PinoInstrumentation()], }); // in your application code, e.g., app.ts import pino from 'pino'; const logger = pino(); logger.info('OTel logs initialized'); ``` The Statsig SDK also supports forwarding logs to Log Explorer. Go to the alternative logging example below. ```js // Requires: npm i @statsig/statsig-node-core import { Statsig, StatsigUser } from '@statsig/statsig-node-core'; const s = new Statsig(process.env.STATSIG_SERVER_SDK_SECRET); await s.initialize(); const user = new StatsigUser({ userID: 'a-user', custom: { service: process.env.OTEL_SERVICE_NAME || 'my-node-service' }, }); // levels: trace, debug, info, log, warn, error s.forwardLogLineEvent(user, 'info', 'service started', { version: process.env.npm_package_version }); try { // your app code } catch (err) { s.forwardLogLineEvent(user, 'error', 'unhandled error', { message: String(err?.message || err), stack: err?.stack, }); } ``` {% callout type="note" %} In Next.js, mark '@statsig/statsig-node-core' as a server external package in `next.config.js` to avoid bundling. {% /callout %} ### Using the Statsig + Vercel integration If you deploy to Vercel, use the [Statsig + Vercel integration](/integrations/vercel/) to forward logs to Statsig automatically. {% callout type="note" %} * Keep `STATSIG_SERVER_SDK_SECRET` out of client bundles (do not use `NEXT_PUBLIC_`). * Client/browser tracing requires separate web tracer setup; do not send secrets client-side. Consider routing through a Collector. {% /callout %} {% /tab %} {% tab title="Other Languages/Frameworks" %} Sending OTLP data directly to Statsig without a collector is only supported for Node.js applications. For other languages and frameworks, send OTLP data to a collector and configure the collector to forward data to Statsig. Go to the [Collector quick starts](#collector-quick-starts) below for example configurations. For installation and configuration instructions for other languages and frameworks, go to the [OpenTelemetry Language APIs & SDKs documentation](https://opentelemetry.io/docs/languages/). {% /tab %} {% /tabs %} *** ## Collector quick starts Running a Collector is optional but recommended for production workloads. Use the OpenTelemetry Collector as a gateway to receive OTLP from your applications and forward to Statsig. This is useful for centralizing telemetry collection, adding advanced sampling methods like tail-based sampling, or scraping logs and metrics from hosts or Kubernetes. {% tabs %} {% tab title="Kubernetes (Helm)" %} Create a minimal `values.yaml` for the OpenTelemetry Collector that forwards all signals (traces, metrics, logs) to Statsig: ```yaml title="values.yaml" config: receivers: otlp: protocols: http: grpc: processors: batch: {} exporters: otlphttp: endpoint: https://api.statsig.com/otlp encoding: json headers: statsig-api-key: ${env:STATSIG_SERVER_SDK_SECRET} service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [otlphttp] metrics: receivers: [otlp] processors: [batch] exporters: [otlphttp] logs: receivers: [otlp] processors: [batch] exporters: [otlphttp] ``` Install the Collector with Helm: ```bash helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts helm repo update helm install otel-gateway open-telemetry/opentelemetry-collector \ -n otel --create-namespace \ -f values.yaml ``` Provide the Statsig key as an environment variable to the Collector pods (for example, through a Secret and envFrom). Your applications then send OTLP to the in-cluster Collector endpoint (for example `http://otel-gateway-collector.otel.svc.cluster.local:4318`). For a production setup that also scrapes Kubernetes logs and metrics, go to the full guide: [Open Telemetry Logs and Metrics](/server/concepts/open_telemetry). {% callout type="note" %} **Version requirement** The `encoding: json` option in the OTLP HTTP exporter requires Collector v0.95.0 or newer. If you pin the image through Helm values, set `image.tag: "0.95.0"` (or newer). {% /callout %} {% /tab %} {% tab title="Docker (Compose)" %} Use Docker Compose to run a Collector gateway that accepts OTLP and forwards to Statsig. ```yaml title="docker-compose.yaml" services: otel-collector: image: otel/opentelemetry-collector-contrib:latest command: ["--config=/etc/otel-collector-config.yaml"] environment: - STATSIG_SERVER_SDK_SECRET=${STATSIG_SERVER_SDK_SECRET} volumes: - ./otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml ports: - "4317:4317" # OTLP gRPC - "4318:4318" # OTLP HTTP ``` Create the Collector config referenced above: ```yaml title="otel-collector-config.yaml" receivers: otlp: protocols: http: grpc: processors: batch: {} exporters: otlphttp: endpoint: https://api.statsig.com/otlp encoding: json headers: statsig-api-key: ${env:STATSIG_SERVER_SDK_SECRET} service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [otlphttp] metrics: receivers: [otlp] processors: [batch] exporters: [otlphttp] logs: receivers: [otlp] processors: [batch] exporters: [otlphttp] ``` Start the Collector: ```bash export STATSIG_SERVER_SDK_SECRET=YOUR_SECRET docker compose up -d ``` Point your applications at the Collector: `http://localhost:4318` (or `http://otel-collector:4318` from other compose services). The Collector forwards telemetry to Statsig using your key. {% /tab %} {% tab title="Other" %} You can run the Collector in other environments (VMs, bare metal) using the config below. Go to the [Collector documentation](https://opentelemetry.io/docs/collector/installation/) for other installation and deployment methods. ```yaml title="otel-collector-config.yaml" exporters: otlphttp: endpoint: https://api.statsig.com/otlp encoding: json headers: statsig-api-key: ${env:STATSIG_SERVER_SDK_SECRET} ``` {% /tab %} {% /tabs %} *** ## Common Collector configs (K8s & Docker) The following examples show popular receivers and processors you can enable in your Collector and still export to Statsig through the same `otlphttp` exporter. {% callout type="note" %} These components live in the contrib distribution. Use an image that includes them: * Docker: `otel/opentelemetry-collector-contrib` or newer * Helm: set `image.repository: otel/opentelemetry-collector-contrib` (and a compatible `image.tag`) {% /callout %} Helm values (contrib image): ```yaml title="values.yaml" image: repository: otel/opentelemetry-collector-contrib tag: "latest" pullPolicy: IfNotPresent ``` ### A. File logs (filelog receiver) Reads and parses logs from files on disk. This is useful for hosts, containers, or Kubernetes nodes. Minimal example: ```yaml receivers: filelog: include: [ /var/log/myservice/*.json ] start_at: beginning operators: - type: json_parser timestamp: parse_from: attributes.time layout: '%Y-%m-%dT%H:%M:%S%z' processors: batch: {} exporters: otlphttp: endpoint: https://api.statsig.com/otlp encoding: json headers: statsig-api-key: ${env:STATSIG_SERVER_SDK_SECRET} service: pipelines: logs: receivers: [filelog] processors: [batch] exporters: [otlphttp] ``` Kubernetes tip: to tail container logs on nodes, mount host paths (for example, `/var/log/pods` and `/var/lib/docker/containers`) into the Collector DaemonSet and set `include` to those paths. ### B. EC2 resource detection (resourcedetection processor) Automatically adds AWS EC2 metadata (cloud provider, region/zone, instance ID) to your telemetry. ```yaml processors: resourcedetection/ec2: detectors: [env, ec2] timeout: 2s override: false service: pipelines: traces: receivers: [otlp] processors: [resourcedetection/ec2, batch] exporters: [otlphttp] metrics: receivers: [otlp] processors: [resourcedetection/ec2, batch] exporters: [otlphttp] logs: receivers: [otlp] processors: [resourcedetection/ec2, batch] exporters: [otlphttp] ``` The Collector must be able to reach the EC2 metadata service (IMDS). Ensure network access to `169.254.169.254` and IMDSv2 where required. ### C. Docker container metrics (docker\_stats receiver) Emits container CPU, memory, network, and block I/O metrics by querying the Docker daemon. ```yaml receivers: docker_stats: endpoint: unix:///var/run/docker.sock collection_interval: 15s processors: batch: {} exporters: otlphttp: endpoint: https://api.statsig.com/otlp encoding: json headers: statsig-api-key: ${env:STATSIG_SERVER_SDK_SECRET} service: pipelines: metrics: receivers: [docker_stats] processors: [batch] exporters: [otlphttp] ``` Requirements: * Linux only (not supported on macOS or Windows). * Mount the Docker socket into the Collector container: `/var/run/docker.sock`. *** ## Related resources * OpenTelemetry Collector: https://opentelemetry.io/docs/collector/ * Kubernetes Collector components: https://opentelemetry.io/docs/platforms/kubernetes/collector/components/ * Helm chart: https://github.com/open-telemetry/opentelemetry-helm-charts * Collector configuration reference: https://opentelemetry.io/docs/collector/configuration * OTLP protocol specification: https://opentelemetry.io/docs/specs/otlp/ * Filelog receiver (contrib): https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/filelogreceiver * Resource detection processor (contrib): https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/resourcedetectionprocessor * Docker stats receiver (contrib): https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/dockerstatsreceiver * Collector contrib distribution: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib ================================================================================ # Traces Explorer Quick Start URL: https://docs.statsig.com/infra-analytics/send-traces Updated: 2026-07-11 ================================================================================ {% callout type="info" %} To set up Logs and Metrics as well, go to [Getting Started](/infra-analytics/getting-started) for more in-depth OpenTelemetry Collector setup instructions. {% /callout %} Use the OTLP/HTTP traces endpoint to forward spans into Traces Explorer. Authenticate using your Server Secret key in the header: * Endpoint: `https://api.statsig.com/otlp/v1/traces` * Header: `statsig-api-key: ` {% callout type="note" %} Statsig currently supports direct to API for TypeScript/Node only. For all other languages, send traces to your OpenTelemetry Collector and configure it to forward to Statsig over OTLP/HTTP. {% /callout %} For non-TypeScript apps, point your Collector (for example, `http://localhost:4318/v1/traces`) to Statsig by configuring the Collector to forward traffic to Statsig: ```yaml title="collector.yaml" receivers: otlp: protocols: http: exporters: otlphttp: endpoint: https://api.statsig.com/otlp encoding: json headers: statsig-api-key: ${env:STATSIG_SERVER_SDK_SECRET} service: pipelines: traces: receivers: [otlp] exporters: [otlphttp] ``` {% tabs %} {% tab title="TypeScript/Node" %} ```bash npm install @opentelemetry/sdk-node @opentelemetry/sdk-trace-node @opentelemetry/sdk-trace-base @opentelemetry/exporter-trace-otlp-http @opentelemetry/resources @opentelemetry/semantic-conventions @opentelemetry/api ``` ```js // trace.js const { NodeSDK } = require('@opentelemetry/sdk-node'); const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base'); const { Resource } = require('@opentelemetry/resources'); const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions'); const { trace } = require('@opentelemetry/api'); const sdk = new NodeSDK({ resource: new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: 'trace-sample-node', }), spanProcessor: new BatchSpanProcessor( new OTLPTraceExporter({ url: 'https://api.statsig.com/otlp/v1/traces', headers: { 'statsig-api-key': process.env.STATSIG_SERVER_SDK_SECRET || '' }, }), ), }); sdk.start().then(() => { const tracer = trace.getTracer('example'); const span = tracer.startSpan('do-work'); span.setAttribute('example', true); span.end(); setTimeout(() => sdk.shutdown(), 1000); }); ``` {% /tab %} {% tab title="Python" %} ```bash pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http ``` ```python # trace.py from opentelemetry import trace from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter trace.set_tracer_provider( TracerProvider(resource=Resource.create({"service.name": "trace-sample-python"})) ) exporter = OTLPSpanExporter( endpoint="http://localhost:4318/v1/traces", # your Collector ) trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(exporter)) tracer = trace.get_tracer(__name__) with tracer.start_as_current_span("do-work") as span: span.set_attribute("example", True) trace.get_tracer_provider().shutdown() ``` {% /tab %} {% tab title="Go" %} ```bash go get go.opentelemetry.io/otel/sdk go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp go.opentelemetry.io/otel/semconv/v1.26.0 ``` ```go // main.go package main import ( "context" "log" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" ) func main() { ctx := context.Background() exporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL("http://localhost:4318/v1/traces"), // your Collector ) if err != nil { log.Fatal(err) } tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), sdktrace.WithResource(resource.NewWithAttributes( semconv.SchemaURL, semconv.ServiceNameKey.String("trace-sample-go"), )), ) otel.SetTracerProvider(tp) tracer := otel.Tracer("example") ctx, span := tracer.Start(ctx, "do-work") span.SetAttributes(attribute.Bool("example", true)) span.End() _ = tp.Shutdown(ctx) } ``` {% /tab %} {% /tabs %} {% callout type="info" %} For collector installation and configuration, go to [Open Telemetry Logs and Metrics](/server/concepts/open_telemetry). For full Logs and Metrics setup, go to [Getting Started](/infra-analytics/getting-started). {% /callout %} ================================================================================ # Logs Explorer Overview URL: https://docs.statsig.com/infra-analytics/logs-explorer Updated: 2026-07-11 ================================================================================ Logs Explorer lets you query logs, traces, and ingested events from a single interface. Use it the same way whether you’re debugging infrastructure issues or investigating product event streams. * **Search**: Slice logs down to only what's relevant (by service, host, status code, etc.) * **Group**: Aggregate logs by dimensions like region, status, or browser. * **Visualize**: Plot log groupings over time to spot spikes, regressions, or anomalies instantly. *** ### Getting started with Log Explorer To get started with Log Explorer, follow the [OTEL onboarding guide](/infra-analytics/getting-started) to set up log ingestion. After you configure log ingestion, navigate to *Infra Analytics > Log Explorer* from the Statsig left menu. You can also use Logs Explorer in [Events Mode](/infra-analytics/events-mode-logs-explorer) to search and analyze your existing Statsig Events, with no additional instrumentation needed. You can switch between Logs and Events mode using the dropdown left of the search bar. {% figure %} ![Logs Explorer Switch Mode](/images/infra-analytics/lex-toggle.png) {% /figure %} *** ### Searching in Logs Explorer * **Write custom queries**: Refer to the [syntax guide](/infra-analytics/logs-explorer-queries) to craft your search. * **Using the query builder**: Point-and-click to construct filters without syntax overhead. {% figure %} ![Logs Explorer Overview](/images/infra-analytics/logs-explorer-1.png) {% /figure %} *** ### Download log results Export your query results to a file when you need to share findings or run them through a script. The **Download** button lives in the Logs Explorer toolbar, in the top-right corner next to the settings gear. Downloads work in both Logs and Events mode. To download results: 1. Run your query. 2. Open the **Download** menu from the toolbar. 3. Choose a format and row cap, then start the download. A progress indicator shows the export as it runs, and you can cancel mid-download. **Formats** * **CSV**: Columns match the visible table. The download excludes hidden columns, so the file reflects what you see on screen. * **JSONL**: Emits one full log object per line, which works well with tools like `grep` and `jq`. **Row caps** Choose a cap of 100, 1,000, or 10,000 rows. The default is 1,000; select 10,000 for heavier pulls. ================================================================================ # Query Syntax for Logs Explorer URL: https://docs.statsig.com/infra-analytics/logs-explorer-queries Updated: 2026-07-11 ================================================================================ This page covers common syntax you can use in day-to-day investigations, plus a few ready-to-edit examples. {% callout type="info" %} Plain text searches will only match against the **log message field**, not the entire log body. {% /callout %} ## Basics ### Property prefixes | Prefix | Description | Example | |--------|-------------|---------| | `@property` | Event or log properties | `@traceId:"1a3cg5"` | | `#property` | Reserved properties | `#custom_event:"shopping_cart_opened"` | | `$user` | User identifiers | `$stableID:"abcdef"` | | *none* | User properties | `tier:prod` | ### Logical operators These are query-level connectors. They combine or negate multiple conditions. | Operator | Description | Example | |----------|-------------|---------| | `AND` (case sensitive) | Match all conditions | `level:error AND service:api-gateway` | | `OR` (case sensitive) | Match any condition | `level:error OR level:warn` | | `!=` or `!:` | Exclude a condition | `status!:error OR service!:api-gateway` | ### Other operators These are field-level conditions. | Operator | Description | Example | |----------|-------------|---------| | `:` or `=` | equals | `status_code:200` or `status_code=200` | | `!=` or `!:` | not equals | `level!:debug` or `level!=debug` | | `>` `<` | greater/less than | `latency_ms>500` or `latency_ms<1000` | | `>=` `<=` | greater/less or equal | `latency_ms>=500` or `latency_ms<=2000` | ### Wildcard search You can use the `*` character as a wildcard in queries. A wildcard matches zero or more characters inside a field value. {% callout type="note" %} Wildcards can impact query performance. Use them sparingly. {% /callout %} ### Additional examples | Query | Description | Matches | |-------|-------------|---------| | `@"log.file.path":"/logs/pods/*"` | Path begins with `/logs/pods/` | `/logs/pods/1234/stdout.log` | | `@"log.file.path":"*/logs/pods/"` | Path ends with `/logs/pods/` | `/mnt/storage/logs/pods/` | | `service:"api-*"` | Services with names starting with `api-` | `api-gateway`, `api-auth` | | `@route:"*products/*"` | Page routes containing the string `products/` | `shopify.com/products/tshirts`, `shopify.com/cart/products/view` | | `@message!:""` | Return logs where the message field is **not null** | any log that has a `message` field | ================================================================================ # Events Mode on Logs Explorer URL: https://docs.statsig.com/infra-analytics/events-mode-logs-explorer Updated: 2026-07-11 ================================================================================ Events Mode brings the searching and filtering abilities from [Log Explorer](/infra-analytics/logs-explorer) to your *existing* Statsig events data. No additional instrumentation required! * Debug user activity with more control * Trace the sequence of actions a user performed * Narrow in on specific events from metadata properties {% callout type="info" %} Events Mode is available to all **Statsig Cloud customers**. {% /callout %} *** ### Get started with Events Mode 1. Open Logs Explorer from the left navigation. 2. Confirm you've switched from **Logs** mode to **Events** mode (the area next to the search bar should say **Events**). 3. Start searching across event names, properties, and time ranges. *** ### Construct a search You can search through events in two ways: 1. **Writing queries:** Use [syntax-based search](/infra-analytics/logs-explorer-queries) to target specific events and properties 2. **Using the query builder:** Point-and-click to construct filters without syntax overhead. ![Events Explorer Overview](/images/infra-analytics/events-explorer-querying.png) *** ### Example workflows | Description | Query | |-------------------------------------------------------|----------------------------------------------------------------------| | Find all enterprise account signups from web browser devices | `event_name:signup_completed AND #plan:enterprise AND #platform:web` | | Find all checkout events where the item name contained "black" | `@#custom_event:checkout_event,add_to_cart AND @product:"*black*"` | | Find all events from users with a gmail account | `#user_object.email:"*gmail.com"` | ================================================================================ # Topline Alerts with Logs URL: https://docs.statsig.com/infra-analytics/topline-alerts-logs Updated: 2026-07-11 ================================================================================ By combining log filters with [Topline Alerts](/product-analytics/topline-alerts), you can detect failures immediately, without writing custom scripts or dashboards. ### When to use log-based alerts - **Monitoring success rates** - Catch regressions before they impact SLOs. - **Detecting error spikes** - Trigger when 5xx or other errors rise above baseline. - **Isolate by segment** - Identify failures concentrated in a region, client, or device type ## Create a log-based Topline Alert (Statsig Cloud) This example creates a monitor for the success rate of a GET request. {% steps %} {% step title="Navigate to Topline Alerts" %} - Go to **Analytics → Topline Alerts** in the product menu. - Click **+Create**. - Enter a name for your alert. {% figure %} ![Alert setup step 1](/images/infra-analytics/loglinealert-1.png) {% /figure %} {% /step %} {% step title="Define Alert Criteria" %} - Select `statsig::log_line` as your event. - Apply filters to define what constitutes success and failure. If you're unsure which fields to filter on, open the [Logs Explorer](/infra-analytics/logs-explorer) and inspect the log body. {% figure %} ![Alert setup step 2](/images/infra-analytics/loglinealert-2.png) {% /figure %} {% /step %} {% step title="Set Up Evaluation Formula" %} - Define the formula for calculating your success rate. - (Optional) Add a group-by dimension. {% figure %} ![Alert setup step 3](/images/infra-analytics/loglinealert-3.png) {% /figure %} {% /step %} {% step title="Configure Alert Thresholds" %} - Set thresholds and the evaluation window. In this example, Statsig triggers a warning when success rate drops below 99.5% and an alert when success rate drops below 99.0%. {% figure %} ![Alert setup step 4](/images/infra-analytics/loglinealert-4.png) {% /figure %} {% /step %} {% step title="Configure Notifications" %} - Add the alert title and description for context. - Include diagnostic hints (e.g. "Check version X" or "Android requests timing out"). - Add subscribers. - Set priority. {% figure %} ![Alert setup step 5](/images/infra-analytics/loglinealert-5.png) {% /figure %} {% /step %} {% step title="Save the Alert" %} - Click **Save**. - After you save your alert, visit the **Diagnostics** tab to view a history of alert triggers. {% figure %} ![Alert setup step 6](/images/infra-analytics/loglinealert-6.png) {% /figure %} {% /step %} {% /steps %} --- ## Best practices - Set up [Slack notifications](/integrations/slack) for team visibility - Keep formulas simple (ratios & percentages are easiest to scan) - Add group-by dimensions (like country or app version) to pinpoint where issues occur - Write clear notification text that explains what the alert means ================================================================================ # Alerts Overview URL: https://docs.statsig.com/product-analytics/alerts-overview Updated: 2026-07-11 ================================================================================ ## Alerts Statsig offers two types of alerts on the platform: 1. **[Topline Metric Alerts](/product-analytics/topline-alerts)** - Monitor a metric’s overall performance, independent of experiments or gates. These alerts keep you informed about topline trends and highlight when key product or business metrics move in a concerning direction. 2. **[Rollout Alerts](/product-analytics/alerts-overview)** - Monitor how a metric behaves in the context of a feature gate or experiment rollout. These alerts help you confirm that no critical metrics regress when introducing a new change. ================================================================================ # Rollout Alerts URL: https://docs.statsig.com/metrics/rollout-alerts Updated: 2026-07-11 ================================================================================ ## How rollout alerts work On Statsig Cloud, Rollout Alerts are evaluated every 24 hours. Alerts only trigger if the cumulative metric delta is statistically significantly lower or higher than your threshold, which reduces alert noise. On Statsig Warehouse Native, Rollout Alerts are evaluated every time Metric Results are loaded in a feature gate or experiment. Loading Results on the first day provides more real-time visibility during this window. Alerts only trigger if the metric delta is statistically significantly lower or higher than your threshold, which reduces alert noise. Statsig applies all stats methodologies you have enabled for your experiment or gate rollout (CUPED, Sequential Testing, and so on) to alert calculations. {% callout type="note" %} Rollout Alerts do not alert at the **topline metric value** level, but rather at **the experiment/feature gate level** . This means that even if you have an experiment allocated to 10% of your users, but the metric change within that 10% allocation breaches the set threshold, Statsig alerts you. All alerts you receive are in the context of a specific experiment or feature gate, and to debug/resolve the alert you are directed to the offending experiment or gate in question. {% /callout %} ## Setting up a rollout alert To set up a metric alert, go to the **Metrics** tab, then **Metrics Catalog**, and search for the metric you want. {% figure %} Metrics catalog entry showing alert configuration option {% /figure %} After you open the Metric Detail View, go to the **Alerts** tab and tap **+ Create Alert**. Configure the following inputs: - **% Change:** The metric delta threshold that triggers an automated alert. This delta is measured in the context of the feature gate rollout or experiment, not as a top-line metric change across your entire user base. - **Minimum Participating Units:** The minimum number of unique units emitting the metric in each test group before triggering an alert. Statsig displays the 25th, 50th, and 75th percentiles of metric-emitting unit counts per group across your gates and experiments to help you choose a reasonable threshold. - **Direction:** Positive or negative, depending on whether you want to be alerted when the metric exceeds or drops below a target threshold. - **Subscribers:** By default, all creators of a feature gate or experiment with an alert configured are notified when the alert fires. You can also add global subscribers, who are notified when any feature gate or experiment regresses the metric beyond the threshold. In a large project, adding yourself as a global subscriber of a metric can generate high alert volume. {% figure %} Metric alert creation panel specifying threshold, units, and subscribers {% /figure %} After you configure a Rollout Alert for a metric, an alarm bell icon appears next to the metric in the Metrics Catalog. You can also filter for metrics with alerts using the standard metrics filter next to the search bar. ## Determining the right threshold To help you configure the right threshold, the Scorecard shows a preview of how much the metric has moved in the context of any feature gates or experiments containing that metric. {% figure %} ![Scorecard showing metric delta preview with alert threshold guidance](/images/metrics/rollout-alerts/2a0434fe-e708-497d-b5bd-53c7b609cd47.png) {% /figure %} To see how a metric has trended over a longer period, hover over the metric delta for a feature gate or experiment and tap a data point to view more details. This opens the time-series for the metric, with a configurable date range picker. {% figure %} ![Metric trend hover showing detailed data point information](/images/metrics/rollout-alerts/a57599b6-044f-42b6-97fc-e6fb8bb3435c.png) {% /figure %} {% figure %} ![Metric time-series chart with selectable date range](/images/metrics/rollout-alerts/f4b0e8a2-c5aa-4bb3-b57d-36930e906e5d.png) {% /figure %} ## Alert notifications and resolution When a Rollout Alert triggers, all subscribers and the relevant gate or experiment creator(s) receive a notification by email, in the Statsig Console, and through Slack for users who have configured Slack notifications for their Statsig accounts. {% figure %} Alert notification card showing metric exceeding threshold {% /figure %} {% figure %} Slack notification preview for rollout alert {% /figure %} Tapping **View Alert** takes you to the Diagnostics page of the offending feature gate or experiment that triggered the rollout alert. {% figure %} Diagnostics page showing call-to-action after alert triggers {% /figure %} In the Diagnostics section, scroll to the **Rollout Alerts** section and select the active alert. The metric delta trend appears with the threshold overlaid. To resolve the alert, tap **Resolve** inline. You can either resolve the alert and provide an explanation, or snooze the alert for a specified period. The alert status updates both in the **Rollout Alerts** section and at the top of the Pulse page for the gate or experiment. {% figure %} Rollout alerts section with resolve and snooze controls {% /figure %} ## Viewing alert history To view alert history, go to the **Metrics** tab, then **Metrics Catalog**, and select the metric. Then go to **Alerts**, open the **…** menu in the **Experiment and Gate Alerts** section, and select **View Alert History**. The history shows all instances of the alert firing, being resolved, or being snoozed, along with the resolution reason, who resolved the alert, and when. {% figure %} Alert history table listing firing and resolution events {% /figure %} {% figure %} Alert resolution dialog capturing notes and snooze options {% /figure %} ================================================================================ # Topline Alerts URL: https://docs.statsig.com/product-analytics/topline-alerts Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Reach out if you would like Topline Alerts enabled for your project. {% /callout %} Topline Alerts are available in Statsig Cloud and Statsig Warehouse Native with support available for three types of Topline Alerts: | Alert Condition Type | When to Use | Example | | -------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------- | | **Threshold** | Use when you want to stay above or below a fixed number. | “Alert me when P90 latency spikes above 15 seconds.” | | **Change** | Use when the absolute size of the change matters. | “Alert me when hourly P90 latency increases by 10 seconds.” | | **Change (%)** | Use when the relative size of the change matters more than raw numbers. | “Alert me when hourly P90 latency increases by 50%.” | ## Creating a topline alert {% steps %} {% step title="Navigate to Topline Alerts" %} Go to **Analytics → Topline Alerts** in the product menu. {% /step %} {% step title="Create a New Alert" %} Click **+Create** and give the new alert a name. {% /step %} {% step title="Select the Event or Metric Source" %} Pick the data you want to monitor. - **On Statsig Warehouse Native:** Select a Metric Source, filter, and group by the dimensions you want. - **On Statsig Cloud:** Select the event, aggregation, filters, and group-by conditions for this alert. {% callout type="info" %} This example sets up an alert to monitor P90 latency for `mex_query` events, filtering out internal employee queries and grouping by the `hadGroupBy` dimension. {% /callout %} {% figure %} ![Select events or metrics for a Topline Alert](/images/infra-analytics/topline-alerts-choose-events.png) {% /figure %} {% /step %} {% step title="Review the Alert Preview" %} The preview shows how your metric is trending with the current setup. Confirm values look correct, or open the metric in [Metrics Explorer](/product-analytics/drilldown) for deeper analysis. {% figure %} ![Topline Alert preview showing trending metric](/images/infra-analytics/topline-alerts-preview.png) {% /figure %} {% /step %} {% step title="Set Alert Conditions" %} The preview updates along with each change. Define the: - Condition type (threshold, change, change %) - Directionality - _Alert_ and _Warn_ values - Evaluation window {% callout type="warning" %} On Warehouse Native: Define the evaluation frequency, lookback, and max delay. These values directly influence warehouse compute costs. {% /callout %} {% figure %} ![Configure alert conditions for a Topline Alert](/images/infra-analytics/topline-alerts-conditions.png) {% /figure %} {% /step %} {% step title="Add Notifications" %} Notifications go to email, the Statsig Console, and Slack (if connected). Project-wide defaults live in Settings. - Draft a clear, actionable message subscribers receive when the alert fires. - Add subscribers. - Set alert priority. - Configure re-notification rules if alerts should resend while conditions hold. {% /step %} {% step title="Save and Monitor" %} After saving, triggered alerts appear at the top of the page. From here you can: - View samples of the event. - Open the trend in [Metrics Explorer](/product-analytics/drilldown). - Mute the alert temporarily if it is noisy or already under investigation. {% figure %} ![Topline Alerts table with active alerts](/images/product-analytics/topline-alerts-overview.png) {% /figure %} {% /step %} {% /steps %} ## Diagnostics Navigate to the **Diagnostics** tab to review alert history, inspect samples, open Metrics Explorer, or mute noisy alerts. {% figure %} ![Diagnostics tab showing alert history](/images/product-analytics/topline-alerts-diagnostics.png) {% /figure %} --- ## Related resources - [Create a Topline Alert on log lines](/infra-analytics/topline-alerts-logs) - [Set up team Slack notifications](/integrations/slack/#team-notifications) ================================================================================ # Statsig Web Analytics Overview URL: https://docs.statsig.com/webanalytics/overview Updated: 2026-07-11 ================================================================================ ## Web analytics overview [Web analytics](https://www.statsig.com/web-analytics) lets you track and watch key measures for your website easily. It is different from product analytics because it's simpler and more direct, making it great for marketers, web site maintainers, or anyone familiar with tools like Google Analytics. With Web Analytics and Statsig Dashboards you can easily gather insights such as number of visitors, views, sessions, how long sessions last, error rates, usage journey, and more. After you add web analytics to your application, events appear in your metrics page: https://console.statsig.com/metrics/events {% figure %} ![Metrics events dashboard showing web analytics data](/images/web-analytics-dash-1.png) {% /figure %} ## Getting started ### Option 1: HTML script installation Add a single script tag to your website: ```html ``` Get YOUR\_CLIENT\_KEY from *Project Settings > Keys & Environments*. Reveal the Client API Key, copy, and paste it over the \[YOUR\_CLIENT\_KEY] in the snippet above. ![Project settings showing client API key configuration](/images/Client-api-key-web-analytics.png) ### Option 2: HTML script with advanced control For more control over initialization and configuration, you can manually initialize the Statsig client: ```html ``` ### Option 3: JavaScript client SDK (React bindings) If using the JavaScript client SDK or another framework, use the corresponding SDK with the autocapture plugin (JS, React, Angular, etc.): ```javascript import * as React from 'react'; import { StatsigProvider } from '@statsig/react-bindings'; import { StatsigAutoCapturePlugin } from '@statsig/web-analytics'; return ( Loading... } options={{ plugins: [ new StatsigAutoCapturePlugin() ] }}>
Hello
); ``` ## Autocaptured events Statsig Web Analytics automatically captures the following events without any additional code: ### Page navigation events * **`auto_capture::page_view`** - Triggered when a user navigates to a new page. Includes page URL, query parameters, referrer information, and metadata. * **`auto_capture::page_view_end`** - Triggered when a user leaves a page. Includes engagement metrics like scroll depth, time on page, and whether the exit was due to inactivity. ### User interaction events * **`auto_capture::click`** - Captures all click events with target element information, coordinates, and page context. * **`auto_capture::rage_click`** - Detects rapid repeated clicks in the same area, often indicating user frustration. * **`auto_capture::dead_click`** - Identifies clicks that don't result in any page changes or navigation, potentially indicating broken functionality. * **`auto_capture::form_submit`** - Captures form submission events with form metadata. * **`auto_capture::copy`** - Tracks when users copy text from your page, including the selected text content. ### Technical performance events * **`auto_capture::error`** - Automatically captures JavaScript errors with stack traces, error messages, and context. * **`auto_capture::web_vitals`** - Captures Core Web Vitals metrics (refer to the [Web vitals events](#web-vitals-events) section below). * **`auto_capture::performance`** - Collects page load performance metrics including load times, DOM interactive time, and network transfer data. (deprecated, use `auto_capture::web_vitals` instead) ### Session events * **`auto_capture::session_start`** - Marks the beginning of a new user session. ### Console logging (disabled by default) * **`statsig::log_line`** - Captures console log output when explicitly enabled. This event is disabled by default and must be configured to activate. For details on how to enable console log autocapture, refer to [Console Log Capture Configuration](#console-log-capture-configuration). ## Event filtering and console configuration ### Disabling events through the console You can disable specific autocapture events from the Statsig console under *Project Settings > Analytics & Session Replay*: ![Analytics event filtering configuration interface](/images/autocapture-settings.png) ### Programmatic event filtering For more granular control, you can filter events programmatically using a custom filter function: ```javascript const client = new StatsigClient( 'YOUR_CLIENT_KEY', { userID: 'optional' }, { plugins: [ new StatsigAutoCapturePlugin({ eventFilterFunc: (event) => { // Filter out events from admin pages if (event.metadata.pageUrl && event.metadata.pageUrl.includes('/admin/')) { return false; } return true; } }) ]} ); ``` ### Console log capture configuration Console log capture is disabled by default but you can enable it with the following configuration: ```javascript const client = new StatsigClient( 'YOUR_CLIENT_KEY', { userID: 'optional' }, { plugins: [ new StatsigAutoCapturePlugin({ consoleLogAutoCaptureSettings: { enabled: true, } }) ]} ); ``` There are a few additional optional configuration settings available when capturing console logs: * **`logLevel`**: minimum level to capture (`debug` | `info` | `log` | `warn` | `error`) * **`sampleRate`**: fraction between 0 and 1 to sample captured logs * **`maxKeys`**: maximum number of keys to serialize from logged objects * **`maxDepth`**: maximum nesting depth when serializing objects * **`maxStringLength`**: maximum number of characters for stringified values ```javascript const client = new StatsigClient( 'YOUR_CLIENT_KEY', { userID: 'optional' }, { plugins: [ new StatsigAutoCapturePlugin({ consoleLogAutoCaptureSettings: { enabled: true, logLevel: 'warn', // capture warn and error sampleRate: 0.5, // capture 50% of matched logs maxKeys: 50, // limit number of keys per object maxDepth: 3, // limit nested object depth maxStringLength: 2000, // truncate long strings } }) ]} ); ``` ## Web vitals events Statsig automatically captures Core Web Vitals metrics that are essential for measuring user experience and SEO performance: ### Core Web Vitals metrics * **CLS (Cumulative Layout Shift)** - Measures visual stability by tracking unexpected layout shifts during page load. Lower scores indicate better user experience. * **FCP (First Contentful Paint)** - Measures loading performance by tracking when the first text or image is painted on the screen. Faster FCP times indicate better perceived performance. * **LCP (Largest Contentful Paint)** - Measures loading performance by tracking when the largest content element becomes visible. This metric correlates strongly with user-perceived load times. * **TTFB (Time to First Byte)** - Measures server response time by tracking how long it takes to receive the first byte of response from the server. This indicates server and network performance. * **INP (Interaction to Next Paint)** - Measures responsiveness by tracking the longest latency across user interactions. Lower values mean more consistent, responsive experiences. Statsig automatically collects and sends these metrics as `auto_capture::web_vitals` events, providing insights into your website's performance and user experience quality. ## Single page application (SPA) support Statsig Web Analytics automatically supports single page applications without any additional configuration. Statsig intelligently detects route changes through: ### Automatic route change detection * **Browser Navigation**: Listens for `popstate` events to detect when users use browser back/forward buttons * **Programmatic Navigation**: Automatically proxies `history.pushState` calls to detect when your application programmatically navigates to new routes * **Automatic Page Views**: Triggers new `auto_capture::page_view` events whenever route changes are detected Modern SPA frameworks like React Router, Vue Router, and Angular Router work with Statsig Web Analytics without requiring manual page view tracking. ## Exploring events in Metrics explorer In [Metrics Explorer](https://console.statsig.com/metrics/explore), you can filter and group your events using complex filters and dimensions, then add the analysis to your dashboard. For more on Metrics Explorer features like Funnels and Retention, refer to the [Product Analytics overview](/product-analytics/overview). ![Metrics Explorer interface for analyzing web analytics data](/images/webanalytics/overview/f4ed6f2c-79ce-4e47-ba6d-8dbe69978cc0.png) For example autocapture event details, refer to [Autocapture Metrics](/webanalytics/autocapture). ## Autocapture settings The autocapture plugin or manual setup can take an optional `options` parameter to customize the autocapture settings. * **`eventFilterFunc`**: `Optional[Callable[[AutoCaptureEvent], bool]]` - A function to filter events based on their metadata. * **`consoleLogAutoCaptureSettings`**: `Optional[ConsoleLogAutoCaptureSettings]` - An object to configure console log capture. **consoleLogAutoCaptureSettings**: * **`enabled`**: `Optional[bool]` - A boolean to enable or disable console log capture. * **`logLevel`**: `Optional[str]` - A string to set the minimum log level for console log capture. * **`sampleRate`**: `Optional[float]` - A number to set the sample rate for console log capture. (0-1) * **`maxKeys`**: `Optional[number]` - Maximum number of keys to serialize from logged objects. * **`maxDepth`**: `Optional[number]` - Maximum nesting depth when serializing objects. * **`maxStringLength`**: `Optional[number]` - Maximum number of characters for stringified values. **eventFilterFunc**: * **`event`**: `AutoCaptureEvent` - The event object Statsig is capturing. * **`return`**: `bool` - A boolean to return true to capture the event or false to ignore it. ================================================================================ # Autocapture Metrics URL: https://docs.statsig.com/webanalytics/autocapture Updated: 2026-07-11 ================================================================================ The JavaScript, React, and Angular SDKs support autocapture, which automatically captures events and their attributes without requiring you to manually instrument your code. ## Attributes captured ### Common metadata (on all events) Statsig automatically captures comprehensive metadata with each autocapture event to provide rich context for analysis: * To filter events, you can use the `eventFilterFunc` option when initializing the Statsig client. * To sanitize events and their metadata, listen for the `pre_logs_flushed` client event and modify the event object before Statsig sends it. (Refer to [Client Event Emitter](/client/javascript-sdk#client-event-emitter) for more details.) #### Page and environment metadata | Property | Description | | --- | --- | | `metadata.title` | Page title from `` tag | | `metadata.current_url` | Full current URL | | `metadata.hostname` | Domain name of the current page | | `metadata.pathname` | Path portion of the URL | | `metadata.user_agent` | Browser user agent string (truncated to 200 chars) | | `metadata.locale` | Browser language setting | | `metadata.timezone` | User's timezone | | `metadata.timezone_offset` | Timezone offset in minutes | | `metadata.timestamp` | Event timestamp in milliseconds | #### Screen and viewport data | Property | Description | | --- | --- | | `metadata.screen_width` | Screen width in pixels | | `metadata.screen_height` | Screen height in pixels | | `metadata.viewport_width` | Browser viewport width in pixels | | `metadata.viewport_height` | Browser viewport height in pixels | #### Network information | Property | Description | | --- | --- | | `metadata.effective_connection_type` | Connection speed estimate | | `metadata.rtt_ms` | Round-trip time in milliseconds | | `metadata.downlink_mbps` | Download speed in Mbps | | `metadata.save_data` | Whether user has data saver enabled | #### Referrer and traffic source data | Property | Description | | --- | --- | | `metadata.referrer` | Full referrer URL | | `metadata.referrer_domain` | Domain of the referrer | | `metadata.referrer_path` | Path of the referrer URL | | `metadata.searchEngine` | Detected search engine | | `metadata.searchQuery` | Search query from referrer | #### Campaign and marketing attribution Statsig automatically captures 25+ marketing attribution parameters including: | Property | Description | | --- | --- | | `metadata.utm_source` | UTM source parameter | | `metadata.utm_medium` | UTM medium parameter | | `metadata.utm_campaign` | UTM campaign parameter | | `metadata.utm_term` | UTM term parameter | | `metadata.utm_content` | UTM content parameter | | `metadata.utm_id` | UTM ID parameter | | `metadata.gclid` | Google Click ID | | `metadata.fbclid` | Facebook Click ID | | `metadata.msclkid` | Microsoft Bing Click ID | | `metadata.ttclid` | TikTok Click ID | | `metadata.li_fat_id` | LinkedIn Click ID | | `metadata.epik` | Pinterest Click ID | Additional platform IDs captured: * `gclsrc`, `wbraid`, `gad_source` (Google) * `dclid` (DoubleClick) * `irclid` (Impact) * `igshid` (Instagram) * `_kx` (Klaviyo) * `mc_cid`, `mc_eid` (Mailchimp) * `qclid` (Quora) * `rdt_cid` (Reddit) * `sccid` (Snapchat) * `ttc`, `ttc_id` (TikTok) * `twclid` (Twitter) ### Action events (click, form submit, etc.) | Property | Description | | --- | --- | | `tagName` | The HTML tag of the clicked element | | `classList` | The list of CSS classes on the clicked element | | `class` | The class attribute of the clicked element | | `id` | The ID attribute of the clicked element | | `ariaLabel` | The aria-label attribute of the clicked element | | `selector` | A CSS selector that uniquely identifies the clicked element | Depending on the event element, the following properties may be captured: * `action` * `method` * `formName` * `formId` * `href` * `content` * `inputName` * `textContent` * `selectedText` ...and more ### Page view events PageViewStart: | Property | Description | | --- | --- | | `last_page_view_url` | The URL of the last page view | PageViewEnd: | Property | Description | | --- | --- | | `page_view_length` | The length of the page view in milliseconds | | `lastScrollY` | The last scroll position in pixels | | `maxScrollY` | The maximum scroll position reached during the page view in pixels | | `lastScrollPercentage` | The last scroll percentage (0-100) of the page | | `maxScrollPercentage` | The maximum scroll percentage reached during the page view (0-100) | | `scrollDepth` | The scroll depth percentage (0-100) - deprecated, use maxScrollPercentage instead | | `dueToInactivity` | Whether the page view ended due to inactivity | ### Dead click events | Property | Description | | --- | --- | | `scrollTimeout` | Boolean indicating if a scroll timeout occurred | | `selectionChangeTimeout` | Boolean indicating if a selection change timeout occurred | | `mutationTimeout` | Boolean indicating if a mutation timeout occurred | | `absoluteTimeout` | Boolean indicating if an absolute timeout occurred | | `scrollDelayMs` | The number of milliseconds to wait before considering a scroll event as a dead click | | `selectionChangeDelayMs` | The number of milliseconds to wait before considering a selection change event as a dead click | | `mutationDelayMs` | The number of milliseconds to wait before considering a mutation event as a dead click | | `absoluteDelayMs` | The number of milliseconds to wait before considering an absolute timeout event as a dead click | ### Rage click events | Property | Description | | --- | --- | | `x` | The x-coordinate of the rage click | | `y` | The y-coordinate of the rage click | | `timestamp` | The timestamp of the rage click in milliseconds | ### Web vitals events | Property | Description | | --- | --- | | `name` | The name of the web vital ('CLS', 'FCP', 'LCP', 'TTFB') | | `value` | The current measurement value (e.g., milliseconds for LCP, FCP, etc.; a numeric score for CLS) | | `delta` | The change from the previously reported value, used to calculate cumulative values over time | | `id` | The ID of the web vital event| ### Console log events | Property | Description | | --- | --- | | `status` | The status of the console log ('debug', 'info', 'warn', 'error') | | `log_level` | The level of the console log ('debug', 'info', 'log', 'warn', 'error') | | `payload` | The payload of the console log (array of strings) | | `timestamp` | The timestamp of the console log in milliseconds (epoch time) | | `trace` | A stack trace (if available) showing where in the code the console log was triggered (array of strings) | | `source` | The source of the console log ('js-auto-capture') | #### Query parameters All URL query parameters are automatically captured as individual metadata fields with their parameter names as keys. ================================================================================ # AI Evals Overview URL: https://docs.statsig.com/ai-evals/overview Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Statsig isn't accepting new customers for AI Evals at this time. {% /callout %} ## What are AI evals? Statsig AI Evals has three core components for iterating on and serving LLM apps in production. 1. **[Prompts](/ai-evals/prompts)**: Prompts represent an LLM prompt and its associated config (model provider, model, temperature, and similar settings). A prompt typically represents a task for the LLM (for example, "Classify this ticket to a triage queue" or "Summarize this text"). You can version prompts, choose which version is live in Statsig, and retrieve prompts in production using the Statsig server SDKs. Prompts can be used as the control plane for your LLM apps without using the rest of the Evals product suite. 2. **[Offline Evals](/ai-evals/offline-evals)**: Offline evals provide quick, automated grading of model outputs on a fixed test set. They catch wins and regressions before any real users are exposed. For example, compare a new support bot's replies to human-curated answers to decide if the bot is ready to ship. You can grade output even without a reference dataset (for example, when using an LLM to validate English-to-French translation). 3. **[Online Evals](/ai-evals/online-evals)**: Online evals let you grade model output in production on real-world use cases. You can run the "live" version of a prompt and also shadow-run "candidate" versions without exposing users to them. Grading works directly on the model output and does not require a ground truth to compare against. ## Gates, Experiments, and Analytics The standard Statsig product capabilities are also available for use with AI Evals. For example, you can target an LLM feature at users who meet specific criteria using a Feature Gate, or roll out a new prompt version as an Experiment to understand its impact on metrics. ### LLM as a judge Some grading can use heuristics (for example, checking if the AI-generated output matches the ideal answer when the output is as simple as High, Medium, or Low). Other grading can't use heuristics: for example, deciding whether "Your ticket has been escalated" and "This ticket has been escalated" mean the same thing. LLM-as-a-judge lets you evaluate AI outputs at scale without requiring many human reviewers. It mimics how a human assesses quality: not perfect, but fast, consistent, and useful for comparing different versions of your model or prompt. In the example above, you could write an LLM-as-a-judge prompt such as "Score how close this answer is to the ideal one on a scale of 0 to 1.0". ================================================================================ # Prompts & Graders URL: https://docs.statsig.com/ai-evals/prompts Updated: 2026-07-11 ================================================================================ ## What is a prompt in Statsig? A Prompt represents an LLM prompt or task in Statsig, along with its config. Prompts are similar to Dynamic Configs and let you evaluate and roll out prompts in production without deploying code. Use the Statsig [Node](/server-core/node-core#getting-a-prompt) or [Python](/server-core/python-core/#getting-a-prompt) Server Core SDKs to retrieve a prompt within your app at runtime. With Prompts, you can: * Manage prompt configuration outside of your application code. You can update the model, configuration, or prompt at runtime. * Allow teammates with access to Statsig to collaborate and iterate on prompts, benefiting from Statsig's production change control processes and versioning. * Add configuration for a new model or model provider and progressively shift production traffic to it while comparing costs, user satisfaction, or any metric of interest. * Support advanced use cases such as retrieval-augmented generation (RAG) and evaluation in production. {% figure caption="Creating a prompt" %} ![Prompt creation screen showing model selection and message list](/images/ai-evals/prompts/77256d12-0d3f-4caa-8fa7-3719517af4eb.png) {% /figure %} {% figure caption="Code snippet to retrieve the Live version of the prompt" %} ![Code example calling statsigAI.getPrompt to fetch live prompt version](/images/ai-evals/prompts/66e3b7f6-4613-4d7c-84c7-0fd84f890a29.png) {% /figure %} {% figure caption="Looking at the scores for a prompt version" %} ![Prompt results page summarizing grader scores per version](/images/ai-evals/prompts/546c4974-b01d-4d13-85a1-4f2a6bfbb6c1.png) {% /figure %} ## What is a grader? A grader is the evaluation component that scores or judges the output of an AI system against a target standard. The grader is the core evaluation unit in the workflow: * **Inputs:** The grader takes in the AI model’s response and, when available, the ideal or ground-truth answer. * **Process:** The grader applies a scoring method. This can be rule-based (exact string match, regex check, cosine similarity) or LLM-as-a-judge (using another model to evaluate correctness, relevance, style, or safety). * **Outputs:** The grader produces a score, ideally 0 (Fail) or 1 (Pass). This score feeds into the Statsig experiment or eval framework to determine performance across datasets, experiments, or model versions. ## What is a critical grader? A critical grader is a must-pass evaluation in Statsig AI Evals. If the AI output fails this grader, the entire run is marked as failed. A critical grader enforces non-negotiable requirements, acting as a hard gate before results are considered valid. When it doesn't fail, it acts like a normal grader. ### Use Case For example, in a financial support chatbot, a critical grader could check that the model never fabricates account balances. Even if the response is otherwise helpful, a single failure blocks the model from being promoted. ================================================================================ # Offline Evals URL: https://docs.statsig.com/ai-evals/offline-evals Updated: 2026-07-11 ================================================================================ ## What are offline evals Offline evals provide quick, automated grading of model outputs on a fixed test set. They catch wins and regressions before any real users are exposed. For example, compare a new support bot's replies to human-curated answers to decide if the bot is ready to ship. Steps to run offline evals on Statsig: 1. Create a Prompt that contains the instruction for your task (for example, "Classify tickets as high, medium, or low urgency based on ticket text"). 2. Upload a sample dataset with example inputs and ideal answers (for example, Ticket1 text to High; Ticket2 text to Low). 3. Run your AI on the dataset to produce output (for example, classify each ticket). 4. Grade or score the outputs by comparing the ideal answers in the dataset with the AI-generated output. 5. Create multiple versions of your prompts, compare scores across versions, and promote the best one to Live. ## Create/analyze an offline eval in 10 minutes **1. Create a Prompt within Statsig** This captures the instruction you provide to an LLM to accomplish your task. Use the Statsig [Node](/server-core/node-core#getting-a-prompt) or [Python](/server-core/python-core/#getting-a-prompt) Server Core SDKs to retrieve this prompt within your app. You can create multiple versions of the prompt as you iterate and choose which one is "live" (retrieved by the SDK). ![Statsig prompt editor listing live and candidate versions with messages](/images/ai-evals/offline-evals/a17b3c4d-2126-4dfe-8d4b-d40b1838f878.png) **2. Create a dataset you can use to evaluate LLM completions for your prompt** For the example above, this might be a list of words alongside known good translations in French. Small lists can be entered manually, or you can upload a CSV. ![Dataset creation table with translation pairs for offline evaluation](/images/ai-evals/offline-evals/6d4b1abc-bde9-4d63-9d0c-95fef60b3f9a.png) **3. Create a grader that grades LLM completions for your prompt** Configure a grader that compares the LLM completion text with the reference output. Use one of the built-in string evaluators, or configure an LLM-as-a-judge evaluator that mimics a human's grading rubric. ![Grader configuration form comparing model output against reference answers](/images/ai-evals/offline-evals/3cd510f7-c267-4cdd-bebe-dbee527a5318.png) **3. Run evaluation** Run an evaluation on a version of the prompt. You should see results in a few minutes that look like this. You can click into any row of the dataset to understand more about the evaluation for that row. ![Offline evaluation results table showing prompt version scores](/images/ai-evals/offline-evals/c450f277-b2ba-4657-b747-440b43859f20.png) You can categorize your dataset, and break scores out by category. ![Category breakdown chart splitting evaluation scores by dataset segments](/images/ai-evals/offline-evals/3c0de7c4-6721-4a45-9a61-04a63db68913.png) If you have scores for multiple versions, you can compare them to see what changed between versions. ![Comparison view charting multiple prompt versions across graders](/images/ai-evals/offline-evals/fd593e52-ddec-4826-bf4b-c2ca1d43e4f0.png) ================================================================================ # Online Evals URL: https://docs.statsig.com/ai-evals/online-evals Updated: 2026-07-11 ================================================================================ ## What are online evals Online evals let you grade model output in production on real-world use cases. You can run the "live" version of a prompt and also shadow-run "candidate" versions without exposing users to them. Grading works directly on the model output and doesn't require a ground truth to compare against. Steps to run online evals in Statsig: 1. Create a Prompt that contains the instruction for your task (for example, "Summarize ticket content. Do not include email addresses or credit card numbers in the summary"). Create a v2 prompt that improves on this. 2. In your app, produce model output using the v1 and v2 prompts. The output from v1 is rendered to the user; the outputs from v1 and v2 are both judged by an LLM-as-a-judge. 3. Statsig logs the grades from v1 and v2 for comparison. {% callout type="info" %} Statsig isn't accepting new customers for AI Evals at this time. {% /callout %} ## Create/analyze an online eval in 15 minutes **1. Identify the prompts you want to serve** In Prompts, there are four prompt types: Live, Candidate, Draft and Archive. Before starting an online evaluation, it’s important to organize your prompt versions into these categories: * **Live** prompt is the version actively served to users. * **Candidate** prompts aren’t shown to users but are still served to your code. Statsig still processes the user’s input against them and logs and grades their outputs alongside the live version. * **Draft** prompts are the offline prompts you iterate on in the console, before deciding that you want to serve them. To start serving them, promote them to "Candidate" or "Live." * **Archive** prompts are inactive versions that you don't iterate on, kept offline. Prompts that you can access in code comprise the Live version and Candidate versions. ![Prompt versions list showing live, candidate, draft, and archived prompts with setup form](/images/ai/version-types.png) **2. Load your prompts in code and run completions on user input** The example below shows how to integrate prompts in your application using the [Statsig AI SDKs](/ai-evals/node). After you retrieve your Live or Candidate prompts, pass in the appropriate values to replace the macros in your prompt (`{{input}}` should be replaced by the user input). Then run completions on each of these prompts. ```js const prompts = statsigAI.getPrompt(user, "ai_config_name"); // get the live prompt const livePrompt = prompts.getLive(); // get the candidate prompts const candidatePrompts = prompts.getCandidates(); // get the live prompt messages const livePromptMessages = livePrompt.getPromptMessages({ input: userInput }); // run completions on your live prompt and show the output to the user const liveOutput = client.completions.create( my_model, livePromptMessages, (temperature = livePrompt.getTemperature()) ); // simulateneously run completions on the candidate prompts to get their output ``` **3. Score your output using graders** After you have a completion’s output, evaluate it using a grader: either one created in Statsig or a custom grader of your choice. The resulting score must always fall within the range of 0 to 1. **4. Log eval results to Statsig** Log your scores as events in Statsig to view the results in your console. ```js // Log the results of the eval statsigAI.logEvalGrade(user, livePromptVersion, 0.5, "my_grader", { session_id: "1234567890", }); ``` **5. View results in Statsig** View these results in Statsig. Select the version you want to evaluate and the versions to compare it against. This end-to-end online eval helps you iterate on your prompts and gain insights. ![Online eval results dashboard comparing prompt versions with cumulative events chart and grader deltas](/images/ai/online-results.png) ================================================================================ # Statsig Visual Editor (Low-code Experiments) URL: https://docs.statsig.com/guides/sidecar-experiments/introduction Updated: 2026-07-11 ================================================================================ ## How A/B testing works A/B testing, or split testing, is a fundamental method in digital marketing for validating website changes. It involves comparing two versions of a webpage to see which performs better with a target audience. Traditionally, implementing A/B tests required substantial technical skills, often requiring collaboration between marketers and developers, leading to longer implementation times and reduced agility for marketing teams. ## Statsig's visual editor Statsig's Visual Editor lets marketers run A/B tests with minimal technical dependency. It provides an intuitive, point-and-click interface for setting up and managing A/B tests on your website. You can experiment on styling, text content, calls to action, and injected scripts that change page behavior. Combined with Statsig's [industry-leading stats engine](/experiments/overview), Sidecar is a useful tool in a marketer's toolkit. {% figure %} ![Statsig sidecar](/images/guides/sidecar-experiments/introduction/85a65ae3-6651-4d5b-87c0-4dadee6c8c30.png) {% /figure %} {% callout type="info" %} This guide assumes you have an existing Statsig account. Go here to create a new free account if you don't already have one: https://statsig.com/signup {% /callout %} ## Performance considerations Sidecar, like any client-side tooling, introduces some degree of page load latency. Test Sidecar and verify that the performance impact falls within your acceptable threshold. Performance-sensitive customers typically use our [JS-SDK](/client/javascript-sdk) for web testing. When using the JS/React SDKs with [Client Bootstrapping](/client/concepts/initialize), the latency is minimal because no network requests are required for initialization. ================================================================================ # Visual Editor Setup & Usage URL: https://docs.statsig.com/guides/sidecar-experiments/sidecar-v3 Updated: 2026-07-11 ================================================================================ ## How Visual Editor v3 works Visual Editor v3 provides a simpler interface for point-and-shoot experiments. Unlike previous versions, you create experiments in the Statsig Console and edit them directly on top of the page. Sidecar still relies on a JavaScript script tag, though the package name has changed. {% callout type="info" %} Sidecar v3 is in Early Access. Try the product and share any feedback in the [Slack Community](https://statsig.com/slack). {% /callout %} ## Prerequisites ### 1. Installing the Sidecar script For experiments to take effect, the visual editor ("sidecar") script must run on your website, on any page you want to experiment on: ```html <script src="https://cdn.jsdelivr.net/npm/statsig-sidecar-v2-beta/dist/index.min.js?apikey=client-key" ></script> ``` Replace `client-key` with a client key from your Statsig project. You can find it at [Settings > Keys & Environments](https://console.statsig.com/api_keys). Most website builders also support the ability to add script tags on your website, like: [Webflow](https://university.webflow.com/lesson/custom-code-in-the-head-and-body-tags?topics=site-settings), [Wordpress](https://wordpress.com/go/website-building/how-to-properly-add-javascript-to-wordpress-3-top-methods/), [Wix](https://support.wix.com/en/article/embedding-custom-code-on-your-site), [Squarespace](https://support.squarespace.com/hc/en-us/articles/205815928-Adding-custom-code-to-your-site), [Weebly](https://weeblytutorials.com/embed-javascript-weebly). ### 2. Installing the Chrome extension (for editing) To create edits for experiment variants, install the Statsig Visual Editor [Chrome Extension](https://chromewebstore.google.com/detail/statsig-sidecar-v3/mmgjfcbidnlghegclgpkgegpdhbopjhn). You also need to be a project admin or have permissions to access console API keys to use all Sidecar functionality. ## Creating an experiment Create experiments in the console by opening the Create Experiment dialog and changing the experiment type to "Visual Editor". ![Create Visual Editor Experiment](/images/sidecarcreation.gif) ## Setting up an experiment ### Metrics When you add the Statsig Visual Editor script to your website, Statsig automatically tracks events like clicks, page views, Core Web Vitals, and more. These are called [autocapture](/webanalytics/autocapture/) metrics. Go to [Metrics](/metrics/101) for more information. You can add any of these metrics to your experiment, or customize them to be filtered to certain attributes (e.g. clicking a certain button, visiting a certain page) by creating a new metric in the [Metrics Catalog](https://console.statsig.com/metrics/metrics_catalog) tab. You must add at least one metric to your experiment before continuing. ### URL filters To limit an experiment to a subset of pages on your website, configure which URLs the experiment runs on: * **All Pages**: anywhere the Sidecar client is installed * **Contains**: The page URL must contain the value entered, for example "pricing" * **Exact Match**: The page URL must match the exact value specified here. * **Regex**: Regular expressions, for example `(http|https):\/\/www.statsig.com\/pricing` matches pages `http://www.statsig.com/pricing` or \`https://www.statsig.com/ ![URL Targeting](/images/sidecarv3urlfilters.png) ### Audiences Statsig infers information about each user, including country, device type, browser type, and a unique identifier called StableID. You can target based on any of these attributes using the "audiences" targeting section. You can also target custom attributes added to the Window.statsigUser object, though this requires a small amount of code. ### Visual Editor starting URL Before editing experiment variants, add a Visual Editor URL. The editor opens at this URL when you begin editing. Add a URL starting with "https://", then click **Save**. To set up an experiment, enter your metrics and any targeting (on URL, or other user attributes per the [StatsigUser object](/sdks/user)), and the starting URL. The URL must begin with "https://". Click **Save**, then click **Open in Editor**. An editor bar appears along the bottom of the page: ![Visual Editor Page](/images/sidecarv3openxp.gif) ### Runtime settings For more advanced experiment flows, you can also configure runtime settings on the experiment setup page before opening the editor. * **Disable Auto Run** prevents the experiment from applying automatically on page load. * **Prerun Script** lets you run custom JavaScript before the experiment starts. This is useful for SPAs or other advanced flows where you need to bind listeners or evaluate custom logic before manually activating the experiment with `StatsigSidecar.activateExperiment("experiment_name")`. Go to [Advanced Configurations](/guides/sidecar-experiments/advanced-configurations-v3) for more detail on runtime settings, manual activation, and prerun scripts. ## Making your edits Click **Element selector** or press command + E to enable the element editor. The editor provides a preset set of editable attributes depending on the selected element, for example: * *Text:* Text, Font attributes * *Button:* Text, target link * *Image:* Image source All elements also support rearranging or hiding attributes. ![Visual Editor Editor](/images/sidecarv3editor.png) ## Starting your experiment Click **Save** in the bottom-right of the visual editor to save changes to the console. Return to the console page and refresh to see your changes listed. Preview the experiment by clicking the three dots in the variant table. When ready, launch the experiment by clicking **Start** in the top-right corner. After starting, the experiment appears for end users and Statsig collects metrics. ### Advanced script setup Add these query string parameters to the Sidecar script URL for additional controls over Sidecar client behavior. * `&reduceflicker=0` disables the brief hiding of the `<body>` tag while the client initializes. ## Advanced configurations For more advanced Sidecar controls after the basic setup is working: * Go to [Advanced Configurations](/guides/sidecar-experiments/advanced-configurations-v3) for runtime settings, manual activation, prerun scripts, script URL tuning, consent, and identity configuration. * Go to [Measuring Experiments](/guides/sidecar-experiments/measuring-experiments#per-assignment-callback-for-outbound-integrations) for outbound analytics callbacks and other measurement patterns. ================================================================================ # Measuring Experiments URL: https://docs.statsig.com/guides/sidecar-experiments/measuring-experiments Updated: 2026-07-11 ================================================================================ ## Using Autocapture Sidecar automatically tracks various web activities, letting you create simple and complex metrics in the Statsig console without writing code. Create a new metric in the Metrics tab on the Statsig console to get started. For a full list of automatically logged metrics, refer to [Autocapture on the Web](/webanalytics/autocapture). ## Using the tracking API You can track events manually for actions not captured by Autocapture. To track events to Statsig, call `StatsigSidecar.logEvent`, which accepts the same arguments as the Statsig JS SDK as documented in [Logging an event](/client/javascript-sdk#logging-an-event). You can call this method before the init routine completes. ```js // example order event StatsigSidecar.logEvent('Order', null, { total: 54.66, units: 3, unitAvgCost: 18.22 }); ``` ## Per-assignment callback for outbound integrations You can bind a callback that is invoked each time Sidecar activates an experiment assignment, including experiments activated later by prerun scripts. *This method must be defined before the Sidecar client script.* ```js window.statsigSidecarConfig = { onExperimentEvaluation: function (event) { /** * add your own callback routine here * ie; annotating 3rd party analytics tools with assignment info */ window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "statsig_experiment_evaluation", experiment_name: event.experimentName, experiment_group_name: event.groupName, }); } } ``` The callback payload includes: * `event.name` - always `"experiment_evaluation"` * `event.experiment` - the full Statsig experiment object * `event.experimentName` - the Sidecar experiment name, or the Statsig experiment name as a fallback * `event.groupName` - the assigned group / variant, or `null` if unavailable ## Post-experiment callback for one-time readiness hooks You can bind `window.postExperimentCallback` to receive a callback after Sidecar finishes its initial run. This callback fires even when there are no experiments, but doesn't cover experiments activated later by prerun scripts. ```js window.postExperimentCallback = function(statsigClient, experimentIds) { // One-time initialization hook after Sidecar finishes the initial run } ``` ### Disabling all logging To disable all logging to Statsig (both autocapture events and experiment exposure logging), append the following query string parameter to the Sidecar script URL: `&autostart=0`. This is useful for GDPR compliance. You can re-enable events later with `client.updateRuntimeOptions({disableLogging: false})`. ================================================================================ # Advanced Configurations URL: https://docs.statsig.com/guides/sidecar-experiments/advanced-configurations-v3 Updated: 2026-07-11 ================================================================================ ## Runtime settings for SPAs and manual activation Visual Editor v3 includes runtime settings that let you control when an experiment starts running on the page. Configure these settings on the experiment setup page in the Statsig Console. * **Disable Auto Run** prevents Sidecar from automatically attempting to run the experiment on page load. * **Custom activation** lets you start the experiment manually with `StatsigSidecar.activateExperiment("experiment_name")`. * **Prerun Script** lets you define a custom script that runs once before the experiment starts when the targeting rules pass. Use this to bind listeners or evaluate custom logic before manually activating the experiment. ## Advanced targeting and segmentation This section describes how to set user identity and attributes for Sidecar to enable more advanced targeting and results segmentation. By default, Sidecar and Autocapture use `stableID` (an auto-generated device-level GUID stored in the user's localStorage) for tracking. To enrich autocapture events with known user identities and attributes, define the following object before autocapture or Sidecar loads. Watch this [video tutorial](https://tinyurl.com/sidecar-targeting) on how to configure more advanced targeting for your Sidecar experiments. ```js window.statsigUser = { userID: "<USER ID>", custom: { // optional attributes object isLoggedIn: false } } ``` ## Accessing the Statsig JS client To access the underlying Statsig js client instance, call `StatsigSidecar.getStatsigInstance()`. ## Configuring runtime options Use runtime options to handle consent management, GDPR compliance, and more. All [StatsigOptions](/client/javascript-sdk/#statsig-options) provided by the JavaScript SDK are fully supported with Sidecar. Pass them to Sidecar using: ```js window.statsigOptions = { // example of disabling logging for loggingEnabled: 'disabled' } ``` ## Managing Consent Before the Sidecar script tag, configure these runtime options to disable browser storage and tracking: ```js window.statsigOptions = { loggingEnabled: "disabled", disableStorage: true } ``` After the user gives consent, re-enable storage and tracking: ```js __STATSIG__.instance().updateRuntimeOptions({loggingEnabled: "browser-only", disableStorage: false}); ``` ## Persisting stableID across subdomains Statsig uses `localStorage` as the preferred mechanism for storing the user's stableID. localStorage keys don't persist across origin boundaries, including subdomains. For example, a user visiting `https://example.com`, `https://show.example.com`, and `https://account.example.com` receives three distinct stableIDs. If you assign a user to a test on one subdomain and track behavior for metrics on a different subdomain, add the following solution to ensure Statsig correctly attributes cross-origin behavior to the Test Group assignment from the initial experiment domain. Paste the following in your HEAD section. ```html <!-- cross domain id script --> <script>!function(){let t="STATSIG_LOCAL_STORAGE_STABLE_ID";function e(){if(crypto&&crypto.randomUUID)return crypto.randomUUID();let t=()=>Math.floor(65536*Math.random()).toString(16).padStart(4,"0");return`$\{t()\}${t()}-$\{t()\}-4${t().substring(1)}-$\{t()\}-${t()}$\{t()\}${t()}`}let i=null,n=localStorage.getItem(t)||null;if(document.cookie.match(/statsiguuid=([\w-]+);?/)&&([,i]=document.cookie.match(/statsiguuid=([\w-]+);?/)),i&&n&&i===n);else if(i&&n&&i!==n)localStorage.setItem(t,i);else if(i&&!n)localStorage.setItem(t,i);else{let o=e();localStorage.setItem(t,o),function t(i){let n=new Date;n.setMonth(n.getMonth()+12);let o=window.location.host.split(".");o.length>2&&o.shift();let s=`.$\{o.join(".")\}`;document.cookie=`statsiguuid=${i||e()};Expires=$\{n\};Domain=${s};Path=/`}(o)}}();</script> <!-- Manually attach stableID from local storage --> <script> if(localStorage.getItem('STATSIG_LOCAL_STORAGE_STABLE_ID')) { window.statsigUser = { customIDs: {stableID: localStorage.getItem('STATSIG_LOCAL_STORAGE_STABLE_ID')} } } </script> <!-- sidecar script below --> <script src="https://cdn.jsdelivr.net/npm/statsig-sidecar/dist/index.min.js?apikey=[client-YOUR-STATSIG-CLIENT-KEY]"></script> ``` ================================================================================ # Setting up Sidecar URL: https://docs.statsig.com/guides/sidecar-experiments/setup Updated: 2026-07-11 ================================================================================ {% callout type="info" %} * This guide assumes you have an existing Statsig account. Go here to create a new free account if you don't already have one: https://statsig.com/signup * You need to use the Google Chrome web browser for this exercise. {% /callout %} ## Set up Sidecar Chrome extension ### Step 1: Install Chrome extension If you don't already have the Sidecar extension, visit the [Chrome Web Store](https://chromewebstore.google.com/detail/statsig-sidecar/blkgemeefnlkmicphlkodgdkhceibgcb) and select "Add to Chrome". {% figure %} ![Statsig Sidecar Extension in the Chrome Web Store](/images/guides/sidecar-experiments/setup/0fc7cf69-3668-4f1b-bdaf-c75a25ca96af.png) {% /figure %} ### Step 2: Activate the extension Select the Extensions toolbar button and choose "Statsig Sidecar" to activate the Sidecar extension. {% figure %} ![Chrome extensions menu with Statsig Sidecar option](/images/guides/sidecar-experiments/setup/e069d1f2-c437-4398-a381-681b76663178.png) {% /figure %} You now see an Experiment Config UI like this: {% figure %} ![Sidecar Empty Interface](/images/sidecarempty.png) {% /figure %} ### Step 3: Update settings Update the API keys in the Settings dialog for the extension to work. Open the Settings dialog from the "Settings" link in the top header. {% figure %} ![Sidecar Settings Dialog](/images/sidecarsettings.png) {% /figure %} Retrieve these keys from your Statsig project. Log in to the Statsig Console at https://console.statsig.com and navigate to Settings (https://console.statsig.com/settings). Select the "Keys & Environments" panel, then copy both the Console API Key and Client API Key and paste them into the Settings dialog. {% figure %} ![Statsig console Keys & Environments panel showing API keys](/images/guides/sidecar-experiments/setup/754cc245-1821-4f75-a87d-08b536422587.png) {% /figure %} Select "OK" to save the API Keys. ## Install Sidecar on your website Add a single script tag within the `<head>` portion of your website, replacing with your own [Client SDK Key](/access-management/api-keys) as shown below. ``` <script src="https://cdn.jsdelivr.net/npm/statsig-sidecar/dist/index.min.js?apikey=REPLACE_WITH_YOUR_CLIENT_SDK_KEY"></script> ``` {% callout type="info" %} Installing Sidecar JS through a Tag Manager can lead to flickering and other unpredictable behavior. Install Sidecar as a synchronous script tag instead. {% /callout %} ### Additional Options Add these query string parameters to the Sidecar script URL for additional controls over Sidecar client behavior * `&reduceflicker=0` will disable the brief hiding of the `<body>` tag while the client initializes * `&autocapture=0` will disable event autocapture Most website builders support adding script tags. Here are some common examples: [Webflow](https://university.webflow.com/lesson/custom-code-in-the-head-and-body-tags?topics=site-settings), [Wordpress](https://wordpress.com/go/website-building/how-to-properly-add-javascript-to-wordpress-3-top-methods/), [Wix](https://support.wix.com/en/article/embedding-custom-code-on-your-site), [Squarespace](https://support.squarespace.com/hc/en-us/articles/205815928-Adding-custom-code-to-your-site), [Weebly](https://weeblytutorials.com/embed-javascript-weebly). You can copy the script code from within the Sidecar Chrome extension. {% figure %} ![Sidecar Script Code](/images/sidecargetscript.png) {% /figure %} You are now ready to create your first experiment. ## Next up: [Creating Experiments](/guides/sidecar-experiments/creating-experiments) ================================================================================ # Creating Your First Experiment URL: https://docs.statsig.com/guides/sidecar-experiments/creating-experiments Updated: 2026-07-11 ================================================================================ Sidecar lets you create and run A/B experiments without writing code or pushing code to production. This guide shows how to create an experiment and review results. {% callout type="info" %} This guide assumes you have followed the previous steps of installing Sidecar, creating a Statsig account, and setting up the API Keys correctly. Refer to [Setup](/guides/sidecar-experiments/setup) for those instructions. {% /callout %} ## Step 1: Navigate to the web page Navigate to the web page you want to experiment on. {% figure %} ![Sidecar experiment interface showing the main dashboard](/images/sidecarfull.png) {% /figure %} ## Step 2: New experiment Select the *New Experiment* button and fill out the details. This creates a local experiment that has not been published yet, so you can configure all the details and verify that everything works before publishing. {% figure %} ![New experiment creation form](/images/guides/sidecar-experiments/creating-experiments/d8cfc8bb-43e9-4a64-8ca8-002c579e7fff.png) {% /figure %} ## Step 3 (Optional): Add URL filter You can select which pages the experiment runs on. URL filters are evaluated before any targeting rules configured on the experiment in the Statsig console. You can configure URL targeting using the following methods: * All Pages - anywhere Sidecar client is installed * Contains - The page URL must contain the value as a substring * Exact Match - The page URL must match the exact value specified here. * Regex - Regular expressions, for example `(http|https):\/\/www.statsig.com\/pricing` matches pages `http://www.statsig.com/pricing` or `https://www.statsig.com/pricing`, and will activate this experiment on those pages. {% figure %} ![URL targeting configuration interface with filter options](/images/sidecarurls.png) {% /figure %} ## Step 4: Add actions Select the *Add Action* button to see a list of actions available for this experiment. Choose *Change content of an element* to run an A/B test that changes the content of an HTML element, such as headlines, descriptions, or CTAs. {% figure %} ![Add action selection menu with available experiment actions](/images/sidecaraddaction.png) {% /figure %} ### Use the Redirect action for landing page and split URL experiments For Landing Page and Split URL experiments, add the "Redirect to another page" action for any test group and specify the destination URL. Query string parameters are preserved and passed to the destination URL. {% figure %} ![Redirect action configuration for landing page experiments](/images/sidecarredirect.png) {% /figure %} ## Step 5: Select an element To run a content change experiment, you need two things: the element to test, and the content to change. Select the yellow *Target element path* text box to activate element selector mode. Move your mouse over the web page to see a red selection rectangle. Select the element you want. In this example, the main Headline is selected. {% figure %} ![Element selection with red highlight showing target element](/images/sidecarselect.png) {% /figure %} Sidecar now reflects the path of the selected element. {% figure %} ![Selected element path display in the configuration panel](/images/sidecarpath.png) {% /figure %} ## Step 6: Update content Choose the two text values to A/B test. In the *Control content* text box, add your control text (for example, "Build Better Products"). In the *Test content* text box, add your test variant (for example, "Experiment Like a Pro"). You can validate these changes in real time by selecting the ▶ button above the text box for each variant. This immediately changes the element's content so you can visually inspect the result before publishing. {% figure %} ![Content update interface with preview functionality](/images/sidecarupdatelt.png) {% /figure %} ## Step 7: Add more actions You can add more actions within the same experiment to explore additional capabilities. {% figure %} ![Adding additional experiment actions to the same test](/images/sidecar2ndaction.png) {% /figure %} You have created your first no-code experiment. ## Next up: [Measuring Experiments](/guides/sidecar-experiments/measuring-experiments) ================================================================================ # Advanced Configurations URL: https://docs.statsig.com/guides/sidecar-experiments/advanced-configurations Updated: 2026-07-11 ================================================================================ ## Single Page App support Sidecar supports integration within Single Page Apps. This includes Sidecar configuration tools and SDK methods for defining custom trigger points, giving you full flexibility to control when an experiment runs. * **Disable Auto Run**: When selected, Sidecar doesn't automatically attempt to run the test on page load. * **Custom activation**: Use `StatsigSidecar.activateExperiment("<experiment_id>")` to activate an experiment manually. * **Prerun Script**: Define a custom script that runs only once per experiment if the URL filter passes. Use this to bind listeners or evaluate custom logic to control when to trigger the experiment using `activateExperiment`. {% figure %} ![Single Page App configuration settings in Sidecar](/images/sidecar-spa.png) {% /figure %} ## Advanced targeting and segmentation This section describes how to set user identity and attributes for Sidecar to enable more advanced targeting and results segmentation. By default, Sidecar and Autocapture use `stableID` (an auto-generated device-level GUID stored in the user's localStorage) for tracking. To enrich autocapture events with known user identities and attributes, define the following object before autocapture or Sidecar loads. Watch this [video tutorial](https://tinyurl.com/sidecar-targeting) on how to configure more advanced targeting for your Sidecar experiments. ```js window.statsigUser = { userID: "<USER ID>", custom: { // optional attributes object isLoggedIn: false } } ``` ## Accessing the Statsig JS client To access the underlying Statsig js client instance, call `StatsigSidecar.getStatsigInstance()`. ## Configuring runtime options Use runtime options to handle consent management, GDPR compliance, and more. All [StatsigOptions](/client/javascript-sdk/#statsig-options) provided by the JavaScript SDK are fully supported with Sidecar. Pass them to Sidecar using: ```js window.statsigOptions = { // example of disabling logging for loggingEnabled: 'disabled' } ``` ## Managing Consent Before the Sidecar script tag, configure these runtime options to disable browser storage and tracking: ```js window.statsigOptions = { loggingEnabled: "disabled", disableStorage: true } ``` After the user gives consent, re-enable storage and tracking: ```js __STATSIG__.instance().updateRuntimeOptions({loggingEnabled: "browser-only", disableStorage: false}); ``` ## Persisting stableID across subdomains Statsig uses `localStorage` as the preferred mechanism for storing the user's stableID. localStorage keys don't persist across origin boundaries, including subdomains. For example, a user visiting `https://example.com`, `https://show.example.com`, and `https://account.example.com` receives three distinct stableIDs. If you assign a user to a test on one subdomain and track behavior for metrics on a different subdomain, add the following solution to ensure Statsig correctly attributes cross-origin behavior to the Test Group assignment from the initial experiment domain. Paste the following in your HEAD section. ```html <!-- cross domain id script --> <script>!function(){let t="STATSIG_LOCAL_STORAGE_STABLE_ID";function e(){if(crypto&&crypto.randomUUID)return crypto.randomUUID();let t=()=>Math.floor(65536*Math.random()).toString(16).padStart(4,"0");return`$\{t()\}${t()}-$\{t()\}-4${t().substring(1)}-$\{t()\}-${t()}$\{t()\}${t()}`}let i=null,n=localStorage.getItem(t)||null;if(document.cookie.match(/statsiguuid=([\w-]+);?/)&&([,i]=document.cookie.match(/statsiguuid=([\w-]+);?/)),i&&n&&i===n);else if(i&&n&&i!==n)localStorage.setItem(t,i);else if(i&&!n)localStorage.setItem(t,i);else{let o=e();localStorage.setItem(t,o),function t(i){let n=new Date;n.setMonth(n.getMonth()+12);let o=window.location.host.split(".");o.length>2&&o.shift();let s=`.$\{o.join(".")\}`;document.cookie=`statsiguuid=${i||e()};Expires=$\{n\};Domain=${s};Path=/`}(o)}}();</script> <!-- Manually attach stableID from local storage --> <script> if(localStorage.getItem('STATSIG_LOCAL_STORAGE_STABLE_ID')) { window.statsigUser = { customIDs: {stableID: localStorage.getItem('STATSIG_LOCAL_STORAGE_STABLE_ID')} } } </script> <!-- sidecar script below --> <script src="https://cdn.jsdelivr.net/npm/statsig-sidecar/dist/index.min.js?apikey=[client-YOUR-STATSIG-CLIENT-KEY]"></script> ``` ================================================================================ # Taking your experiments to production URL: https://docs.statsig.com/guides/sidecar-experiments/publishing-experiments Updated: 2026-07-11 ================================================================================ After completing the experiment configuration, publish and launch it in production. Sidecar makes this process straightforward with a few steps. {% callout type="info" %} This guide assumes you have followed the previous steps of creating an experiment in Sidecar. Check out [Creating Experiments](/guides/sidecar-experiments/creating-experiments) for those instructions. {% /callout %} ## Step 1: Publish the experiments When you are satisfied with the experiment configuration, select the blue _Publish_ button. This saves all configurations to Statsig. To verify the changes were stored successfully, select the `...` menu and choose _Go to Experiment Console_. Publishing changes doesn't start any experiments; it does the following: - Sync any unsaved changes to Statsig (making them accessible in *Console* where you can configure metrics and other targeting conditions if applicable). - Include any configured tests in the Sidecar script installed on your website. - Allow you to QA experiments on your site while they're in an unstarted state. {% figure %} ![Statsig experiment console interface](/images/sidecarconsole.png) {% /figure %} The experiment console lets you configure targeting, metrics, and advanced statistical settings. {% figure %} <img src="/images/guides/sidecar-experiments/publishing-experiments/22f57816-6fdd-422c-87a9-90a5c08f36a5.png" alt="Statsig experiment setup tab with checklist and scorecard fields" width="50%" /> {% /figure %} ## Step 2: Preview and QA the experiment At this point, your experiment is in a pre-started state, meaning it isn't yet active to site visitors. To preview a specific variant, pass a query string to your test page URL using the `overrideuser` parameter. The override method uses the following convention to force a test & test group:<br /> `https://www.DOMAIN.com/?overrideuser=<EXPERIMENT_ID>_<TEST_GROUP_ID>` The image below shows where to find the experiment ID and each variation ID. Based on this example, force a preview of the Test Group by visiting: `https://www.DOMAIN.com/?overrideuser=name_color_test_1` {% figure %} ![Sidecar experiment QA and preview interface](/images/sidecarqa.png) {% /figure %} {% callout type="note" %} This works best with the default test/control group names. If you change a group name, you must also update it in the Statsig Console by selecting "Manage Overrides". {% /callout %} ## Step 3: Start the experiment Refresh the page in your browser with the script embedded. Sidecar automatically detects the published experiment and displays all experiment properties. Start the experiment by selecting the `...` menu and choosing _Start Experiment_. Sidecar then starts the experiment, serves the correct variants for control and test groups, and begins collecting metrics. {% figure %} ![Sidecar experiment start interface](/images/sidecarstartexp.png) {% /figure %} You have successfully built and shipped an experiment. ================================================================================ # Integrating Sidecar with GTM for tracking URL: https://docs.statsig.com/guides/sidecar-experiments/integrating-gtm Updated: 2026-07-11 ================================================================================ {% callout type="info" %} This integration is for tracking purposes only. Avoid loading Sidecar itself through GTM, as this delays the changes from being applied and results in "flickering". {% /callout %} ## How GTM integration works This integration automatically sends GTM-tagged events to Statsig. No additional coding or tagging is required after completing these steps. {% figure %} ![Statsig logstream showing GTM events flowing in](/images/gtm-logstream.png) {% /figure %} *(statsig logstream showing gtm events flowing in)* ## Step 1: Create new tag {% figure %} ![GTM create new tag interface for Statsig integration](/images/gtm-create-tag.png) {% /figure %} ## Step 2: Choose tag type Choose "Custom HTML" for tag type, and paste [this GTM code](#gtm-code) (including script tag) {% figure %} ![GTM tag configuration screen with custom HTML setup](/images/gtm-tag-configuration.png) {% /figure %} ## Step 3: Adjust fire options Under Advanced Settings under "Tag Firing options", select "Once per page" {% figure %} ![GTM tag firing options settings for once per page](/images/gtm-create-tag-options.png) {% /figure %} ## Step 4: Set tag trigger Below the "Tag Configuration" section, set the Trigger to "Initialization - All Pages" Option. {% figure %} ![GTM tag trigger configuration for all pages initialization](/images/gtm-tag-trigger.png) {% /figure %} ## Step 5: Save tag and test After saving and publishing your updated GTM tag, tracking runs automatically without any additional configuration. To debug the integration, set a local storage entry `debug_ss_gtm` with any value on your webpage. This produces console log statements for each tracking call dispatched to Statsig. You can also inspect your browser's network traffic to see events being tracked. ## GTM code ```html <script type="text/javascript"> /* dataLayer helper */ (function(){ var f=/\[object (Boolean|Number|String|Function|Array|Date|RegExp|Arguments)\]/;function g(a){return null==a?String(a):(a=f.exec(Object.prototype.toString.call(Object(a))))?a[1].toLowerCase():"object"}function m(a,b){return Object.prototype.hasOwnProperty.call(Object(a),b)}function n(a){if(!a||"object"!=g(a)||a.nodeType||a==a.window)return!1;try{if(a.constructor&&!m(a,"constructor")&&!m(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}for(var b in a);return void 0===b||m(a,b)};function p(a,b){var c={},d=c;a=a.split(".");for(var e=0;e<a.length-1;e++)d=d[a[e]]={};d[a[a.length-1]]=b;return c}function q(a,b){var c=!a._clear,d;for(d in a)if(m(a,d)){var e=a[d];"array"===g(e)&&c?("array"===g(b[d])||(b[d]=[]),q(e,b[d])):n(e)&&c?(n(b[d])||(b[d]={}),q(e,b[d])):b[d]=e}delete b._clear}; function r(a,b,c){b=void 0===b?{}:b;"function"===typeof b?b={listener:b,listenToPast:void 0===c?!1:c,processNow:!0,commandProcessors:{}}:b={listener:b.listener||function(){},listenToPast:b.listenToPast||!1,processNow:void 0===b.processNow?!0:b.processNow,commandProcessors:b.commandProcessors||{}};this.a=a;this.l=b.listener;this.j=b.listenToPast;this.g=this.i=!1;this.c={};this.f=[];this.b=b.commandProcessors;this.h=u(this);var d=this.a.push,e=this;this.a.push=function(){var k=[].slice.call(arguments, 0),l=d.apply(e.a,k);v(e,k);return l};b.processNow&&this.process()}r.prototype.process=function(){this.registerProcessor("set",function(){var c={};1===arguments.length&&"object"===g(arguments[0])?c=arguments[0]:2===arguments.length&&"string"===g(arguments[0])&&(c=p(arguments[0],arguments[1]));return c});this.i=!0;for(var a=this.a.length,b=0;b<a;b++)v(this,[this.a[b]],!this.j)};r.prototype.get=function(a){var b=this.c;a=a.split(".");for(var c=0;c<a.length;c++){if(void 0===b[a[c]])return;b=b[a[c]]}return b}; r.prototype.flatten=function(){this.a.splice(0,this.a.length);this.a[0]={};q(this.c,this.a[0])};r.prototype.registerProcessor=function(a,b){a in this.b||(this.b[a]=[]);this.b[a].push(b)}; function v(a,b,c){c=void 0===c?!1:c;if(a.i&&(a.f.push.apply(a.f,b),!a.g))for(;0<a.f.length;){b=a.f.shift();if("array"===g(b))a:{var d=a.c;g(b[0]);for(var e=b[0].split("."),k=e.pop(),l=b.slice(1),h=0;h<e.length;h++){if(void 0===d[e[h]])break a;d=d[e[h]]}try{d[k].apply(d,l)}catch(w){}}else if("arguments"===g(b)){e=a;k=[];l=b[0];if(e.b[l])for(d=e.b[l].length,h=0;h<d;h++)k.push(e.b[l][h].apply(e.h,[].slice.call(b,1)));a.f.push.apply(a.f,k)}else if("function"==typeof b)try{b.call(a.h)}catch(w){}else if(n(b))for(var t in b)q(p(t, b[t]),a.c);else continue;c||(a.g=!0,a.l(a.c,b),a.g=!1)}}r.prototype.registerProcessor=r.prototype.registerProcessor;r.prototype.flatten=r.prototype.flatten;r.prototype.get=r.prototype.get;r.prototype.process=r.prototype.process;window.DataLayerHelper=r;function u(a){return{set:function(b,c){q(p(b,c),a.c)},get:function(b){return a.get(b)}}};})(); window.StatsigLogger = (function () { var statsigInstance, log = function () { if (typeof localStorage !== 'undefined' && typeof console !== 'undefined' && localStorage.getItem('debug_ss_gtm')) { console.log.apply(console, arguments); } }; var checkStatsigReady = function (callback) { // statsig has already loaded if(typeof StatsigSidecar !== 'undefined' && StatsigSidecar.getStatsigInstance()) { log('+++ Statsig:ready Already loaded'); callback(StatsigSidecar.getStatsigInstance()); } else { log('+++ Statsig:ready callback'); window.postExperimentCallback = function(statsigClient, expIds) { callback(statsigClient); } } }; var handleGTMMessage = function (model, message) { if (typeof message === 'object' && typeof message.event === 'string') { var metadata = {}; for (var prop in message) { if(!(message[prop] instanceof HTMLElement) && typeof(message[prop]) !== 'object') { metadata[prop] = message[prop]; } } log('++ handleGTMMessage', message.event, message.conversionValue || null, metadata); statsigInstance.logEvent(message.event, message.conversionValue || null, metadata); } else { log('++ handleGTMMessage / skip'); } } checkStatsigReady(function (statsigInstanceReady) { statsigInstance = statsigInstanceReady; // dataLayer will contain gtm events (~messages) that were fired pre statsig readiness new DataLayerHelper(dataLayer, { listener: handleGTMMessage, listenToPast: true, }); }); return { track: function() { try { statsigInstance.logEvent.apply(statsigInstance, arguments); } catch(err) { } } } })(); </script> ``` ================================================================================ # Running an A/A Test using Sidecar URL: https://docs.statsig.com/guides/aa-sidecar Updated: 2026-07-11 ================================================================================ This guide explains how to use Statsig’s Sidecar to run an A/A test on your product. {% callout type=”info” %} This guide assumes you have successfully set up and configured Statsig Sidecar. For a step-by-step guide, go to [“Setting up Sidecar”](/guides/sidecar-experiments/setup). {% /callout %} ## Why run an A/A test? The most common reason to run an A/A test is to validate a new experimentation engine you're integrating with, such as Statsig. For new users, running an A/A test first provides a low-stakes environment to confirm that your metrics are set up correctly and that exposures are flowing through as expected before you start your first real A/B test. ## How to run an A/A test ### Step 1: Create a new Experiment in Sidecar Navigate to the page on your website where you want to run an A/A Test. Open the Statsig Sidecar extension and select **New Experiment**. Fill in the title of your A/A test. {% figure %} ![Sidecar A/A test experiment setup interface](/images/sidecar_aa_setup.png) {% /figure %} Then, determine the URI filter (for example, All Pages, contains). After you configure the URI, set up the variants. For the variant 'Control', choose an action. In this example, the action changes the content of an element: specifically the title 'Getting Started is Simple'. {% figure %} ![Sidecar A/A test variant configuration screen](/images/sidecar_aa_select.png) {% /figure %} Repeat the step above for the variant 'Test', using the exact same action. Your setup should match the following. {% figure %} ![Sidecar A/A test final configuration showing identical variants](/images/sidecar_aa_final.png) {% /figure %} Select **Publish** to push the experiment to Statsig as a draft. ### Step 2: Configure experiment scorecard in Statsig console After Sidecar pushes the experiment to end users, edit the scorecard for your experiment in the console. Navigate to the console, select the **Experiments** tab, and open the experiment you just created. In the **Setup** tab, fill out the scorecard with the experiment hypothesis and any primary metrics you want to monitor. Statsig shows results for all your metrics, but these key metrics represent your hypothesis. Establishing a hypothesis upfront ensures the experiment improves your understanding of users, rather than providing data points to support a predetermined decision. In the Allocation and Targeting section, for an A/A test, allocate 100% of users to the experiment while targeting everyone. Save and push your experiment. Your test is now ready to start measuring metrics associated with the A/A test. ### Step 3: Review A/A test results Within 24 hours of starting your experiment, cumulative exposures appear in the **Pulse Results** tab of your experiment. This shows a breakdown of your logged exposures and their distribution. If something looks off, check the **Diagnostics** tab for more granular, day-by-day exposure breakdowns at both the Checks and User level. In the **Scorecard** panel, you can see the full picture of how all your tagged metrics perform. {% figure %} ![pulse_results_empty](/images/experiments/types/aa-test/163248267-7bd7419a-59e0-4d58-b8e5-8ace95ed74d9.png) {% /figure %} What to expect: * **Exposures:** Confirm that exposures are flowing through as expected from your product. If exposures are missing, use the **Diagnostics** tab and the **Exposure Stream** to debug. * **Pulse results:** About 5% of your metrics in Pulse should show a statistically significant change, because Statsig’s stats engine uses a 95% confidence interval. Run your A/A test long enough to reach most of your weekly active users, or at least one week. ================================================================================ # Session Replay Overview URL: https://docs.statsig.com/session-replay/overview Updated: 2026-07-11 ================================================================================ Session Replay records users interacting with your website or product and lets you play back those sessions. Use it to understand how users navigate your service, diagnose problems, and uncover insights that improve conversion and the overall user experience. A session recording plays back like a video in the Statsig Console. It is a serialized representation of your website and all events and interactions that occurred during the session. Statsig captures recordings using the [rrweb open source recording library](https://github.com/rrweb-io/rrweb). This approach is performant and space-efficient, with options to apply privacy filters to on-screen content. {% figure %} ![Session replay interface in Statsig console](/images/session_replay/overall_ui.png) {% /figure %} ================================================================================ # Install Statsig Session Replay URL: https://docs.statsig.com/session-replay/install Updated: 2026-07-11 ================================================================================ Statsig supports Session Replay on the Javascript and React SDKs for both desktop and mobile web users. Follow the instructions below to install the SDK and record user sessions. ## Option 1 - No code - Add Javascript snippet to your website ```html <script src="https://cdn.jsdelivr.net/npm/@statsig/js-client@3/build/statsig-js-client+session-replay+web-analytics.min.js?apikey=[YOUR_CLIENT_KEY]"></script> ``` Get YOUR\_CLIENT\_KEY from *Project Settings > Keys & Environments*. Reveal the Client API Key, copy, and paste it over the \[YOUR-API-KEY] in the snippet above. This auto-initializes the SDK and starts recording sessions, no code required. {% callout type="info" %} To use your existing Statsig integration or customize further, refer to option 2 below. To use the script tag with customization, remove your key from the script URL and initialize using the JavaScript code below. {% /callout %} ## Option 2 - Custom code - Install using a package manager {% tabs %} {% tab title="Javascript" %} {% codetabs %} ```bash npm npm install @statsig/js-client @statsig/session-replay @statsig/web-analytics ``` ```bash yarn yarn add @statsig/js-client @statsig/session-replay @statsig/web-analytics ``` {% /codetabs %} {% /tab %} {% tab title="React" %} {% codetabs %} ```bash npm npm install @statsig/session-replay @statsig/web-analytics @statsig/react-bindings ``` ```bash yarn yarn add @statsig/session-replay @statsig/web-analytics @statsig/react-bindings ``` {% /codetabs %} {% /tab %} {% /tabs %} Statsig recommends Autocapture for getting started quickly. To skip automatic event logging, remove the `runStatsigAutoCapture` option from the JavaScript snippet or skip the `@statsig/web-analytics` package installation. Next, following the [instructions for the Statsig Javascript SDK](/client/javascript-sdk), initialize Statsig with your SDK key, [user](/sdks/user) and options: {% tabs %} {% tab title="Javascript" %} ```jsx import { StatsigClient } from "@statsig/js-client"; import { runStatsigSessionReplay } from "@statsig/session-replay"; import { runStatsigAutoCapture } from "@statsig/web-analytics"; const client = new StatsigClient( sdkKey, { userID: "some_user_id" }, { environment: { tier: "production" } } // optional, pass options here if needed ); runStatsigSessionReplay(client); runStatsigAutoCapture(client); await client.initializeAsync(); ``` {% /tab %} {% tab title="React" %} ```jsx import { StatsigProvider, useClientAsyncInit } from "@statsig/react-bindings"; import { StatsigSessionReplayPlugin } from "@statsig/session-replay"; import { StatsigAutoCapturePlugin } from "@statsig/web-analytics"; function App() { return ( <StatsigProvider sdkKey={YOUR_CLIENT_KEY} user={{ userID: "a-user" }} loadingComponent={<div>Loading...</div>} options={{ plugins: [ new StatsigSessionReplayPlugin(), new StatsigAutoCapturePlugin(), ], }} > <Content /> </StatsigProvider> ); } ``` {% /tab %} {% /tabs %} {% callout type="info" %} To use Conditional Triggers, you must use `StatsigTriggeredSessionReplay`. Refer to Configure (next page) for more information. {% /callout %} Continue to Configure to learn more about controlling who, what, and when you record sessions. ================================================================================ # Configure Statsig Session Replay URL: https://docs.statsig.com/session-replay/configure Updated: 2026-07-11 ================================================================================ ## Conditional recording In the Statsig Console, you can configure your Session Replay settings under **Project Settings → Analytics & Session Replay**. You must be a project admin to modify these settings. ### Global targeting gate The Global Targeting Gate controls who is *eligible* for session recording. If a user doesn't pass this gate, Statsig never records their sessions. By default, this is set to Everyone, meaning there are no restrictions and Statsig can record anyone. This gate defines the upper bound of session recording eligibility. ### Global sampling rate The Global Sampling Rate determines what percentage of eligible sessions Statsig records from the start. By default, this is set to **100%**, meaning Statsig records all eligible sessions. You can lower this value to limit session recordings while still capturing a consistent percentage of sessions. This rate applies only to sessions that begin at session start and doesn't affect conditional triggers. ### Conditional triggers: events and exposures Conditional triggers can start a session recording mid-session, even if recording didn't begin at session start. These triggers respect the Global Targeting Gate but operate independently of the Global Sampling Rate. When a trigger fires, the recording includes the last 30 seconds leading up to the event, if you enable the rolling window. Types of conditional triggers: * **Individual Gate Exposures**: Trigger based on exposure to a specific gate, optionally filtered by group (for example, Pass/Fail). * **All Gate Exposures**: Trigger based on exposure to any gate, optionally filtered by group (for example, Pass/Fail). Individual Gate Exposure triggers override All Gate configuration. * **Individual Experiment Exposures**: Trigger based on exposure to a specific experiment, optionally filtered by group (for example, Test/Control). * **All Experiment Exposures**: Trigger based on exposure to any experiment, filtered by groups Test/Control. Statsig doesn't support other group names. If an experiment includes additional groups, configure individual triggers for each one. Individual Experiment Exposure triggers override All Experiment configuration. * **Events**: Trigger based on a specific logged event, optionally filtered by event values (for example, "purchase\_event" with value "book"). For each trigger, you can define an individual sampling rate. Statsig evaluates this rate based on `session_id`, so the result (pass or fail) remains consistent for the same session, even if the trigger occurs multiple times. **All Gates** and **All Experiments** conditional triggers are only available in `3.30.1` or higher. If a conditional trigger occurs while a session recording is already in progress, the recording continues uninterrupted. ![Session replay settings panel with targeting and sampling controls](/images/session_replay/settings.png) ![Flowchart outlining conditional recording logic for events and exposures](/images/session_replay/conditional_recording_flowchart.png) ### Example walkthrough Suppose you have the following setup (refer to the image above): * The Global Targeting Gate `session_replay_global_targeting_gate` allows all US users and excludes everyone else. * The Global Sampling Rate is set to 25%, so Statsig records only 25% of eligible US user sessions from the start. * For the remaining 75% of eligible users, session recording can still begin mid-session if a conditional trigger occurs. Example Scenario: 1. A US user starts a session. They don't pass the 25% Global Sampling Rate, so Statsig doesn't record their session from the beginning. 2. Later, a `purchase_event` occurs with value `book`. This event is set up as a conditional trigger with a 50% sampling rate. If this session fails the sampling rate check, recording doesn't start. 3. A minute later, the user encounters the `cool_new_feature` gate, and the recording begins. {% callout type="note" %} A trigger's sampling rate is consistent for the entire session based on session\_id. So if `purchase_event` fails the sampling rate once, future occurrences of the same event in that session will also fail. {% /callout %} ### Initialization: StatsigTriggeredSessionReplay {% tabs %} {% tab title="Javascript" %} ```jsx import { StatsigClient } from "@statsig/js-client"; import { runStatsigTriggeredSessionReplay } from "@statsig/session-replay"; import { runStatsigAutoCapture } from "@statsig/web-analytics"; const client = new StatsigClient( sdkKey, { userID: "some_user_id" }, { environment: { tier: "production" } } // optional, pass options here if needed ); runStatsigTriggeredSessionReplay(client, { autoStartRecording: true, keepRollingWindow: true, }); runStatsigAutoCapture(client); await client.initializeAsync(); ``` {% /tab %} {% tab title="React" %} ```jsx import { StatsigProvider, useClientAsyncInit } from "@statsig/react-bindings"; import { StatsigTriggeredSessionReplayPlugin } from "@statsig/session-replay"; import { StatsigAutoCapturePlugin } from "@statsig/web-analytics"; function App() { return ( <StatsigProvider sdkKey={YOUR_CLIENT_KEY} user={{ userID: "a-user" }} loadingComponent={<div>Loading...</div>} options={{ plugins: [ new StatsigTriggeredSessionReplayPlugin({ autoStartRecording: true, keepRollingWindow: true, }), new StatsigAutoCapturePlugin(), ], }} > <Content /> </StatsigProvider> ); } ``` {% /tab %} {% /tabs %} #### Initialization options * `autoStartRecording` * `true`: Recording *can* start automatically after initialization. Statsig respects the global targeting gate and sample rate. * `false`: You *must* manually start recording using startRecording(). This is helpful if you want to start the recording after a set point and block any auto-recording before then. * `keepRollingWindow` * `true`: Statsig maintains a local rolling window of the last 30 seconds of the session, allowing recordings to include context leading up to a trigger. * `false`: If a conditional trigger occurs, recording begins from that moment onward, with no historical context. {% callout type="warning" %} If you use bootstrapping, contact the Statsig team to confirm your server SDK supports conditional recording. {% /callout %} ## Advanced: forcing a recording on demand To manually start a recording, use the `startRecording` API, which begins recording immediately when called. * `startRecording`: Respects both the Global Targeting Gate and Global Sampling Rate. Use this if you want to start recordings after a certain point (for example, after login) while still applying the Global Sampling Rate. * `forceStartRecording`: Respects the Global Targeting Gate but ignores the Global Sampling Rate. Useful for debugging or when you want to bypass the Global Sampling Rate. * `stopRecording`: Stops the current recording, if one is in progress. Calling this method when no recording is active has no adverse effects. After you call stopRecording, conditional recording triggers don't automatically restart the recording. Only an explicit call to `startRecording` or `forceStartRecording` resumes recording. If you have access to your Session Replay client, you can call these functions directly on the client instance. ``` const sessionReplayClient = new SessionReplay(client); … if (someCondition) { sessionReplayClient.startRecording(); } ``` If you don't have access to the client instance, you can import the function from `@Statsig/session-replay` and call it using your SDK key. ``` import { startRecording } from '@Statsig/session-replay'; … startRecording(CLIENT_SDK_KEY) ``` ## Additional options These are options provided by the rrweb recorder (the open source recording tool used internally) | key | default | description | | ------------------------ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | blockClass | 'rr-block' | Use a string or RegExp to configure which elements should be blocked | | blockSelector | null | Use a string to configure which selector should be blocked | | ignoreClass | 'rr-ignore' | Use a string or RegExp to configure which elements should be ignored | | ignoreSelector | null | Use a string to configure which selector should be ignored | | ignoreCSSAttributes | null | array of CSS attributes that should be ignored | | maskTextClass | 'rr-mask' | Use a string or RegExp to configure which elements should be masked | | maskTextSelector | null | Use a string to configure which selector should be masked | | maskAllInputs | false | mask all input content as \* | | maskInputOptions | `{ password: true }` | mask some kinds of input \*. Refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L77-L95) | | maskInputFn | - | customize mask input content recording logic | | maskTextFn | - | customize mask text content recording logic | | slimDOMOptions | `{}` | remove unnecessary parts of the DOM. Refer to the [list](https://github.com/rrweb-io/rrweb/blob/588164aa12f1d94576f89ae0210b98f6e971c895/packages/rrweb-snapshot/src/types.ts#L97-L108) | | dataURLOptions | `{}` | Canvas image format and quality ,This parameter will be passed to the OffscreenCanvas.convertToBlob(),Using this parameter effectively reduces the size of the recorded data | | inlineStylesheet | true | whether to inline the stylesheet in the events | | hooks | `{}` | hooks for events. Refer to the [list](https://github.com/rrweb-io/rrweb/blob/9488deb6d54a5f04350c063d942da5e96ab74075/src/types.ts#L207) | | packFn | - | refer to the [storage optimization recipe](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/optimize-storage.md) | | sampling | - | refer to the [storage optimization recipe](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/optimize-storage.md) | | recordCanvas | false | Whether to record the canvas element. Available options: `false`, `true` | | recordCrossOriginIframes | false | Whether to record cross origin iframes. rrweb has to be injected in each child iframe for this to work. Available options: `false`, `true` | | recordAfter | 'load' | If the document is not ready, then the recorder will start recording after the specified event is fired. Available options: `DOMContentLoaded`, `load` | | inlineImages | false | whether to record the image content | | collectFonts | false | whether to collect fonts in the website | | userTriggeredOnInput | false | whether to add `userTriggered` on input events that indicates if this event was triggered directly by the user or not. [What is `userTriggered`?](https://github.com/rrweb-io/rrweb/pull/495) | | plugins | \[] | load plugins to provide extended record functions. [What is plugins?](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/plugin.md) | | errorHandler | - | A callback that is called if something inside of rrweb throws an error. The callback receives the error as argument. | ## Limits ### 4 hours per session or 30 min inactive time Sessions end after four hours total or if the user returns from inactive time greater than 30 minutes later. ### Recording limits | Tier | Monthly Limit | Daily Limit | Hourly Limit | | :--------- | ------------: | ----------: | -----------: | | Free | 50,000 | 3,500 | 3,500 | | Pro | 100,000 | 7,000 | 7,000 | | Enterprise | 100,000 | 7,000 | 7,000 | After you reach this limit, the SDK automatically prevents new recordings from starting. You can monitor your session replay usage in your project settings. [Contact us](https://statsig.com/contact/demo) for custom contracts. ### Replay availability time Statsig takes about 1 hour to process a recorded session before it appears in your console. ### Default 30 day retention Sessions have a default retention period of 30 days and Statsig automatically deletes them after that time. You can configure a shorter retention period in settings if needed. Reducing your retention period doesn't affect your monthly session replay limit and is typically done for privacy and compliance purposes. ================================================================================ # Privacy Options for Session Replay URL: https://docs.statsig.com/session-replay/privacy Updated: 2026-07-11 ================================================================================ To support your app’s privacy requirements and align with your organization’s policies, Statsig provides multiple ways to control replay privacy: * **Baseline privacy options** - Select from three preset privacy configurations. Each option applies a different level of text masking, helping you protect PII and sensitive data according to your app’s and users’ needs * **Fine-grained privacy controls** - After choosing a baseline configuration, use CSS selector rules to mask, reveal, or block specific elements * **Global Targeting Gate** - Use a feature gate to define which users are eligible for replays, ensuring you limit recordings to specific users or cohorts. In the Statsig Console, you can configure your privacy settings under **Project Settings → Analytics & Session Replay**. You must be a project admin to modify these settings. ### Baseline privacy options * **Passwords (Default)**: Statsig replaces only password inputs with asterisks (\*). All other text and inputs appear as-is. * **Inputs**: Statsig replaces all text in inputs with asterisks (\*). All other text appears as-is. * **Maximum**: Statsig replaces all text and inputs with asterisks (\*). ### Selector rules Use CSS selectors to precisely control how individual elements are handled during session replay: masked, unmasked, or blocked. * **Masking** and **unmasking** apply only to text content. Masked text is replaced with asterisks (\*). * **Blocking** removes the element entirely from the replay and replaces it with a black placeholder of the same size. * **You can't unmask password inputs**, regardless of selector rules. Selector rules override the baseline privacy settings. When multiple selector rules apply to the same element, Statsig enforces the following precedence: **Block → Mask → Unmask** The examples below show how precedence is enforced. ```js // Everything within the blocked class will // appear as a single black placeholder <div class="blocked"> <div id="masked">I will be part of the black placeholder</div> <button id="unmasked">I will be part of the black placeholder</button> </div> ``` ```js // The closest rule will apply <div> <div id="masked">Masked Text</div> <div id="masked"> <button id="unmasked">Unmasked Text</button> </div> </div> ``` ```js // With conflicting rules applied at the same level, // the higher precedence will apply <div> <button id="unmasked" className="masked"> Masked Text </button> </div> ``` ```js // With baseline privacy setting set to Maximum, all text is masked // by default but this can be overwritten by unmasking <div> <div>Masked Text</div> <div> <button id="unmasked">Unmasked Text</button> </div> </div> ``` All selectors must be valid CSS selectors. For details on supported selector syntax, refer to MDN’s list of CSS selectors. {% callout type="warning" %} Using selector rules or baseline privacy settings besides Passwords will overwrite the `maskTextFn`, `maskInputFn`, `maskTextSelector`, `maskAllInputs`, `maskInputOptions`, and `blockSelector` options you passed in during initialization. {% /callout %} ### Global targeting gate The Global Targeting Gate controls who is *eligible* for session recording. If a user doesn't pass this gate, Statsig never records their sessions. By default, this is set to Everyone, meaning there are no restrictions and Statsig can record anyone. This gate defines the upper bound of session recording eligibility. {% callout type="warning" %} If you are using bootstrapping, contact the Statsig team to confirm your server SDK is supported. {% /callout %} ================================================================================ # Debug Statsig Session Replay URL: https://docs.statsig.com/session-replay/debug Updated: 2026-07-11 ================================================================================ ## Large session recording warning In the Console, you may encounter the warning: > "This session recording is too large to load." This occurs when a session exceeds 50 MB in size. Once Statsig records a session at this size, the console can't display it, and you can't fix it retroactively. If you consistently see this warning, here are a few steps you can take to reduce session sizes going forward: 1. **Disable Inline Stylesheets**\ When initializing the `SessionReplayClient`, set `inlineStylesheet` to `false`. Statsig generally recommends keeping `inlineStylesheet` set to `true`, because it ensures recordings accurately reflect your original CSS even if you later update your styles. However, this setting often causes sessions to grow very large and is the most common source of bloat. {% tabs %} {% tab title="Javascript" %} ```jsx runStatsigSessionReplay(client, { inlineStylesheet: false, }); ``` {% /tab %} {% tab title="React" %} ```jsx ... options={{ plugins: [ new StatsigSessionReplayPlugin({ inlineStylesheet: false, }), ], }} ... ``` {% /tab %} {% /tabs %} 2. **Exclude Large Static Elements**\ If certain elements on your page are large but static (for example, background images or videos), you can exclude them from session capture by adding the `rr-block` class to their `className`. This prevents Statsig from recording those elements and can significantly reduce session size. 3. **Reach Out for Assistance**\ If you continue to experience large sessions, contact the team in [Slack Community](https://statsig.com/slack). A Statsig team member can review your session and provide recommendations for your setup. ================================================================================ # Watch Session Replays URL: https://docs.statsig.com/session-replay/watch Updated: 2026-07-11 ================================================================================ To find Session Replays, go to the User’s group in the Statsig console’s navigation panel. {% figure %} ![Session replay navigation in Statsig console](/images/session-replay/watch/2f9ca1b1-f8e0-403f-9d3f-898434f06266.png) {% /figure %} The main interface has three sections. The leftmost column lists available replays and filter controls. The middle playback surface lets you play, pause, and skip to events of interest. Select the next button to jump to the next event, or select an event in the rightmost events panel to skip to that point in the replay timeline. {% figure %} ![Session replay interface with playback controls and events panel](/images/session-replay/watch/f810ceb5-b6fc-4312-8e5d-9acfec0809bb.png) {% /figure %} For a more immersive replay experience you can hide the events panel: {% figure %} ![Session replay interface with hidden events panel](/images/session-replay/watch/d63fdf9e-cfc4-45e5-9365-d478504574c3.png) {% /figure %} You can also enter full screen mode. ## Find a replay {% figure %} ![Session replay selection interface with replay cards](/images/session-replay/watch/45ce3a59-6847-43d6-bfc8-6d3cd5d45917.png) {% /figure %} Select a replay from the list on the left to watch it. The replay card shows details such as URL, browser, country, and more, which you can use to scan for or filter to replays of interest. To narrow the set of replays, add filters. Filters let you scope to replays that contain a specific event, user, Feature Gate exposure (pass or fail), or Experiment group exposure. ## Playlists Playlists let you organize related session recordings into curated collections focused on specific user behaviors, issues, or research questions. ## Key features * Organized Collections: Create named playlists to group related session recordings. This makes it easier to focus on specific user journeys, bug reports, or research topics without searching through large numbers of individual sessions. * Collaborative Analysis: Share playlists with team members to collaborate on user experience analysis. Team members can view the same curated set of recordings to discuss findings and insights. * Persistent Organization: Unlike temporary filters, playlists preserve your organizational structure, making it easy to return to specific sets of recordings for ongoing analysis or follow-up research. * Integration: Playlists work within the existing session replay interface. When you open a playlist, you can view all recordings using the same replay player and analysis tools. ================================================================================ # CLI Session Replay URL: https://docs.statsig.com/session-replay/cli-session-replay Updated: 2026-07-11 ================================================================================ CLI Session Replay allows you to record terminal sessions in your Node.js CLI applications and replay them in the Statsig Console. Use it to understand how users interact with your command-line tools, diagnose issues, and improve the user experience. The plugin records terminal output and resize events. User input is planned for a future version. Because user input is likely to contain sensitive information, Statsig will release it after introducing methods to pause, filter, or redact recordings. ## Installation Install the CLI session replay package for Node.js: {% tabs %} {% tab title="npm" %} ```bash npm install @statsig/js-client @statsig/cli-session-replay-node ``` {% /tab %} {% tab title="yarn" %} ```bash yarn add @statsig/js-client @statsig/cli-session-replay-node ``` {% /tab %} {% tab title="pnpm" %} ```bash pnpm add @statsig/js-client @statsig/cli-session-replay-node ``` {% /tab %} {% /tabs %} ## Basic usage ```javascript import { StatsigClient } from '@statsig/js-client'; import { StatsigCliSessionReplayPlugin } from '@statsig/cli-session-replay-node'; const client = new StatsigClient( 'your-client-key', { userID: 'user-123' }, { loggingEnabled: 'always', // Required for CLI environments plugins: [new StatsigCliSessionReplayPlugin()], } ); // Recording starts here console.log('Hello from CLI!'); await client.initializeAsync(); // Your CLI application logic here console.log('Continue'); ``` {% callout type="note" %} **CLI logging requirement**: CLI applications must set `loggingEnabled: 'always'` when initializing the StatsigClient. By default, Statsig enables logging only in browser environments. CLI session replay requires logging to be enabled in all environments to capture and send session data. {% /callout %} ## Configuration options The `StatsigCliSessionReplayPlugin` accepts optional configuration: ```javascript import { StatsigCliSessionReplayPlugin } from '@statsig/cli-session-replay-node'; const plugin = new StatsigCliSessionReplayPlugin({ // Override the start timestamp (in milliseconds) startTimestamp: Date.now(), // Custom Asciicast header properties asciicastHeader: { title: 'My CLI App Session', command: 'my-cli-tool --verbose', env: { TERM: 'xterm-256color', SHELL: '/bin/bash' }, } }); ``` ### Configuration properties * **`startTimestamp`** (optional): Override the recording start time in milliseconds. Defaults to `Date.now()`. * **`asciicastHeader`** (optional): Custom properties for the Asciicast header. For details, refer to the [Asciicast v2 File Format page](https://docs.asciinema.org/manual/asciicast/v2/#header). Common fields include: * `title`: Human-readable title for the recording * `command`: The command that was executed * `env`: Environment variables relevant to the session * `theme`: Terminal color theme object. ## Recording limits * **Duration**: Sessions automatically end after 4 hours. * **Size**: Recording stops if session data exceeds 1 MB. ## Viewing recordings CLI session recordings appear in the Statsig Console alongside web session replays. Each recording shows exactly what happened in the terminal, including: * All terminal output * Terminal resize events * Timing information for each interaction * Session metadata and environment details ## Manual recording control You can access the recording instance for manual control: ```javascript import { CliRecording } from '@statsig/cli-session-replay-node'; // Check if currently recording if (CliRecording.isRecording()) { console.log('Session is being recorded'); } // Get current recording instance const recording = CliRecording.currentRecording; // Manually finish recording CliRecording.finish(); ``` ## Platform support CLI Session Replay supports: * Node.js applications * Linux, macOS, and Windows terminals * Any terminal that supports standard input/output streams ================================================================================ # Overview URL: https://docs.statsig.com/integrations/mcp/overview Updated: 2026-07-11 ================================================================================ ## MCP configuration guides {% card-grid columns="2" %} {% card title="Codex" href="/integrations/mcp/codex" icon="computer" %} Set up Statsig MCP using Codex Desktop App, CLI, or IDE extension. {% /card %} {% card title="ChatGPT Connector" href="/integrations/mcp/chatgpt-connector" icon="message" %} Talk to your Statsig projects from within ChatGPT. {% /card %} {% /card-grid %} {% card-grid columns="3" %} {% card title="Cursor" href="/integrations/mcp/cursor" %} Configure Statsig MCP in Cursor IDE. {% /card %} {% card title="Claude Code" href="/integrations/mcp/claude-code" %} Set up Statsig MCP in Claude Code. {% /card %} {% card title="Manual setup" href="/integrations/mcp/manual-setup" %} Manual configuration for any MCP-compatible tool. {% /card %} {% /card-grid %} ## Current MCP capabilities {% accordion-group %} {% accordion title="Audit Logs" %} {% table %} - Tool - Description --- - Get_Audit_Logs - List audit logs in the project. Filter by: - `id` - `sortKey` - `sortOrder` - `tags` - `startDate` - `endDate` {% /table %} {% /accordion %} {% accordion title="Dynamic Configs" %} {% table %} - Tool - Description --- - Create_Dynamic_Config - Create new config --- - Get_Dynamic_Config_Details_by_ID - Retrieve detailed config information --- - Get_List_of_Dynamic_Configs - List all dynamic config objects in the project. Filter by: - `creatorName` - `tags` --- - Update_Dynamic_Config_Entirely - Replace entire dynamic config with new targeting and values {% /table %} {% /accordion %} {% accordion title="Experiments (A/B Tests)" %} {% table %} - Tool - Description --- - Create_Experiment - Create new experiment --- - Get_Experiment_Details_by_ID - Get experiment details. Optionally trim the response to only the fields you need: - `fields`: comma-separated list of field names to return (for example, `hypothesis,primaryMetrics`); omit to return all fields. Reduces context consumption by up to 95–99%. --- - Get_Experiments_Batch - Read multiple experiments in a single call. Accepts a list of experiment IDs and an optional `fields` parameter to return only specific fields across all requested experiments. --- - Get_Experiment_Overall_Results - Retrieve experiment results/pulse data for a specific experiment. Analyze by: - `date` - `cuped` - `confidence` --- - Get_Experiment_Metric_Dimension_Results - Retrieve metric results for one experiment metric with dimensional breakdowns. Analyze by: - `date` - `cuped` - `confidence` --- - Get_List_of_Experiments - List all experiments in the project. Filter by: - `status` - `creatorName` - `tags` - `stale` --- - Update_Experiment_Entirely - Replace entire experiment configuration (Statsig removes any excluded data) {% /table %} {% /accordion %} {% accordion title="Autotunes" %} **Create_Autotune**: Create a new Autotune (multi-armed bandit) experiment. Describe the arms, success event, exploration/attribution windows, and winner threshold; the agent builds and creates the Autotune. Creates in draft by default; start the Autotune from the console to allocate traffic. Prompts for confirmation before creating. {% /accordion %} {% accordion title="Gates (Feature Flags)" %} {% table %} - Tool - Description --- - Create_Gate - Create new gate/flag --- - Get_Gate_Details_by_ID - Get complete gate configuration details --- - Get_Gate_Results - Retrieve pulse/results for a specific gate rule. - `cuped` - `confidence` --- - Get_List_of_Gates - List all gates/flags. Filter by: - `type` - `creatorName` - `tags` --- - Update_Gate_Entirely - Replace entire gate setup with new rules and settings (Statsig removes any excluded data) {% /table %} {% /accordion %} {% accordion title="Layers" %} {% table %} - Tool - Description --- - Create_Layer - Create a new layer --- - Get_Layer_Details_by_ID - Retrieve layer details, including parameters and metadata --- - Get_List_of_Layers - List all layers in the project --- - Update_Layer_Entirely - Replace the full layer configuration {% /table %} {% /accordion %} {% accordion title="Metrics and Metric Sources" %} {% table %} - Tool - Description --- - Get_List_of_Metric_Sources - List all metric sources in the project --- - Get_List_of_Metrics - List all metrics in the project. Filter by: - `showHiddenMetrics` - `tags` - `filters` --- - Get_Metric_Definition_by_ID - Get the full definition for a metric, including its type, source, and configuration details {% /table %} {% /accordion %} {% accordion title="Parameter Stores" %} {% table %} - Tool - Description --- - Create_Param_Store - Create a new Parameter Store and its parameters --- - Update_Param_Store - Update an existing Parameter Store, including adding or changing parameters --- - Delete_Param_Store - Delete a Parameter Store {% /table %} {% /accordion %} {% accordion title="Segments" %} {% table %} - Tool - Description --- - Create_Segment - Create a new segment. Supports `id_list`, `rule_based`, `analysis_list`, and `user_store_id_list` segment types --- - Get_List_of_Segments - List all segments in the project --- - Get_Segment_Details_by_ID - Retrieve segment details --- - Update_Segment - Update an existing segment. Specifically: - Update rules for conditional segment types - Add IDs to user stores and ID lists {% /table %} {% /accordion %} {% /accordion-group %} Need other functions? Statsig considers additions by request. Reach out in Slack. ## Use cases The Statsig MCP server supports both `GET` and `POST` requests. Read-only users can connect and use all read tools. Write tools require an API key with write permissions. The Statsig MCP server is especially useful for: - Repetitive tasks like cleaning up stale gates - Summarizing console information in your IDE workflows - Bulk creating or deleting gates, and making the necessary changes in your code ### Example prompt for stale gate cleanup ``` You are an expert, diligent Software engineer with the sole goal of reducing the amount of tech debt in the code base. This code base, making use of best practices, leverages feature gates liberally using Statsig. As gates complete their lifecycle in Statsig, they may end up "stale" which means that they're enabled, but no longer checked. Your job is to find these gates, and refactor the codebase to no longer check the gate (instead, changing the check to a constant value). You should follow coding best practices: - You should not simply replace gate calls with "True" or "False" but instead carefully trace the logic through to where it is used and change the behavior that way - adjusting the code in minor ways to make the default behavior what the value is that the gate was returning - You should always strive to write minimal code - readable but terse, never longer than it needs to be - You should never write comments or debug statements. You should use the statsig-local MCP to list feature gates, then look for gates that are marked as stale. You should then grep the codebase for that feature flag name, and do a minimal rewrite of the code to no longer use Statsig, removing the checkGate call or similar. When you use the MCP use the get /console/v1/gates endpoint and parameters type="STALE" and limit =10. You should select only one gate to do this with, before stopping. If you cannot find the gate after a grep, try the next one you found using the MCP. Once you successfully remove a gate, return. ``` ================================================================================ # Docs MCP server URL: https://docs.statsig.com/integrations/mcp/docs-mcp-server Updated: 2026-07-11 ================================================================================ Use the Statsig Docs MCP server to let supported AI clients read public Statsig documentation directly from `https://docs.statsig.com/api/mcp`. The server is read-only and serves English documentation from this site, so your AI tools answer Statsig questions with current, accurate docs content. This server is separate from the authenticated [Statsig MCP server](/integrations/mcp/overview), which connects to your project data. The Docs MCP server reads documentation only and needs no API key. ## Supported clients Use a client that supports remote MCP servers over Streamable HTTP and accepts a direct URL. Claude Code, Cursor, and Codex CLI all work. Older MCP clients that only support the deprecated HTTP+SSE transport can't connect to this endpoint. ## Add the server Many MCP clients read a JSON config: ```json { "mcpServers": { "statsig-docs": { "url": "https://docs.statsig.com/api/mcp", "transport": "streamable-http" } } } ``` If your client supports command-line setup, add the same URL as a remote HTTP MCP server: ```bash claude mcp add --transport http statsig-docs https://docs.statsig.com/api/mcp ``` ## Available tools The server exposes three read-only tools: - `get_page`: Retrieve the full markdown content of a Statsig documentation page. - `list_pages`: List all available documentation pages with their titles and slugs. - `search_docs`: Search documentation pages by keyword. ## Limitations {% callout type="note" heading="" %} The Docs MCP server is public, read-only, and serves English documentation only. It doesn't require authentication, doesn't expose MCP prompts or resources, and supports Streamable HTTP only. The server rate-limits requests per client. {% /callout %} For authenticated access to your project data, like experiments, gates, and metrics, use the [Statsig MCP server](/integrations/mcp/overview) instead. ## Troubleshooting - If your client reports an unsupported transport, update it to a version that supports remote MCP servers over Streamable HTTP. - If setup fails, confirm that you used the exact endpoint URL: `https://docs.statsig.com/api/mcp`. - If you need project data or write access, this server isn't the right endpoint. Use the authenticated [Statsig MCP server](/integrations/mcp/overview). ================================================================================ # Statsig MCP with Codex URL: https://docs.statsig.com/integrations/mcp/codex Updated: 2026-07-11 ================================================================================ ## Installation and authentication with OAuth {% callout type="note" %} MCP OAuth only supports Personal Console API Keys. Ensure your Statsig org owner has enabled Personal Console API Keys creation for your role [here](https://console.statsig.com/settings?tab=organization){" "} {% /callout %} On Codex, use OAuth and the HTTP transport directly. Authentication is saved for future sessions. {% tabs %} {% tab title="Codex Desktop" %} In Codex Desktop, navigate to *Settings > MCP servers* and add a new custom server. Set up using Streamable HTTP, with URL set to `https://api.statsig.com/v1/mcp`: ![Set up Statsig MCP in the Codex Desktop App](/images/integrations/mcp/statsig-codex-desktop-app-setup.png) Upon saving, you should see statsig turned on under custom servers. ![Statsig MCP in the Codex Desktop App MCP Servers List](/images/integrations/mcp/statsig-codex-desktop-app-complete.png) {% /tab %} {% tab title="Codex CLI and IDE extension" %} If you are working in Codex CLI or IDE extension, run the below command to add the Statsig MCP server into your `~/.codex/config.toml` file and restart Codex. ```bash codex mcp add statsig --url https://api.statsig.com/v1/mcp ``` This command opens a browser window for you to sign in to your Statsig account and authorize access to your Statsig project. After signing in, restart Codex and run `/mcp` in CLI to confirm the Statsig MCP is listed as an available MCP server. Make sure the status is set to **enabled**: ![Statsig MCP in the Codex CLI MCP Servers List](/images/integrations/mcp/statsig-codex-cli-complete.png) {% /tab %} {% /tabs %} {% accordion title="Using API key instead of OAuth" %} ```toml [mcp_servers.statsig] url = "https://api.statsig.com/v1/mcp" command = "npx" args = ["--yes", "mcp-remote", "https://api.statsig.com/v1/mcp", "--header", "statsig-api-key: console-YOUR-CONSOLE-API-KEY"] trust_level = "trusted" enabled = true ``` Replace `console-YOUR-CONSOLE-API-KEY` with your actual Statsig Console API key, which you can retrieve [here](https://console.statsig.com/api_keys). Ensure your API key has the right permissions: read-only keys can view data, while write keys can make changes to your project. {% /accordion %} ## Using Statsig MCP with Codex With Statsig MCP configured in any Codex environment, you can: * **Explore Experiments**: "List all my active experiments" * **Manage Gates**: "What gates are currently stale?" * **Configure Dynamic Configs**: "Show me the configuration for the dynamic config 'dynamic-config'" * **Get Insights**: "Show me details about the experiment called 'new-checkout-flow'" ## Next steps After installation, you can: * List experiments, gates, and dynamic configs * Create and update experiments, gates, and configs * Access your Statsig data directly from Codex For available MCP capabilities, go to the [MCP capabilities](/integrations/mcp/overview#current-mcp-capabilities) section. ================================================================================ # Statsig ChatGPT App URL: https://docs.statsig.com/integrations/mcp/chatgpt-connector Updated: 2026-07-11 ================================================================================ ## How the Statsig ChatGPT app works The ChatGPT App is built on top of the Statsig MCP server. After setup, you can query experiments, manage feature flags, and explore analytics directly within OpenAI's ChatGPT conversational interface. ## Installation 1. Navigate to the Statsig App in the ChatGPT Apps Directory [here](https://chatgpt.com/apps/statsig/asdk_app_6967f065ac9481918969c660ff7686e9). 2. Click "Connect" and "Continue" to complete OAuth for your Statsig project. {% callout type="note" %} ChatGPT App OAuth only supports Personal Console API Keys. Ensure your Statsig org owner has enabled Personal Console API Keys creation for your role [here](https://console.statsig.com/settings?tab=organization) {% /callout %} {% figure %} <img src="/images/integrations/mcp/statsig-chatgpt-app-listing.png" alt="Statsig listing in the ChatGPT Apps Directory" className="w-full rounded-lg border border-gray-200 dark:border-gray-800" /> {% /figure %} ## Using Statsig MCP within ChatGPT With Statsig MCP configured in ChatGPT, you can: - **Explore Experiments**: "List all my active experiments" - **Manage Gates**: "What gates are currently stale?" - **Configure Dynamic Configs**: "Show me the configuration for the dynamic config 'dynamic-config'" - **Get Insights**: "Show me details about the experiment called 'new-checkout-flow'" ## What you can do after installation - List experiments, gates, and dynamic configs - Create and update experiments, gates, and configs - Access your Statsig data directly from ChatGPT For available MCP capabilities, go to the [MCP capabilities](/integrations/mcp/overview#current-mcp-capabilities) section. ================================================================================ # Statsig MCP with Cursor URL: https://docs.statsig.com/integrations/mcp/cursor Updated: 2026-07-11 ================================================================================ ## Installation and authentication with OAuth {% callout type="note" %} MCP OAuth only supports Personal Console API Keys. Ensure your Statsig org owner has enabled Personal Console API Keys creation for your role [here](https://console.statsig.com/settings?tab=organization){" "} {% /callout %} You can add the Statsig MCP with OAuth to Cursor in two ways: ### Option 1: Quick install (recommended) [Click here](cursor://anysphere.cursor-deeplink/mcp/install?name=statsig\&config=eyJ1cmwiOiJodHRwczovL2FwaS5zdGF0c2lnLmNvbS92MS9tY3AifQ%3D%3D) to automatically add the Statsig MCP to Cursor. ### Option 2: Manual configuration 1. Open Cursor settings 2. Navigate to **Settings → Cursor Settings → Tools & Integrations** 3. Find the MCP servers section 4. Add the following configuration to `~/.cursor/mcp.json`: ```json { "mcpServers": { "statsig": { "url": "https://api.statsig.com/v1/mcp" } } } ``` Cursor automatically handles OAuth authentication when you first use the Statsig MCP. You'll be prompted to: 1. Sign in to your Statsig account 2. Authorize the MCP server to access your Statsig project 3. Restart Cursor to apply the changes 4. Verify the connection by navigating to *Settings → Cursor Settings → Tools & Integrations*, where the Statsig MCP server should appear and be active Cursor saves the authentication for future sessions. {% accordion title="Using API key instead of OAuth" %} OAuth is the recommended setup, but you can also authenticate with your Console API key. [Click here](cursor://anysphere.cursor-deeplink/mcp/install?name=statsig\&config=eyJjb21tYW5kIjoibnB4IG1jcC1yZW1vdGUgaHR0cHM6Ly9hcGkuc3RhdHNpZy5jb20vdjEvbWNwIC0taGVhZGVyIHN0YXRzaWctYXBpLWtleToke0FVVEhfVE9LRU59IiwiZW52Ijp7IkFVVEhfVE9LRU4iOiJpbnNlcnQteW91ci1hcGkta2V5LWhlcmUifX0%3D) to quick install, or manually configure: 1. Open Cursor settings 2. Navigate to **Settings → Cursor Settings → Tools & Integrations** 3. Find the MCP servers sections 4. Add the below configuration to `~/.cursor/mcp.json` ```json { "mcpServers": { "statsig": { "command": "npx mcp-remote https://api.statsig.com/v1/mcp --header statsig-api-key:${AUTH_TOKEN}", "env": { "AUTH_TOKEN": "console-YOUR-CONSOLE-API-KEY" } } } } ``` Replace `console-YOUR-CONSOLE-API-KEY` with your actual Statsig Console API key, which you can retrieve [here](https://console.statsig.com/api_keys). Ensure your API key has the right permissions: read-only keys can view data, while write keys can make changes to your project. {% /accordion %} ## Using Statsig MCP with Cursor After configuration, you can use Statsig MCP commands in Cursor's chat interface: * **Query Experiments**: "What experiments are currently running?" * **Manage Gates**: "List all my feature flags" * **Get Details**: "Show me the configuration for gate 'new-feature'" * **Create Entities**: "Create a new experiment called 'checkout-test'" ## Troubleshooting If the MCP server doesn't appear: * Make sure you've restarted Cursor after adding the configuration * Check that the `mcp.json` file is in the correct location: `~/.cursor/mcp.json` * Verify your Statsig account has the necessary permissions ## Next steps * [MCP capabilities](/integrations/mcp/overview#current-mcp-capabilities) * [Use cases](/integrations/mcp/overview#use-cases) for Statsig MCP * [Stale gate cleanup example](/integrations/mcp/overview#example-prompt-for-stale-gate-cleanup) ================================================================================ # Statsig MCP with Claude Code URL: https://docs.statsig.com/integrations/mcp/claude-code Updated: 2026-07-11 ================================================================================ ## Installation and authentication with OAuth {% callout type="note" %} MCP OAuth only supports Personal Console API Keys. Ensure your Statsig org owner has enabled Personal Console API Keys creation for your role [here](https://console.statsig.com/settings?tab=organization){" "} {% /callout %} On Claude Code, use OAuth and the HTTP transport directly. Run this command: ```bash claude mcp add --transport http statsig https://api.statsig.com/v1/mcp ``` This command: * Adds the Statsig MCP server to your Claude Code configuration * Configures it to use HTTP transport with OAuth authentication * Sets up the connection to Statsig's MCP endpoint To authenticate with OAuth, run `/mcp` in Claude Code and follow the setup instructions: 1. Claude Code opens a browser window 2. Sign in to your Statsig account 3. Authorize the MCP server to access your Statsig project 4. Claude Code saves the authentication for future sessions {% accordion title="Using API key instead of OAuth" %} OAuth is the recommended setup, but you can also authenticate with your Console API key. Run this command on the command line: ```bash claude mcp add --transport http statsig-local https://api.statsig.com/v1/mcp \ --header "statsig-api-key: console-YOUR-CONSOLE-API-KEY" ``` Replace `console-YOUR-CONSOLE-API-KEY` with your actual Statsig Console API key, which you can retrieve [here](https://console.statsig.com/api_keys). Ensure your API key has the required permissions: read-only keys can view data, while write keys can make changes to your project. {% /accordion %} ## Verify the connection After installation, verify the connection: 1. Open Claude Code. 2. Ask Claude to list your Statsig experiments or gates. 3. Confirm that Claude can access your Statsig data. ## Using Statsig MCP with Claude Code After configuration, you can interact with your Statsig data through Claude Code: * **Query Experiments**: "What experiments are currently running?" * **Manage Gates**: "List all my feature flags" * **Get Details**: "Show me the configuration for gate 'new-feature'" * **Create Entities**: "Create a new experiment called 'checkout-test'" ## Troubleshooting If you encounter issues: * Make sure you have the latest version of Claude Code * Verify your Statsig account has the necessary API permissions * Check that the MCP server URL is correct: `https://api.statsig.com/v1/mcp` * Try re-running the installation command ## Related resources * [MCP capabilities](/integrations/mcp/overview#current-mcp-capabilities) * [Use cases](/integrations/mcp/overview#use-cases) for Statsig MCP * Set up Statsig MCP in other tools: [Cursor](/integrations/mcp/cursor), [Codex CLI](/integrations/mcp/codex) ================================================================================ # Statsig MCP with Other MCP-Compatible Clients URL: https://docs.statsig.com/integrations/mcp/manual-setup Updated: 2026-07-11 ================================================================================ If your tool doesn't have a specific setup guide, you can manually configure the Statsig MCP server. This guide covers the general configuration steps that work with any MCP-compatible client. ## Prerequisites * A Statsig account (sign up at [console.statsig.com](https://console.statsig.com)) * An MCP-compatible tool or client * Access to your tool's configuration files ## Configuration Add the following configuration to your MCP client's configuration file: ```json { "mcpServers": { "statsig": { "url": "https://api.statsig.com/v1/mcp" } } } ``` This installs the Statsig MCP Server with OAuth. When you first use the MCP server: 1. Your tool prompts you to authenticate 2. You'll be redirected to Statsig's OAuth page 3. Sign in and authorize the MCP server 4. The authentication token is stored automatically {% callout type="note" %} MCP OAuth only supports Personal Console API Keys. Ensure your Statsig org owner has enabled Personal Console API Keys creation for your role [here](https://console.statsig.com/settings?tab=organization){" "} {% /callout %} {% accordion title="Using API Key instead of OAuth" %} OAuth is the recommended setup, but you can also authenticate with your Console API key. Add the below configuration to your MCP client's configuration file: ```json { "mcpServers": { "statsig": { "command": "npx mcp-remote https://api.statsig.com/v1/mcp --header statsig-api-key:${AUTH_TOKEN}", "env": { "AUTH_TOKEN": "console-YOUR-CONSOLE-API-KEY" } } } } ``` Replace `console-YOUR-CONSOLE-API-KEY` with your actual Statsig Console API key, which you can retrieve [here](https://console.statsig.com/api_keys). Ensure your API key has the right permissions: read-only keys can view data, while write keys can make changes to your project. {% /accordion %} ## Verification After adding the configuration: 1. Restart your tool to apply the changes 2. Check your tool's MCP server list to verify Statsig appears 3. Try using a Statsig MCP command to test the connection ## Testing the connection You can test the connection by asking your tool to: * **List experiments**: "Get list of experiments" * **List gates**: "Get list of gates" * **Get details**: "Get details for experiment \[experiment-id]" ## Next steps * [MCP capabilities](/integrations/mcp/overview#current-mcp-capabilities) * [Use cases](/integrations/mcp/overview#use-cases) for Statsig MCP * Tool-specific guides: [Cursor](/integrations/mcp/cursor), [Claude Code](/integrations/mcp/claude-code), [Codex CLI](/integrations/mcp/codex) ================================================================================ # Agent Skills Repository URL: https://docs.statsig.com/integrations/agent-skills Updated: 2026-07-11 ================================================================================ The [statsig-io/agent-skills](https://github.com/statsig-io/agent-skills) repository contains reusable skill packs for coding agents. Skills are packaged, shareable workflows for repeated tasks, built on top of the Statsig MCP tools and Console API. These skills build on the open [Agent Skills standard](https://agentskills.io/). ## Available skills | Name | Description | | --- | --- | | `statsig` | Manage Statsig experiments, gates, dynamic configs, segments, layers, and audit logs through the Statsig MCP. | | `statsig-create-cloud-metric` | Draft or execute Statsig Cloud metric creation requests through the Statsig Console API. | | `statsig-dashboard` | Create, read, and update your Statsig dashboards. | ## Install from the repository Install a skill from this repo using the Vercel `skills` CLI: ```bash npx skills add statsig-io/agent-skills ``` You can also: * List installable skills: `npx skills add statsig-io/agent-skills --list` * Install a skill globally for your user: `npx skills add -g statsig-io/agent-skills --skill statsig-dashboard` * Install every skill in this repo: `npx skills add statsig-io/agent-skills --all` After installation, compatible agents can discover each skill from its metadata. ## Requirements * `STATSIG_CONSOLE_API_KEY` for Statsig Console API access * Python 3 if you want to run bundled helper scripts directly ================================================================================ # AI Development with Statsig URL: https://docs.statsig.com/integrations/ai_development_with_statsig Updated: 2026-07-11 ================================================================================ ## How Statsig supports AI development Developers increasingly use AI agents for small-to-midsized tasks. For small feature additions, this is low risk but can still introduce bugs or low-quality user experiences. When an agent is aware of Statsig, the agent can automatically add logging for potential errors and add feature gates to new features (or confirm they are behind existing gates), so you can manage rollouts safely and measure any degradation. This becomes significant given the high velocity individuals can now achieve with AI assistance, including the ability to develop within codebases they have less context in. ## Recommended approach Use an `AGENTS.md` file (or an equivalent file such as `CLAUDE.md`) to provide instructions you can tailor to your workflow. The critical behaviors are: * Adding gates to new features or codepaths, and tuning how aggressively to do so * Automatically adding your user ID, and any test IDs your agent may log in as, to those gates for testing in development * Adding critical logs for errors and user behaviors * Adding these to tracking is manual now, but Statsig has plans to make this automatable through MCP for event count metrics. ### Boilerplate `Agents.md` file Fill in your Statsig ID, and follow the [MCP guide](/integrations/mcp/overview) to set up the MCP server. Consider updating naming conventions and logic around when to ask. Use a limited-access console API key to reduce the risk of an agent deleting entities in Statsig. ``` # Statsig Development Guidelines It is important that we selectively killswitch new features or any new, risky codepaths using Statsig Feature Gates so that we can turn them off if they cause issues. ## When To Use Gates - We should consider adding a gate whenever we make significant changes to product features that have any risk of causing issues. - It is possible for this to be spammy, so clarify with users during planning what should be killswitched. - Start by assuming atomic, session-level features will be killswitched - e.g. "A new modal" would be, but not a copy change within a modal, or a bugfix. - Sometimes, adding a gate is just as dangerous as the feature itself. - Identify situations where this could occur - usually when adding the gate adds a large amount of complexity - and flag the risk to the user when asking if they want to create a gate. Always ask the user if they want to gate the feature. Often, users will want to include the feature behind an existing gate, so ask in this way, when appropriate: "This seems like a feature that you might want to be able to turn off remotely. Would you like to gate this feature, add it to an existing gate, or proceed without using gates?" Look for gates already being used for this feature type - often in the same file or nearby in the file tree for client code. Don't spend a bunch of time investigating code before asking, since it leads to a lot of hang time. This should be an early step in the process. Quickly check in with the user on gating before you do a deep codebase scan - though you might want to use a quick context check to identify risk. ## How to Use Gates - Create a new gate, if requested, using the statsig-local MCP server. If this server does not exist, ignore the above. - After creation, use that gate in code, following the local codebase patterns for accessing statsig. - This will normally be Statsig.useGate() or similar, but they may have an internal wrapper or use a general feature flag library which wraps Statsig. - When creating the gate, start it with a default-fail rules. The user, or we, can add rules later to release the gate. - Add a rule called "Development" which passes the user's ID, specified below. Make sure this is the first evaluated rule. ## Override instructions - The user's ID is the string "" - Use a custom ID gate condition, with the userID field set to that ID ## Naming Conventions - For these features, create a feature gate which is a 3-4 word description separated by dashes. - Do not use the word killswitch. Propose the name of the gate before creating it, allowing the user to update it if desired. Do not include your name (e.g. codex, claude, cursor) in the gate name. ## Logging - We would like to measure our gate changes as we roll out features. - If there's key interactions in the code which are not logged to Statsig, consider logging those events to statsig - Include critical metadata (e.g. information about what happened, key numeric values) Propose these logging changes to the user and see what they say, and only if they're very clearly needed. Similar to the gate checks, pattern match to what is in the repo already, e.g. Statsig.logEvent or a wrapper like LoggingContext.log() ## Statsig Implementation Instructions [left empty - fill with specific instructions about your Statsig implementation if it exists, such as specific calling patterns or gotchas] ## Guardrails and Critical Rules - In this context, only use this MCP server to create gates - Never run deletions or other destructive tools without explicit instructions from the user ``` ================================================================================ # Github AI Integration URL: https://docs.statsig.com/integrations/github-ai-integration Updated: 2026-07-11 ================================================================================ ## How the GitHub AI integration works {% callout type="info" %} This feature might have rough edges. If you have feedback, share it with the Statsig team. {% /callout %} The Statsig GitHub integration links your organization's code repository directly to your Statsig project. Statsig analyzes your codebase and creates a private Knowledge Graph that maps relationships between your codebase and Statsig services such as Feature Gates, Experiments, and Metrics. This integration connects your software development with measurable business impact, delivering contextual insights within Statsig. {% figure %} ![Knowledge Graph diagram showing relationships between codebase and Statsig services](/images/Knowledge-graph-diagram.png) {% /figure %} ### Key features Integrating your code repository unlocks the following capabilities: * Locate specific lines of code where a Feature Gate or Experiment is implemented * Automatically generate PRs (Pull Requests) to remove stale flags from your code {% figure %} ![An example of Remove from Code in a stale gate banner](/images/ai-remove-from-code.png) {% /figure %} * Understand the intent behind metrics and experiments based on code implementation {% figure %} ![An example of AI description in the right rail of a config](/images/ai-contextual-description.png) {% /figure %} * Write natural language queries to create charts to answer business questions (Coming Soon) ## Why connect your codebase In modern product development, there is often a disconnect between the code you write (hosted on GitHub) and its impact and business outcomes (data in Statsig). The Knowledge Graph bridges this gap by analyzing your connected repositories to provide the context behind every feature. * Context: The Knowledge Graph knows when an event is logged or a feature is exposed to a user. * Intent: The Knowledge Graph understands the purpose and meaning of that event and feature. * Freshness: The Knowledge Graph ensures insights in Statsig reflect the current state of your main branch. ## Installation guide Before you begin, confirm you have Admin permissions in both your Statsig Project and the target GitHub Organization. ### Prerequisites If your organization uses IP restrictions to limit access to your GitHub resources, you must allowlist the IP addresses that Statsig services use to connect to GitHub. Follow the steps in this [GitHub doc](https://docs.github.com/en/enterprise-cloud@latest/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-allowed-ip-addresses-for-your-organization) to allowlist IP addresses for your organization. {% figure %} ![Configuring IP addresses in your Github Org](/images/IP-address-github-app.png) {% /figure %} If you choose to manually add IP addresses, go to [Statsig IP ranges](/infrastructure/statsig_ip_ranges#statsig-ip-ranges). ### First-time installation If you're connecting GitHub to Statsig for the first time: 1. Navigate to **Settings** → **Integrations** in the Statsig console. 2. Locate and select the **Statsig GitHub App** card. 3. Select **Enable Connection**. 4. You are redirected to GitHub. Authenticate and select the target Organization and Repositories. 5. After authorization, you are redirected back to Statsig to confirm the connection. {% callout type="info" %} You can install the app on specific repositories or your entire organization. For the best results with Knowledge Graph, select all relevant repositories containing feature code. {% /callout %} ### Connecting additional projects GitHub allows a specific GitHub App to be installed only once per GitHub Organization. If you have multiple Statsig Projects (for example, "Project 1" and "Project 2") that need access to the same GitHub Org, they share the underlying connection. To link a new project to an existing GitHub connection: 1. Navigate to **Settings** → **Integrations** in the Statsig console. 2. Locate and select the **Statsig GitHub App** card. Statsig indicates if an existing connection exists for any other project. 3. Select the Statsig Project you want to inherit the connection from. 4. Confirm the link. ### Migrating from legacy (PAT) integration If you're using the older Personal Access Token (PAT) integration, upgrade to the GitHub App for improved security and functionality. 1. Navigate to **Settings** → **Integrations** in the Statsig console. 2. Locate and select the **Statsig GitHub App** card. 3. A banner on the GitHub integration card prompts you to upgrade from the PAT-based connection. 4. Follow the flow to install the GitHub App on your repositories. 5. After the app is installed, the system automatically deprecates the old PAT connection. ## Statsig GitHub integration FAQs 1. **Will my data be used to train LLM models?**\ Statsig doesn't use your data to train LLM models, and neither do its sub-processors. Your data is secured to your organization and used to improve the services provided to you. 2. **Does Statsig store my source code?**\ Statsig doesn't permanently store your source code. Statsig temporarily processes code to extract metadata and embeddings to create the Knowledge Graph, then discards it when no longer necessary. 3. **How long does it take for the code insights to appear in Statsig?**\ Setting up the integration can take up to a few hours depending on the size of the repository. After the initial setup, Statsig indexes new data every 7 days. 4. **What security controls apply?**\ Go to [Statsig security practices](https://www.statsig.com/trust/security) for security information. Go to [AI governance](/compliance/ai_governance_security_privacy) for AI governance information. ================================================================================ # Control Panel URL: https://docs.statsig.com/control-panel/overview Updated: 2026-07-11 ================================================================================ Control Panel is a surface to measure and track features that you and your teams deploy and measure with Statsig. It is similar to the standard gate and experiment tables in Statsig. Control Panel lets you switch between filters on configs, experiments, and gates; measure results; make or review ship decisions; and manage development flows such as overrides. ## Using Control Panel * Configure sections in the left column to specify different filters and display settings, such as "My Changes" or "My Team's Changes". * View results inline. Configs and experiments show a summary of metric movements. Hover to see a table of all observed metric movements. Gates and Dynamic Configs can be opened to see results per rule. * Manage configs. View or set your override status from Control Panel, and end features if unexpected behavior occurs. ================================================================================ # Metrics User Guide URL: https://docs.statsig.com/metrics/introduction Updated: 2026-07-11 ================================================================================ ## Metrics user guide Statsig combines data from your existing data sources to give you a complete view of your product metrics and the impact new features and experiments have on those metrics. Statsig automatically creates metrics from the **raw events** that you log from your application, as well as raw and transformed events you send to Statsig through a data collector. Statsig can also reuse your existing **precomputed metrics** by natively integrating and pulling data from your cloud data warehouse. You can also ingest metrics into Statsig using an HTTP endpoint or Azure Blob storage. {% figure %} ![Data integration architecture diagram](/images/metrics/introduction/200041707-e6cc4f1e-7ec4-4044-8afe-e877964eb2dc.png) {% /figure %} The **Metrics User Guide** consists of [Metrics 101](/metrics/101), [201](/metrics/201), and [301](/metrics/301) level guides that step through basic concepts to real-time analytics in Statsig. ## Metrics 101 - An overview * [Introduction](/metrics/101) * [Raw Events](/metrics/raw-events) * [Raw Event Metrics](/metrics/raw-event-metrics) * [Custom Metrics](/metrics/custom-metrics) * [Precomputed Metrics](/metrics/precomputed-metrics) * [Pulse Metrics](/metrics/pulse) * [Metric Dimensions](/metrics/metric-dimensions) ## Metrics 201 - Diving deeper * [Introduction](/metrics/201) * [Creating Metrics](/metrics/create) * [Tagging Metrics](/metrics/create-metric-tags) * [User Metrics](/metrics/user) * [Assignment ID != Analysis ID](/metrics/different-id) ## Metrics 301 - Advanced topics * [Introduction](/metrics/301) * [Metrics Explorer](/product-analytics/overview) * [User Funnels](/metrics/create-user-funnels) * [User Flows](/metrics/create-user-flows) Statsig combines product experimentation with real-time analytics to support **product observability**. {% figure %} ![Product observability dashboard overview](/images/metrics/introduction/182259790-b898161f-6657-4d52-8367-f3bdeb8d6037.png) {% /figure %} To get started, go to [Metrics 101](/metrics/101). ================================================================================ # Metrics Overview URL: https://docs.statsig.com/metrics/101 Updated: 2026-07-11 ================================================================================ {% callout type="info" %} **Warehouse Native users**: You're viewing the Cloud docs for this page. Metrics and experiments behave differently in Warehouse Native. Read more in [Data & Semantic Layer in Warehouse Native](/statsig-warehouse-native/configuration/data-and-semantic-layer). {% /callout %} This guide steps through the basic concepts to help you set up essential product metrics in your Statsig project. 1. [How Metrics Work on Statsig](/metrics/how-metrics-work) 2. [Raw Events](/metrics/raw-events) * [Types of Raw Events](/metrics/raw-events#types-of-raw-events) * [Unit Identifiers](/metrics/raw-events#unit-identifiers) required for raw events * [ID Mapping Considerations](/experiments/create-new#id-mapping-capabilities) for cross-ID analysis * [Ingesting Raw Events](/metrics/raw-events#ingesting-raw-events) * [Seeing Raw Events in the Statsig Console](/metrics/raw-events#raw-events-in-console) 3. [Auto-generated Events](/metrics/raw-event-metrics) * [Event Count](/metrics/raw-event-metrics#event-count-metric) and [Event DAU](/metrics/raw-event-metrics#event-dau-metric) metrics * [User Accounting](/metrics/raw-event-metrics#user-accounting-metrics) 4. [Custom Metrics](/metrics/custom-metrics) 5. [Importing Precomputed Metrics](/metrics/precomputed-metrics) 6. [Pulse Metrics](/metrics/pulse) {% callout type="info" %} **Ask for Help**: Hop on to the [Statsig Slack channel](https://statsig.com/slack) if you have any questions or want to validate the best path to import your metrics. {% /callout %} ================================================================================ # How Metrics Work on Statsig URL: https://docs.statsig.com/metrics/how-metrics-work Updated: 2026-07-11 ================================================================================ ## How metrics work on Statsig {% callout type="info" %} **Warehouse Native users** : You're viewing the Cloud docs for this page. Metrics and experiments behave differently in Warehouse Native. Read more in [About Warehouse Native](/statsig-warehouse-native/introduction#how-warehouse-native-works) . {% /callout %} A metric in Statsig is a numeric value for each user on a given day. You can aggregate this value across the entire user base or a subset, such as the test or control group of an experiment. For example, say one user made two purchases on September 1st, and another made only one. You can aggregate these values across multiple users to calculate the total number of purchases across all users on September 1st. By default, Statsig computes metrics from logged raw events in the production environment. When testing experiments in lower environments (such as development or staging) with **Enable for Environments**, you can track cumulative exposures and metric results from those environments. Tracking lower-environment data lets you validate your experiment setup before launching to production. Statsig prioritizes production data for final *Pulse* result analyses. ## Sources of Statsig metrics There are two fundamental sources of metrics in Statsig: 1. **Raw Events** * Statsig [auto-generates certain metrics](/metrics/raw-event-metrics) such as **event\_count** and user accounting metrics from these events * You can also define [custom metrics](/metrics/create) using your logged raw events 2. **Precomputed Metrics** - You can provide these pre-computed values to Statsig Statsig's Stats Engine joins these metrics with your exposure events from feature gates and experiments to compute experiment results and analytics. {% figure %} ![Statsig data flow architecture diagram](/images/data_flow_cloud.png) {% /figure %} {% callout type="info" %} **How are events and metrics billed?** Statsig bills each event (or a row when importing from your data warehouse) once, regardless of how many experiments use the event. {% /callout %} ================================================================================ # Raw Events URL: https://docs.statsig.com/metrics/raw-events Updated: 2026-07-11 ================================================================================ Statsig uses the raw events emitted by your application to compute a wide range of product metrics. These events contain the context needed to understand user behavior and infer user intentions. ## Types of raw events Statsig records two types of raw events from your application: 1. **Exposure events** track which users are assigned to control and test groups. This data allows Statsig to generate test results so you can evaluate the impact of new features and experiments. Exposure events also allow Statsig to assess the health of an experiment so you can make key decisions based on trustworthy data. Statsig requires exposure events at a minimum to generate experiment results. 2. **Custom events** track user actions and any events triggered during use of your application, including events that capture performance (for example, latency) or analytics data (for example, session start). These events allow Statsig to assess overall user engagement in your application (for example, daily active users, weekly stickiness) and changes in user behavior as you roll out new features and experiments. {% callout type="note" %} When logging custom events, avoid using dot (.) notation in metadata keys. Keys containing dots are interpreted as nested paths during JSON parsing (e.g., by JSON\_VALUE), which can cause the values to be parsed as NULL. {% /callout %} ## Unit identifiers You must include at least one unit identifier when you record any raw events with Statsig. The unit identifier is required for two reasons: 1. To ensure that your users receive a consistent application experience when allocated to control or test groups in an experiment 2. To join exposure events with all custom events triggered by a given user to compute experiment results ## Ingesting raw events You can send raw events into Statsig in three ways. {% figure %} ![Raw events ingestion methods diagram](/images/metrics/raw-events/182466148-a40ad007-a60a-47b8-9cd3-9b27d0af82ed.png) {% /figure %} 1. Integrate with Statsig's [client](/client/introduction) or [server](/server/introduction) SDKs or [HTTP](/http-api/overview) API 2. Set up Statsig as a destination in a data connector such as [Segment](/integrations/data-connectors/segment#configuring-incoming-events), [mParticle](/integrations/data-connectors/mparticle#configuring-incoming-events), [RudderStack](/integrations/data-connectors/rudderstack#configuring-incoming-events) and [Census](/integrations/data-connectors/census#configuring-incoming-events) 3. Import from your data warehouse such as [Snowflake](/integrations/data-imports/snowflake#direct-ingestion-from-snowflake), [BigQuery](/integrations/data-imports/bigquery), and [Redshift](/integrations/data-imports/redshift#direct-ingestion). {% callout type="note" %} When processing events, Statsig drops event names that contain this regex/character set: `"\\[\]{}<>#=;&$%|\u0000\n\r` {% /callout %} ## Raw events in console As you ingest custom events, they appear in the *Metrics* section under the **Events** tab in the Statsig console. {% figure %} ![Events tab list view showing recent custom events](/images/metrics/console/172451019-fc450842-a546-4ea0-94a9-d54df8279ed2.png) {% /figure %} You can toggle between a list view or chart view of your events to view the trend line over time. {% figure %} ![Chart view of events showing trend line over time](/images/metrics/console/172461387-a3d42641-2c2c-4128-aabc-fc2b5dba2ed9.png) {% /figure %} Statsig provides two unit identifiers by default: **User ID** and **Stable ID**. Select a unit identifier from the dropdown to view all events that include that unit identifier. Go to the [guide to create custom ID types](/guides/experiment-on-custom-id-types#step-1---add-companyid-as-a-new-id-type-in-your-project-settings) to create additional unit identifiers for your project. {% figure %} ![Unit identifier dropdown for filtering events by user or stable ID](/images/metrics/console/172450890-4a4c95eb-a362-49a6-90ad-68f3460a933f.png) {% /figure %} ## Billing Statsig bills for the two types of raw events described above. Statsig bills only for production environment events. 1. Statsig records an **Exposure Event** for billing when you check a user for assignment in a Feature Gate or Experiment, or check for a value using a Dynamic Config. The following checks are not billed: * Statsig doesn't bill you for duplicate checks for the same user on the same Feature Gate, Experiment, or Dynamic Config within an hour. * Statsig doesn't bill you for checks against Features Gates that are disabled. * Statsig also doesn't bill you for checks for users who are in not participating in an experiment due to the allocation or targeting you have configured. 2. Statsig records a **Custom Event** for billing when you log an event using the Statsig SDK (or import from your data warehouse, or ingest from your data collector). Each event may contain multiple unit identifiers and may be used in multiple experiments. * Statsig bills custom events only once, regardless of the number of experiments in which they are used. ================================================================================ # Auto-generated Metrics URL: https://docs.statsig.com/metrics/raw-event-metrics Updated: 2026-07-11 ================================================================================ Metrics are critical for monitoring the health and usage of your product and the impact of new features and experiments. Statsig automatically generates an "event\_count" metric for each uniquely named **custom event** that you log. Statsig creates auto-generated metrics from production environment events, and creates an event\_count metric for any newly logged custom event within 24 hours of the first logged events. When testing experiments in lower environments (such as development or staging) with **Enable for Environments**, you can track cumulative exposures and metric results collected from those environments. This allows you to validate your experiment setup before launching to production. Statsig prioritizes production data for final Pulse result analyses. This auto-generated metric consists of three elements: 1. **Roll-up Window** - Statsig computes metrics from custom events aggregated over a 24-hour day, with the hours depending on your company's setting. These hours don't change with daylight saving time. This prevents some days from having 23 and 25 hours which can cause a +/-4% change to some metrics on a biannual basis. 2. **Unit Identifier** - While you can record custom events with and without a unique user identifier, Statsig requires a unit identifier (usually a user\_id) to track a user across multiple events and sessions to support Experiments, Pulse (experiment results), and Autotune. If you don't have access to a user\_id when logging a custom event, create a temporary identifier to track users at a session or device-level. 3. **Metric Value** - Statsig automatically computes values for **event\_count**, which measures the number of times an event is triggered. Through October 16, 2024, Statsig also auto-computed values for an **event\_dau** metric that measures the number of unique users who triggered the event. Statsig no longer auto-computes an **event\_dau** metric for every logged event. You can create metrics that function like **event\_dau** through [Custom Metrics](/metrics/custom-dau). Go to the [event\_dau deprecation details](/metrics/deprecate-event-dau) for more information. | Metric | Automatic | Dimensions | Possible Values | Description | Example | | ----------- | --------- | ---------- | -------------------------- | -------------------------------------------------------------------------- | ------------------------------ | | event\_count | Yes | Yes | 0, 1, 2,... | Counts the number of events triggered on a given day | Number of page views | | event\_dau (Legacy support as of October 16, 2024) | Yes | Yes | 0, 1 | Marks each user as 1 or 0 based on whether they triggered the event or not | Unique users who viewed a page | When you select an event type in the **Events** tab, a detailed view of the event appears, including any metrics linked to that event. Select a metric to go to its detail page. {% figure %} ![Event metrics dashboard view](/images/metrics/raw-event-metrics/e4967643-33b4-46b6-9fca-5cee83f8f0a6.png) {% /figure %} ## Event count metric Event count is the simplest metric in your Statsig project. For every event recorded, Statsig automatically creates an **event\_count** metric based on the number of times Statsig receives that event each day. In experiments, Statsig calculates this value for each user. Each user can have values of 0, 1, 2, and so on. You can find an **event\_count** metric for each event type that you record in the Statsig console. The metric name matches the name of the raw event, and the metric type is marked as **event\_count**. {% figure %} ![Event count metric configuration interface](/images/metrics/raw-event-metrics/4b7b30d6-d14d-4430-81a5-4fc467d466d2.png) {% /figure %} ## User accounting metrics Statsig automatically derives **User Accounting** metrics based on any exposure or custom event triggered by a user on a given day. **User Accounting** metrics start with a definition of a daily active user (DAU). By default, Statsig considers a user a DAU if they trigger any event, gate check, or experiment check on a given day. A DAU is a binary designation assigned to every user: a user is either a DAU for a given day or not (inactive). You can customize this DAU definition to exclude or include specific exposure and custom events from your application. | Metric | Automatic | Dimensions | Possible Values | Description | Example | | ----------- | --------- | ---------- | -------------------------- | -------------------------------------------------------------------------- | ------------------------------ | | user | Yes | No | Depends on specific metric | Counts users that trigger any exposure or custom event on a given day | Daily Active Users | Like the **event\_dau** metric (deprecated as of October 16, 2024), Statsig computes **User Accounting Metrics** for each unit ID you define in your Statsig project. For example, given User IDs, DAU counts the number of distinct users that triggered the event. Given Stable IDs, DAU counts the number of distinct devices running your application. Go to [User Accounting Metrics](/metrics/user) for the full list of user accounting metrics and information about customizing the DAU definition. Statsig's default day starts at GMT-8 (Pacific Standard Time), and doesn't follow daylight savings time. {% callout type="note" %} Auto-generated **User Accounting Metrics** aren't supported today for data warehouse ingestions. {% /callout %} ## Metrics catalog The **Metrics Catalog** tab allows you to search and tag your metrics. Tags organize metrics into collections. For example, you can tag metrics focused on a product area, business function, or business objective. You can also create a guardrail metrics collection that teams add to every experiment to confirm there are no unexpected effects in other parts of the business. After you create a tagged collection, you can filter to that set when viewing experiment results. {% figure %} ![Metrics catalog interface](/images/metrics/raw-event-metrics/d22ff8d2-5aea-42b4-86e0-126c25678e15.png) {% /figure %} ## Event DAU metric (legacy support only) {% callout type="warning" %} Refer to the deprecation notice above. {% /callout %} Like **event\_count**, Statsig formerly created an **event\_dau** metric that measures the number of unique users who trigger a specific event on a given day. Each user receives a value of 1 (active) or 0 (inactive) based on whether they triggered an event on that day. An **event\_dau** metric produces a single value per user per day. When aggregated across users for the duration of an experiment, the result is known as the "Event Participation Rate," which represents the probability that a unit is DAU for that event. **event\_dau** metrics are always between 0 and 1 for a user, because they are computed as "# Days with the Event" / "# Days Being Considered". {% callout type="info" %} **Tip** : Sometimes you might want a metric similar to **event_dau** but not normalized by a number of days. If you're looking for a metric that measures if the user has an event over the entire duration of the experiment, try a custom metric set to metric type "Unit Count" with "One-Time Event" rollup mode. {% /callout %} This metric works well in experimentation as it minimizes outliers, has tighter confidence intervals, and enables a simple measure to describe a user's breadth of activity across different events. Statsig computes the **event\_dau** for each unit ID you define in your Statsig project. For example, given User IDs, **event\_dau** counts the number of distinct users that triggered the event. Given Stable IDs, **event\_dau** counts the number of distinct devices using your application. You can find an **event\_dau** metric for each event type that you record with Statsig. The metric name matches the name of the raw event, and the metric type is marked as **event\_dau**. {% figure %} ![Event DAU metric configuration interface](/images/metrics/raw-event-metrics/008034e9-cce8-4e6f-bce1-84a789ac4a19.png) {% /figure %} ================================================================================ # Deprecating Event_dau Metric URL: https://docs.statsig.com/metrics/deprecate-event-dau Updated: 2026-07-11 ================================================================================ ### Deprecation details From Wednesday, October 16, 2024, Statsig stopped auto-generating new event\_dau metrics for incoming events. Statsig continues to auto-generate an event\_count metric for each logged event. {% callout type="note" %} This change only affects Statsig Cloud customers. This change doesn't affect Warehouse Native customers. {% /callout %} * This change doesn't affect any existing event\_dau metrics that you have used in a gate, experiment, dashboard, or other Custom Metrics. * Existing event\_dau metrics that were archived or never used in another config no longer exist. To retain these metrics, refer to the Next steps section below. * Going forward, you must create new event\_dau metrics manually as a Custom Metric. Refer to [this guide](/metrics/custom-dau) to learn how to create a DAU metric. Contact Statsig support if you have questions or concerns about this change. ### Motivation for this change Historically, Statsig auto-generated an event\_count and event\_dau metric for every incoming event. Auto-generating two metrics for every event causes confusion and clutter inside projects. This change produces a cleaner Metrics Catalog and faster Console performance, while retaining the ability to create event\_dau metrics for specific events. ### Next steps To keep any unused event\_dau metrics, mark them as active by performing any of the actions below: * Adding a Tag (RECOMMENDED) * Adding a description * Referencing in a gate/experiment/dashboard These actions mark your unused metric as active, indicating that Statsig shouldn't deprecate the metric. ================================================================================ # Custom Metrics URL: https://docs.statsig.com/metrics/custom-metrics Updated: 2026-07-11 ================================================================================ ## Custom metrics You can create **Custom Metrics** using the custom events you ingest by filtering or aggregating events based on event metadata. Include metadata along with the custom events when you log these events with Statsig. For example, in addition to tracking overall **event\_count** for all events of type *purchase\_event*, you may want to filter these metrics only for events where your users purchase a specific product category. Statsig supports six types of custom metrics: | Metric Type | Description | Examples | | ----------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | Event Count | **Total count of events** filtered by the *Value*, *Metadata*, or *User* properties of an event type | **Add to Cart** event filtered by category type | | User Count | **Number of unique users** that trigger events filtered by the *Value*, *Metadata*, or *User* properties of an event type | **Active Users** based on their views of a product category | | Aggregation | **Sum or Average** of the *Value*, *Metadata*, or *User* property of an event type | **Total Revenue** | | Ratio | **Rates** (e.g. cart conversion rate, purchase rate), **Normalized Values** (e.g. sessions per user, items per cart) | **Cart Conversion Rate**, **Sessions per User** | | Funnel | **Funnels**- funnel of multiple events with conversion tracking | **Sign-up Funnel**, **Checkout Funnel** | | Count Distinct | **Count Distinct**- Number of unique values for a given field, often approximated with sketches | **Unique Songs** listened per user | {% callout type="info" %} Sketch-based count distinct metrics are in Early Access. Reach out to Statsig Support to request access. {% /callout %} The "average" in aggregation is the average of event value (average revenue per purchase per user), not the average of exposed units (average revenue per user). The latter is defined by sum. Refer to the **Metrics 201** topic, [Creating Custom Metrics](/metrics/create), to learn how to create custom metrics for your product. ================================================================================ # Precomputed Metrics URL: https://docs.statsig.com/metrics/precomputed-metrics Updated: 2026-07-11 ================================================================================ ## Importing precomputed metrics ### Importing precomputed metrics from your data warehouse Statsig integrates natively with cloud data warehouses such as [Snowflake](/data-warehouse-ingestion/snowflake), [BigQuery](/data-warehouse-ingestion/bigquery), and [Redshift](/data-warehouse-ingestion/redshift) to ingest your existing metrics for computing experiment results. Go to [Data Warehouse Ingestion](/data-warehouse-ingestion/introduction) to get started. ## Debugging precomputed metrics Statsig creates a metric detail page for all precomputed metrics imported from your data warehouse. These metric detail pages take a few hours to generate after import or ingestion. The fastest way to start viewing and debugging your precomputed metrics is through the **Metrics Logstream** in the **Metrics Catalog** tab within **Metrics**. {% figure %} ![Metrics logstream showing imported precomputed metrics](/images/metrics/ingest/178854882-730ef0d5-8eb2-4344-88ab-33111301e712.png) {% /figure %} The **Metrics Stream** surfaces all ingested, precomputed metrics in real time, enabling you to check metric name, metric value, unit identifier, ID type, and ingestion date. {% callout type="info" %} **Tip** : A common issue is ensuring that precomputed metrics have the correct ID type. Pay extra attention to this column. {% /callout %} The **Metrics Stream** only appears if you are actively ingesting precomputed metrics. If it doesn't appear at the bottom of your **Metrics Catalog**, Statsig likely isn't receiving your precomputed metrics due to a connection issue or an invalid schema. ================================================================================ # User Property URL: https://docs.statsig.com/metrics/user-property Updated: 2026-07-11 ================================================================================ Breaking down Pulse results by user properties (such as Free vs Paid or OS type) lets you slice data by those properties. You set user properties when you create the User object used with the Statsig SDK. Statsig freezes these properties when a user is first exposed to a feature gate or experiment, because the experiment may change those properties (for example, converting a Free user to Paid). {% figure %} ![User property configuration interface](/images/metrics/user-property/226679274-01705500-48ee-44d4-8a5c-cbc49d97d0b2.png) {% /figure %} You can run custom queries on your Pulse results in the **Explore** tab to group by or filter by user properties. {% figure %} ![Pulse results custom query interface](/images/metrics/user-property/226679816-5c7d393f-80e2-4670-8978-fc607b5fbe1a.png) {% /figure %} ================================================================================ # Event Property URL: https://docs.statsig.com/metrics/metric-dimensions Updated: 2026-07-11 ================================================================================ Statsig enables you to break down metrics into a single set of non-overlapping dimensions for detailed analysis. For example, you can break down an event such as **add-to-cart** into product categories such as *sports*, *toys*, *appliances*, *electronics*. To do this, log **add-to-cart** events and provide the product category in the event's **value** field. Refer to the [Statsig SDK reference guide](/client/javascript-sdk#event-logging) to learn more. Statsig enables you to define up to four custom dimensions for an event (one through the **value** field and three through **metadata** fields). To configure these custom event fields, go to *Metrics > Events*, select the event you want to configure, and then go to the **Setup** tab for that event. {% figure %} ![Event property configuration](/images/metrics/metric-dimensions/209886245-c26f569b-a4d4-4882-9d9c-f65f3c1ba43b.png) {% /figure %} Providing custom dimensions with logged events allows you to break down the impact on the total **add-to-cart** events by category in Pulse, as shown below. This breakdown lets you identify the category most impacted by your experiment. {% figure %} ![Pulse results breakdown by product category](/images/metrics/metric-dimensions/162332284-259ea614-8cb6-4c9d-aebd-3e41f9092a64.png) {% /figure %} Statsig recommends keeping the number of distinct dimensions in your logging below 8. Tracking dimensions involves additional computational and storage resources, so Statsig prioritizes notable dimensions that make up a significant portion of your traffic. Statsig tracks dimensions that make up more than 5% of your total event volume. Lower-frequency dimensions generally have more statistical noise and wider confidence intervals, and Statsig buckets these as "Other". Statsig also supports metric dimensions for custom metrics that are **Aggregations**. To set this up, log your dimension as a **value** and the number to be tallied as a metadata field. This is the old metric dimension page. Statsig renamed it to Event Property. ================================================================================ # Ingesting Metrics URL: https://docs.statsig.com/metrics/ingest Updated: 2026-07-11 ================================================================================ Statsig can ingest your precomputed product and business metrics using the data warehouse connector (Metrics Imports). Integrations with [Snowflake](/integrations/data-imports/snowflake), [BigQuery](/integrations/data-imports/bigquery), and [Redshift](/integrations/data-imports/redshift) are supported. Statsig doesn't automatically process these metrics until you mark them as ready, because you might load data out of order. After you finish loading data for a period, mark the data as ready by calling the `mark_data_ready` API. ``` curl --location --request POST ‘https://api.statsig.com/v1/mark_data_ready’ \ --header ‘statsig-api-key: {your statsig server secret}’ \ --header ‘Content-Type: application/json’ \ --data-raw ‘{ “timestamp”: 1647975283, “type”: “metrics” } ``` The timestamp provided should be: * A unix timestamp * The latest point in time for which all metrics have been uploaded * Any future calls to this API with an earlier timestamp are invalid * Statsig doesn't guarantee correct behavior if you provide metrics with an earlier timestamp after calling this API Statsig processes metrics as a full day in the PST timezone, and waits until a full day is marked as ready before processing that day. ## Debugging precomputed metrics All precomputed metrics generate Metric Detail View pages. These Detail View pages take a few hours to generate after ingestion. The fastest way to start viewing and debugging your precomputed metrics is through the **Metrics Logstream** on the **Metrics Catalog** tab within **Metrics**. {% figure %} ![Metrics catalog displaying logstream entries for ingested metrics](/images/metrics/ingest/178854882-730ef0d5-8eb2-4344-88ab-33111301e712.png) {% /figure %} The **Metrics Logstream** surfaces all ingested, precomputed metrics in real time, enabling you to check metric name, value, ID, ID type, and ingestion date. A common issue is ensuring precomputed metrics have the correct ID type, so pay extra attention to this column. You can also include test metrics tagged with **isTest**, which you can toggle on or off for debugging in the **Metrics Logstream**. The **isTest** flag is only available for precomputed metrics ingested through Statsig's APIs. Support for this flag through integrations with Snowflake, BigQuery, and Redshift is coming soon. To mark a batch of metrics as test metrics, set the `isTest` parameter to `true` in the request data, as shown below. ```bash curl \ “https://events.statsigapi.net/v1/log_custom_metric” \ --header “statsig-api-key: <YOUR-SDK-KEY>” \ --header “Content-Type: application/json” \ --request POST \ --data “{"isTest": true, “metrics": [{"user_id": "1237", "metric_name": "test_metric", "id_type": "user_id", "metric_value": 90}, {"user_id": "4568", "metric_name": "ratio", "id_type": "stable_id", "numerator": 3, "denominator": 15}]}” ``` {% figure %} ![Metrics logstream UI showing isTest flag toggle](/images/metrics/ingest/179048336-ebdde45b-17e7-47ad-bb81-01f8f032b978.png) {% /figure %} The **Metrics Logstream** only appears if you are actively ingesting precomputed metrics. If it doesn't appear at the bottom of your **Metrics Catalog**, a connection or schema issue may be preventing Statsig from receiving your precomputed metrics. ================================================================================ # Pulse Metrics URL: https://docs.statsig.com/metrics/pulse Updated: 2026-07-11 ================================================================================ Experiments with Statsig use **Pulse** to compute and communicate results. The metric type affects how Statsig computes and interprets the final result. Statsig aggregates most metric types across all users in the group. For metric types that use ratios, Statsig aggregates only across [participating users](/experiments/interpreting-results/participating-units) (users that have a non-null value for that metric). This article covers the metric types available in experiments and how to interpret their Pulse results. ## Pulse statistics by metric type | Metric Type | Total | Mean | Units | | ------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | | event\_count | Sum of events (99.9% winsorization applied) | Average events per user (99.9% winsorization applied) | All users | | event\_dau | Sum of event DAU (distinct user-day pairs) | Average event\_dau value per user per day. This is called "Event Participation Rate" because it represents the probability a user is DAU for that event. | All users | | sum | Total sum of values (99.9% winsorization) | Average value per user (99.9% winsorization) | All users | | mean | Overall mean value | Overall mean value | [Participating users](/experiments/interpreting-results/participating-units) | | event\_user | Count of distinct users that have had the event. | Average metric value per user per day. Depending on Rollup Mode, can be a one-time event or daily participation rate. | All users | | ratio | Not shown | Overall ratio: sum(numerator values)/sum(denominator values) | [Participating users](/experiments/interpreting-results/participating-units) | | funnel | Not shown | Overall ratio: sum(numerator values)/sum(denominator values) | [Participating users](/experiments/interpreting-results/participating-units) | | user: dau, wau, mau\_28day | Not shown | Average metric value per user per day. The probability that a user is xAU | All users | | user: new\_dau, new\_wau, new\_mau\_28day | Count of distinct users that are new xAU at some point in the experiment | Fraction of users that are new xAU | All users | | user: retention metrics | Overall average retention rate | Overall average retention rate | [Participating users](/experiments/interpreting-results/participating-units) | | user: L7, L14, L28 | Not shown | Average L-ness value per user per day | All users | | count\_distinct | Total number of unique values | Average number of unique values per user | All users | **Some example metric breakdowns in Pulse:** {% figure %} ![pulse 1](/images/metrics/pulse/aa7e1063-6473-4e4e-9ca4-7074f5a0c450.png) {% /figure %} {% figure %} ![pulse 2](/images/metrics/pulse/7b0b1d99-c720-480e-8671-f5f696485500.png) {% /figure %} ## Event count and event DAU in Pulse {% callout type="warning" %} **event_dau Legacy Support** : event_dau metrics are now in legacy support only and are no longer created for new events. Existing event_dau metrics will continue to be available for any of your new experiments and will continue to be computed daily. For all new events, you should create an event_user metric to measure daily active users. {% /callout %} From [Metrics 101](/metrics/101) and [Auto-generated Metrics](/metrics/raw-event-metrics), * [**event\_count**](/metrics/raw-event-metrics#event-count-metric) measures the volume of the activity based on count of events triggered * [**event\_dau**](/metrics/raw-event-metrics#event-dau-metric) measures unique daily users who triggered a given event For example, the table below shows the **event\_count** and **event\_dau** metrics for two event types, *Page Views* and *Add to Cart*, for three users over three days. {% figure %} ![Event count and event DAU metrics table](/images/metrics/pulse/187719553-c7e5c186-5dfe-4521-8bfb-1bb4b8cdb38d.png) {% /figure %} Over the duration of an experiment, Pulse results measure the change in: * the **mean** event\_count, or the average event count per user * the **mean** event\_dau, or average active days per user: Statsig calls this the **Daily Event Participation Rate** For example, the table below shows the **Total event\_count**, **Total Units**, and **Mean event\_count** over the same three days as above, now in the context of an experiment. {% figure %} ![Experiment metrics table showing total event count and mean values](/images/metrics/pulse/187721781-3240ebc6-43ae-4fd8-ac44-c3493308e127.png) {% /figure %} The table below shows the **Total event\_dau**, **Total Units**, and **Mean event\_dau** over the same three days of the experiment. Alice was active on three days for the *Page View* event and on one day for the *Add to Cart* event. Alice's average event\_dau is 3/3 for the *Page View* event and 1/3 for the *Add to Cart* event. Alice's **daily participation rate** is 1.00 for the *Page View* event and 0.33 for the *Add to Cart* event. Statsig aggregates the average event\_dau for each user in the experiment, with each user weighted equally. {% figure %} ![Event DAU metrics table showing daily participation rates](/images/metrics/pulse/187721834-b8e94f15-f3ee-4584-924b-96e424ddcd0c.png) {% /figure %} To measure engagement changes for a call-to-action link or button, use event\_count to measure the change in average clicks per user, and use event\_dau to measure the change in the daily participation rate for the click. {% callout type="info" %} **Event Count and Event DAU in Custom Metrics** : When creating a custom ratio metric, use event_count to include all events (counting all events triggered by the same user). Use event_user (or event_dau, if available) to count unique active users on a given day (all events triggered by the same user are counted as one). {% /callout %} ## Winsorization To reduce the impact of outliers, Statsig caps *event\_count* and *sum* metric types at the 99.9th percentile by default. This reduces the risk that bots and extreme values skew experiment results. The 99.9th percentile threshold is computed using all non-zero and non-null metric values. All values exceeding this limit are replaced with the threshold value. Warehouse Native (WHN) allows for more customization of winsorization by metric and by percentile. ## Frequently asked questions **1. Can a ratio metric move in the opposite direction from both the numerator and denominator metrics?** Yes, the ratio can rise while both the numerator and denominator metrics decline. This happens when the denominator falls more than the numerator. Statsig recommends tracking the numerator and denominator as independent metrics when monitoring a ratio metric. Ratio metrics are often subject to statistical noise and can be difficult to use for obtaining a statistically significant result. **2. For ratio metrics, how does Statsig determine *participating users*?** Statsig computes ratio metrics only for users with a non-zero value in the denominator. A user must trigger the denominator event on a given day to be included in the daily ratio. Statsig excludes users who don't trigger the denominator event during an experiment from the test vs. control comparison. **3. What is the difference between One-Time Event and Daily Participation Rate?** This distinction is relevant only in the context of an experiment. Daily participation rate counts the number of *days* a user has that event, divided by the number of *days* the user has been in the experiment. One-time event is a binary metric that checks whether the user has that event *at least once* during the experiment. ================================================================================ # Local Metrics URL: https://docs.statsig.com/metrics/local-metrics Updated: 2026-07-11 ================================================================================ ## How Local Metrics work Local Metrics are metrics scoped to an individual config (a specific experiment or gate). Statsig creates them within that config to capture how the metric trends in that context, without adding the metric to the project-wide Metrics Catalog. ## Creating a Local Metric You can create a Local Metric from two places within your config: 1. **Setup**: While setting up your gate or experiment and adding Primary, Secondary, or Monitoring metrics, you can add a metric that doesn't yet exist to your Scorecard. Select **+ Create New Local Metric** to enter the Local Metric creation flow. {% figure %} ![Setup tab showing + Create New Local Metric button](/images/metrics/local-metrics/2a249684-56a9-4c63-b2b2-7870efd89b76.png) {% /figure %} 2. **Pulse**: After you start your gate or experiment rollout, you can still create and add a new Local Metric to your scorecard. From your Scorecard in Pulse, select **Edit Primary Metrics** (or Secondary or Monitoring metrics depending on where you want to add your new Local Metric), and then **+ Create New Local Metric**. {% figure %} ![Pulse scorecard with Edit Primary Metrics and Create New Local Metric options](/images/metrics/local-metrics/b718c3b7-9696-4af0-bbc5-48fef3cfa1d3.png) {% /figure %} Entering the Local Metric creation flow from either entry point starts the Local Metric creation wizard. The wizard lets you select events, add filters, and preview the output values. {% figure %} ![Local metric creation wizard with event selection and filters](/images/metrics/local-metrics/452d9efe-2706-4d47-aee6-48c8f6288e8f.png) {% /figure %} When you are ready to save your Local Metric, you can choose to save it to either the Primary/ Secondary Metrics section of your Scorecard (for experiments), or the Monitoring Metrics section of your feature gate rollout. {% figure %} ![Local metric placement selector for primary scorecard](/images/metrics/local-metrics/50bc5742-2f2a-4147-9cb3-70658d6391da.png) {% /figure %} {% figure %} ![Monitoring metrics section showing new local metric entry](/images/metrics/local-metrics/ae3932df-f81b-4fed-9d23-b2b196ac83bc.png) {% /figure %} After you create a Local Metric, you can select it in your Scorecard to view its configuration in the Local Metric wizard. ## Types of Local Metrics The types of Local Metrics you can create are similar to Custom Metrics (accessible through the *Metrics* tab), with a few exceptions. Here are the supported types of Local Metrics: | Metric Type | Description | Examples | |-------------|-----------------------|---------| | Event Count | **Total count of events** filtered by the *value* and *metadata* properties of an event type | **Add to Cart** event filtered by category type | | User Count | **Number of unique users** that trigger events filtered by the *value* and *metadata* of an event type| **Active Users** based on their views of a product category | | Aggregation | **Sum or Average** of the *value* of an event type | **Total Revenue** | | Ratio | **Rates** (e.g. cart conversion rate, purchase rate), **Normalized Values** (e.g. sessions per user, items per cart) | **Cart Conversion Rate**, **Sessions per User** | The one type of Custom Metric that you can't (yet) create as a Local Metric are funnels. ## Lifecycle of Local Metrics By default, Local Metrics are scoped to the config they are created in and exist only for the lifecycle of that config. After you make a decision on your experiment or launch your feature gate, Statsig no longer computes the Local Metric. Local Metrics don't appear in your *Project Metrics Catalog* and aren't searchable in top-line search. Converting a Local Metric into a Metrics Catalog metric isn't yet supported, but this conversion flow is coming soon. In the meantime, you can recreate the same metric definition as a Custom Metric in the *Metrics Catalog* if you want the metric to exist outside the scope of your gate or experiment. ================================================================================ # Metrics Dashboard URL: https://docs.statsig.com/metrics/console Updated: 2026-07-11 ================================================================================ Metrics are available for all unit types enabled in the project. Statsig provides User ID and Stable ID by default; you can add others following [these steps](/guides/experiment-on-custom-id-types#step-1---add-companyid-as-a-new-id-type-in-your-project-settings). Select a unit type from the dropdown to view event DAU and user accounting metrics calculated for that unit type. {% figure %} ![Metrics unit type selection](/images/metrics/console/172450890-4a4c95eb-a362-49a6-90ad-68f3460a933f.png) {% /figure %} ## Events The Metrics console allows you to visualize all the events that you have logged in Statsig. The **Events** tab shows all the events, including a real-time stream of events as they come in. {% figure %} ![Events tab view](/images/metrics/console/172451019-fc450842-a546-4ea0-94a9-d54df8279ed2.png) {% /figure %} You can toggle between a list view or chart view of your events to view the trend line over time. {% figure %} ![Events chart view](/images/metrics/console/172461387-a3d42641-2c2c-4128-aabc-fc2b5dba2ed9.png) {% /figure %} From here you can drill into each event to see a detailed view of the logs, broken down by each unique value that was logged. {% figure %} ![Event detailed view](/images/metrics/console/172462231-ff2f3063-0c4e-49fd-af17-7147bd09d3d1.png) {% /figure %} ## Metrics Catalog The **Metrics Catalog** tab lets you search and tag your metrics, as well as [create custom metrics](/metrics/create). Tags let you organize metrics and create collections grouped by product area, business function, business objective, or any other common attribute. You can also create a collection of guardrail metrics that teams check in every experiment to detect unexpected effects in other parts of the business. After you create a tagged collection, you can filter your experiment results to that collection and focus on the metrics that matter. {% figure %} ![Metrics catalog](/images/metrics/console/172462680-68a6de4e-17bf-4b11-920d-6d7830551012.png) {% /figure %} Similar to the **Events** tab, you can toggle between a list view or chart view of your metrics to view the trend line over time. {% figure %} ![Metrics chart view](/images/metrics/console/172462947-877bbcc7-46b3-45cd-ac57-d0dc2c949d7d.png) {% /figure %} ================================================================================ # Metrics 201 - Creating Custom Metrics URL: https://docs.statsig.com/metrics/201 Updated: 2026-07-11 ================================================================================ In this guide, you'll learn to create your own metrics and organize them as your project grows. You'll also be able to customize the definition of your daily active users (DAU). 1. [Creating Custom Metrics](/metrics/create) for your product in the Statsig console 2. [Creating Metric Tags](/metrics/create-metric-tags) to organize the metrics in your Statsig Project 3. [Customizing the DAU Definition](/metrics/user) that Statsig uses to compute [User Accounting](/metrics/raw-event-metrics#user-accounting-metrics) metrics. ================================================================================ # Creating Custom Metrics URL: https://docs.statsig.com/metrics/create Updated: 2026-07-11 ================================================================================ Statsig computes custom metrics from your raw events. To create custom metrics, navigate to **Metrics** from the left-hand navigation panel, then to the **Metrics Catalog** tab. Select the **Create** button. {% figure %} ![Create custom metrics interface](/images/create-metric.png) {% /figure %} Statsig supports six types of custom metrics: | Metric Type | Description | Examples | | -------------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | Event Count | **Total count of events** filtered by the *Value*, *Metadata*, or *User Object* properties of an event type | **Add to Cart** event filtered by category type | | User Count | **Number of unique users** that trigger events filtered by the *Value*, *Metadata*, or *User Object* of an event type | **Active Users** based on their views of a product category | | Aggregation | **Sum or Average** of the *Value*, *Metadata*, and *User Object* property of an event type | **Total Revenue** | | Ratio | **Rates** (e.g. cart conversion rate, purchase rate), **Normalized Values** (e.g. sessions per user, items per cart) | **Cart Conversion Rate**, **Sessions per User** | | Funnel | **Funnels**- funnel of multiple events with conversion tracking | **Sign-up Funnel**, **Checkout Funnel** | | Count Distinct | **Count Distinct**- Number of unique values for a given field, often approximated with sketches | **Unique Songs** listened per user | Statsig computes custom metrics daily for your **Metrics** dashboard, and rolls them up for the duration of the experiment in **Pulse Results** for your Feature Gates and Experiments. After you create a custom metric, it doesn't populate until the next day and doesn't backfill to previous days. Statsig calculates the metric only from the creation date forward. Statsig lets you set a daily **max cap** for specific metrics. You can define maximum caps for various unit types associated with a metric. Statsig automatically adjusts any value exceeding the cap down to match it. For example, if purchases over $10,000 per day on your e-commerce platform shouldn't skew analysis, Statsig adjusts any transaction above that threshold down to the limit. Capped metrics are available for event count and aggregation (sum) metric types. When creating a custom metric, you can preview the metric’s value at the bottom of the page. Select **View Output Preview** to see what the metric would have looked like based on its component values over the last 7 days, in both chart and table form. {% figure %} ![Aggregation metric setup screen with output preview chart and table](/images/metrics/create/0869e1bf-8fea-407e-8a5f-8b97860b3867.png) {% /figure %} ## Examples ### 1. Event count metrics This example sets up a custom event metric to count the **number of add\_to\_cart events** filtered by a metadata property called *value*, which carries the price of the item added to the cart. Because the ID Type is set to *userID*, Statsig computes this metric as part of the test group that Statsig assigns the corresponding user to in an experiment. {% figure %} ![Event count metric filtering add_to_cart events with value greater than 100](/images/metrics/create/95489e9b-36f2-4704-9970-da675a8a3d2e.png) {% /figure %} If you select the ID Type as *stableID*, Statsig computes this metric in the test group that Statsig assigns the corresponding device to in an experiment. When you select more than one ID Type, Statsig computes this metric for each ID Type you specify. ### 2. User count metrics The example below creates a metric to count the **number of unique users** who viewed a product in the *toys* category that was priced under $10. {% figure %} ![User count metric configuration counting viewers of toys category under ten dollars](/images/metrics/create/ae270d11-d3de-4f34-85e5-a67ad9c7cd9e.png) {% /figure %} **Time Window** Statsig offers three time window options for user count metrics: * **Daily Participation Rate**: Counts the total number of days a user has the selected event, divided by the number of days the user is in the experiment. The result is a value between 0 and 1, representing the probability of a user having the event on any given day. Works best for events expected to occur repeatedly for a given user. * **One Time Event**: Checks if a user has the selected event at least once during their time in the experiment. The result is a binary metric with value 0 or 1 for each user. Ideal for events expected to occur only once per user, such as sign-up events. * **Custom Attribution Window**: Lets you define a custom window after exposure to count an event toward a metric calculation. ### 3. Aggregation metrics The example below shows a **Total Revenue** metric that sums the *value* associated with all purchase events. {% figure %} ![Sum aggregation configuration for Total Revenue metric](/images/metrics/create/f0d4d21b-08ae-405d-a156-4d29f96ab298.png) {% /figure %} **\[Early Access] Currency Sum** eCommerce customers often span multiple currency types and log payments in the currency the user actually pays. Currency sums convert payments recorded in different currencies to a single common currency. You must log two metadata fields in your event: a currency code and a value. Example metadata: `_{"currency_code": "USD", "currency_value": 123.45}_` Statsig supports currency sums upon request. Ask in [Slack](https://statsig.com/slack). ### 4. Ratio metrics The example below shows creating a **Cart Conversion Rate** metric. Statsig uses unique users who triggered the *purchase event* as the numerator, and unique users who triggered the *add to cart* event as the denominator. When calculating the numerator, Statsig includes only users who also had the denominator event on the same day. A user who has only a *purchase event* on a given day without an *add to cart* on that same day doesn't count toward the numerator. {% figure %} ![Ratio metric setup for cart conversion rate](/images/metrics/create/9f8a671e-ac1f-4e2f-92cb-d154e0daecac.png) {% /figure %} This pattern also applies to **click-through rates (CTR)** in any part of a step-wise product journey (funnels). Statsig recommends using unique users in both the numerator and denominator for these metrics. For example, a user who reloads a page multiple times but clicks only once corresponds to a 100% CTR (1 out of 1). A user who loads a page once but clicks a button multiple times also counts as 1 out of 1. This approach handles cases where users see a key button, such as "Sign-up," multiple times a day, and Statsig counts a single click as a success. The example below shows creating an **Items per Cart** metric. You can track the number of unique items added to a cart by logging an *add\_to\_cart* event for each item. For the numerator, select total event count. For the denominator, select unique users. Because Statsig computes this metric daily and only for users with a non-zero denominator, it can generate ratios such as 1/1, 2/1, and 5/1 for individual users. When aggregated, this translates to an average such as 10/4 = 2.5 items per cart per day. {% figure %} ![Items per cart metric configuration using event counts](/images/metrics/create/cf6e6777-a154-4eeb-9a2d-81893810a31d.png) {% /figure %} {% callout type="info" %} **A Word of Caution** : In experimentation, ratio metrics are a frequent source of misleading information. It's possible to see an increase in **click through rate** alongside a net *decrease* in total clicks (the opposite may also happen). This situation can occur if the number of unique users viewing a button (denominator) decreases. As a best practice, Statsig recommends tracking the numerator and denominator as independent metrics when monitoring ratio indicator. Ratio metrics are often subject to statistical noise and can be tricky to use for obtaining a statistically significant result. In addition, for the numerator in ratios, Statsig excludes units that don't have a denominator value. {% /callout %} ### 5. Funnel metrics You can create a custom funnel metric from either the Custom Metrics Creation wizard in the Metrics Catalog or through the **Charts** tab. {% figure %} ![Funnel metric creation wizard](/images/metrics/create/86914826-b8ce-4b4b-9514-39f31d05687a.png) {% /figure %} {% callout type="info" %} **Important Note** : Statsig handles funnel metrics differently between the Cloud and Warehouse Native platforms. Funnel metrics using the Cloud platform are *always* unordered, meaning that funnel steps can be completed in any order, and they have a time window of *one day* . This means that for the metric to record a completion, all steps must be triggered by a user within 24 hours. In contrast, funnel metrics on Warehouse Native have the option to be set with strict ordering and custom time windows. {% /callout %} ### Components of funnel metrics Funnel metrics have a few components: 1. **Lineage**: Surfaces the events used to generate the funnel 2. **Metric Value**: Metric value represents the overall funnel conversion rate, or the percentage of users who complete a funnel (trigger the end event) relative to all users who start the funnel (trigger the starting event) 3. **Conversion rate between stages**: This set of metrics track the percentage of users who triggered an event N relative to all users that triggered event N-1 in the funnel {% figure %} ![Funnel lineage and stage conversion breakdown view](/images/metrics/create-user-funnels/172446711-5511e394-b353-4d38-97f1-1b681b67042b.png) {% /figure %} After Statsig creates and populates funnels, you can view your funnel metric like any other metric in Pulse. You can also expand the funnel metric to view Pulse performance at each step in the funnel. In the example below, the **Square** variant shows a lift in the **overall funnel conversion rate**. Expanding the metrics to examine the entire funnel reveals two key insights: * Both the **Square** and **Circle** variants show a lift in top-of-funnel DAU (*Land Page View Start DAU*). However, only the **Square** variant shows statistically significant increase in end-of-funnel DAU (*Purchase Event End DAU*). * The overall funnel conversion rate improvement for **Square** is primarily due to the higher conversion from *Checkout Event* to *Purchase Event* stages in the funnel. {% figure %} ![Funnel experiment results showing Square variant performance](/images/metrics/create-user-funnels/148440643-8e8a24bd-934d-4100-a15a-abcbcc4bb11c.png) {% /figure %} ### 6. Count distinct metrics {% callout type="info" %} Sketch-based count distinct metrics are in Early Access. Reach out to Statsig Support if you would like Statsig to enable this. {% /callout %} * **What it is:** A high-performance way to estimate the number of distinct values using compact sketch data structures. Example use cases include unique songs per user or unique products purchased per user. * **Benefit:** Much faster to compute and uses significantly less memory compared to exact distinct counting, especially at high cardinality. ### Custom metrics and dimensions For custom metrics composed of a single event (event count, event DAU, aggregation, and so on), Statsig automatically includes the dimensions of the source event in the custom metric and exposes them as dimensions of that metric. If your custom metric is composed of two or more different events, Statsig doesn't include the dimensions of those source events in the custom metric, and they aren't queryable through the "Explore" tab in Pulse Results. ================================================================================ # Custom DAU Metric Creation Guide URL: https://docs.statsig.com/metrics/custom-dau Updated: 2026-07-11 ================================================================================ ## Custom DAU metric creation guide This guide covers the steps to create a custom DAU metric. ### Step 1: Navigate to the metrics catalog Go to the *Metrics Catalog* in the left navigation bar and select **Create**. {% figure %} ![Picture1.png](/images/custom-metrics/Picture1.png) {% /figure %} {% figure %} ![Picture2.png](/images/custom-metrics/Picture2.png) {% /figure %} ### Step 2: Name your metric Enter a name for your metric. For example: "Add to Cart DAU". {% figure %} ![Picture3.png](/images/custom-metrics/Picture3.png) {% /figure %} ### Step 3: Choose the metric type To create a DAU metric, select **Unit Count** as the metric type. {% figure %} ![Picture4.png](/images/custom-metrics/Picture4.png) {% /figure %} ### Step 4: Select the ID type Select the ID type to count. For example, select **User ID**, which is the most common ID type for DAU metrics. {% figure %} ![Picture5.png](/images/custom-metrics/Picture5.png) {% /figure %} ### Step 5: Choose the event Select the event to use for your metric. For example, select the **Add to Cart** event. {% figure %} ![Picture6.png](/images/custom-metrics/Picture6.png) {% /figure %} ### Step 6: Submit the metric Submit the metric. Your custom DAU metric is now created. {% figure %} ![Picture7.png](/images/custom-metrics/Picture7.png) {% /figure %} ================================================================================ # Archiving and Deleting Metrics URL: https://docs.statsig.com/metrics/archiving-metrics Updated: 2026-07-11 ================================================================================ Statsig offers two ways to manage the end-of-life for your metrics. * **Archiving a metric**: Statsig stops computing the metric, but retains its history. Use archiving when a metric is no longer relevant but you want to preserve its historical record. For example, archive older versions of a metric that continues to evolve. * **Deleting a metric**: Statsig removes the metric and its history completely. Use deletion when you made a mistake, logged or imported an irrelevant metric, or created a more accurate replacement. Examples include an incorrect definition, incorrect name, or a duplicate metric. ## Archiving metrics ### Archiving a metric There are two ways to archive a metric: 1. In your Metric Catalog, select the metric(s) you want to archive to see a toolbar of options appear to **Archive**, **Compare**, or **Tag**. Select the **Archive** icon. {% figure %} ![Bulk Archive](/images/metrics/archiving-metrics/215638876-3c2ae682-8db8-4dc7-9c14-3b4d7185f57e.png) {% /figure %} 2. In the Metrics Detail View page, select the "..." in the upper right-hand corner, and select **Archive**. {% figure %} ![Archive](/images/metrics/archiving-metrics/215639240-321b4e3f-d9c7-4f7d-ab77-9e18ba1c5867.png) {% /figure %} After you select Archive, Statsig checks if this metric is used in any feature gates, experiments, or other metrics. {% figure %} ![Archive dependencies](/images/metrics/archiving-metrics/215640348-b210eb9e-5475-4853-869f-7a9f66375f0a.png) {% /figure %} Feature gate or experiment dependencies appear as soft warnings (no action required). Metric dependencies require you to remove the dependency before proceeding, because archiving a metric stops its computation and would affect other metrics that depend on it. After you remove all metric dependencies, a 24-hour grace period begins during which you can undo the archival. The Metrics Detail View page shows a banner at the top indicating the start of the grace period. After the 24-hour grace period, Statsig stops computing the metric. The Metric Detail View page updates with a banner confirming the metric is archived, and Metric Value changes to an inactive state. ### Implications of archiving a metric *As soon as you click **Archive**,* * A 24-hour grace period starts * Owners of experiments and gates using this metric receive an email notification about potential impact *After the 24-hour grace period has ended,* * Statsig stops computing archived metrics. * Archived metrics won't show up in your Metric Catalog search. To access all archived metrics, go to the last page(s) of your Metrics Catalog. * Statsig removes archived metrics from Pulse, including any scorecard entries in experiments or Monitoring Metrics sections of Feature Gates ### Unarchiving a metric If you mistakenly archived a metric, you can undo the archival. * *During* the 24-hour grace period: Select "undo" on the archival banner at the top of the Metrics Detail View page. Because the metric is still being computed, unarchiving restores it to your Metrics Catalog and to any experiment results that include the metric. {% figure %} ![Archive Grace Period](/images/metrics/archiving-metrics/215640435-412375f7-398b-4bef-9495-cc20d1805769.png) {% /figure %} * *After* the 24-hour grace period: Either a) navigate to the last few pages of your Metrics Catalog, select the archived metric(s) you want to unarchive to display a toolbar of options, and select the **Unarchive** icon, or b) in the Metrics Detail View page of the archived metric, select **Unarchive** in the archival banner. {% figure %} ![Archive after grace period](/images/metrics/archiving-metrics/215640543-7cb05d46-e61d-4cf4-a07c-eb76c9f50e36.png) {% /figure %} Because the grace period has ended and metric computation has already stopped, the metric's calculation restarts from scratch and Statsig doesn't restore historical data. ### Auto-Archival To reduce metric clutter, Statsig provides a default auto-archival feature that removes metrics not in use for at least 60 days. Metric creators and admins receive a warning about a week before archival. At that point, they can extend the metric for another 60 days or mark it as permanent. The entire process is outlined below: {% figure %} ![State graph](/images/metrics/archiving-metrics/c7912507-636f-4f33-9555-70180dfd205e.png) {% /figure %} #### How does Statsig measure activity? Statsig counts the number of times the custom metric is used in one or more of the following components: 1. *Scorecard*: Used in experiments, pulse reports, holdouts, etc. 2. *Dashboards*: Used to build dashboards and other analytical assets 3. *Other Metrics*: Used to calculate other composite metrics If a metric is in use, Statsig considers it active. You can see a summary of a metric’s usage on the metric’s main page: {% figure %} ![Metric References](/images/metrics/archiving-metrics/6f7eb3db-399a-45c8-be19-794e89dd349d.png) {% /figure %} Statsig also detects: (1) any edits to the metric, including changing setup fields or restoring a previous version; (2) adding tags to the metric; or (3) creating or modifying an alert on the metric. Any of these interactions restarts the 60-day clock. #### How to pause / stop auto-archiving Any tracked action (such as adding the metric to a scorecard) also removes the metric from the archival queue. To pause the archival process, extend the metric for another 60 days. You can also mark the metric as permanent, which removes it from the auto-archival process entirely. Reserve permanent status for your most important and widely reused metrics. {% figure %} ![Banner](/images/metrics/archiving-metrics/d7378d7b-a588-496b-ae35-24f38c6d5b6a.png) {% /figure %} You can also mark a metric as permanent by selecting the setup dropdown from the metrics page and choosing **Mark as Permanent**. {% figure %} ![Dropdown](/images/metrics/archiving-metrics/2a570a92-76d6-41b3-aea5-ebdd53469856.png) {% /figure %} To turn off auto-archiving entirely for your project, navigate to the Project Settings page. {% figure %} ![Project Settings](/images/metrics/archiving-metrics/74cd5575-e1bb-4f69-87f9-1feece5eb73f.png) {% /figure %} ## Deleting metrics ### Deleting a metric To delete a metric, navigate to the Metrics Detail View page of the metric, select the "..." in the upper right-hand corner, and select **Delete**. {% figure %} ![Delete metric](/images/metrics/archiving-metrics/215641202-82f23bac-f620-4d4a-8c32-fe64a4ffc06c.png) {% /figure %} After you select Delete, Statsig checks if the metric is used in any feature gates, experiments, or other metrics. Feature gate or experiment dependencies appear as soft warnings (no action required). Metric dependencies require you to remove the dependency before proceeding, to avoid breaking other metrics that depend on it. {% figure %} ![Dependencies](/images/metrics/archiving-metrics/215641295-55c8dc10-7199-4505-ba0e-d02299fb371a.png) {% /figure %} After you remove all metric dependencies, a 24-hour grace period begins during which you can undo the deletion. The Metrics Detail View page shows a banner at the top indicating the start of the grace period. {% figure %} ![Undo delete](/images/metrics/archiving-metrics/215641634-1c70e688-0fe9-4cac-80bb-d3faeedcc0ed.png) {% /figure %} ***Metric deletion can't be undone after the grace period.*** ### Implications of deleting a metric *As soon as you click **Delete**,* * A 24-hour grace period starts * Owners of experiments and gates using this metric receive an email notification about potential impact *After the 24-hour grace period has ended,* * Statsig permanently removes deleted metrics and their history, and you can't restore them. * Statsig removes deleted metrics from Pulse, including any scorecard entries in experiments or Monitoring Metrics sections of Feature Gates ================================================================================ # Creating Metric Tags URL: https://docs.statsig.com/metrics/create-metric-tags Updated: 2026-07-11 ================================================================================ ## Tagging metrics To create a metric tag, click on **Metrics** in the left hand navigation menu, and click on the [Metrics Catalog](https://console.statsig.com/4TLCtqzctSqusYcQljJLJE/metrics/metrics_catalog) tab. {% figure %} ![Metrics catalog navigation](/images/metrics/create-metric-tags/172474361-3c47eb33-0d44-457f-b3be-dc61cd085cb8.png) {% /figure %} In the **Metrics Catalog** tab, click on the **Tags** filter icon in the upper right-hand corner, and then tap **Manage** in the drop-down. This takes you to the tag manager within Project Settings for all tags in your project. {% figure %} ![Tags filter management](/images/metrics/create-metric-tags/172474897-7eb84784-ff21-4461-87cd-6a0c45bb7ef7.png) {% /figure %} Tap on the **Create New Tag** in the upper right-hand corner of the tag manager tab to create a new tag. Enter a name and description for your new tag. Click on **Create**. {% figure %} ![Create new tag dialog](/images/metrics/create-metric-tags/154555162-9309a3f5-0278-4a04-8620-245408e5d6d0.png) {% /figure %} After creating the tag, add it to any metric using the **+** icon when you hover on the metric. To see which metrics are associated with a given tag, refer to the **Tags** tab within Project Settings. {% figure %} ![Metrics catalog with tag management interface](/images/metrics/create-metric-tags/154557097-3a2e07d9-1bb4-4cc5-9d03-c96b61f34296.png) {% /figure %} After tagging your metrics, you can filter **Metric Lifts** in Pulse by the tagged collection to focus on the results that matter most. {% figure %} ![Pulse results showing tagged metric lifts](/images/metrics/create-metric-tags/154556623-79b2b198-b24d-40fa-bcf8-0feccef70f29.png) {% /figure %} ## Core tag Every Statsig project includes an auto-created **Core** tag pre-populated with a subset of User Metrics. User Metrics are classic growth metrics (such as MAU, WAU, DAU, L7, and 28-day retention) that Statsig calculates daily from your logged events. The following User Metrics are added to the Core tag by default (you can remove them or replace them with more relevant business metrics): * DAU/ WAU/ MAU * New users * Daily/ Weekly/ Monthly user stickiness The Core tag serves as a collection of your most important business metrics to monitor with every new feature rollout and experiment. The Core tag is automatically added to all new feature gates and experiments at the point of creation. In an experiment, the Core tag is added to the Scorecard, under **Secondary Metrics**. In a feature gate, the Core tag is added to the **Monitoring Metrics** section. ## Configuring your core metrics Because the Core tag receives special treatment, curate the metrics in this tag collection during project setup. Go to **Metrics** → **Metrics Catalog** and add the **Core** tag to any relevant metrics. To see which metrics already have the **Core** tag applied, filter by the **Core** tag in the Metrics Catalog by tapping the filter icon in the upper right-hand corner, then **Tags**, then selecting the **Core** tag. Remove the Core tag from any unwanted metrics in the filtered list. {% figure %} ![Core metrics filter view](/images/metrics/create-metric-tags/206882708-399dbb67-098e-463c-aa56-df62fc5c780a.png) {% /figure %} ## Setting rollout alert thresholds for your core metrics When rolling out a new feature or experiment, you may want to be notified if the rollout negatively impacts a key business metric. Statsig lets you set Rollout Alerts at the per-metric level. You receive an alert if any running experiment or feature gate regresses the metric beyond the set threshold. Because Core Metrics are important, configure Rollout Alerts for Core Metrics through the Metrics Catalog. For more on Rollout Alert configuration, refer to [Rollout Alerts](/metrics/rollout-alerts). ================================================================================ # User Accounting Metrics (DAU/WAU/etc.) URL: https://docs.statsig.com/metrics/user Updated: 2026-07-11 ================================================================================ ## DAU (daily active user) definition Statsig automatically creates a standard set of user accounting metrics to track common product-wide engagement metrics like daily active users (DAU), new users, and retention. Statsig also tracks more sophisticated metrics like L-ness, retention, and stickiness. All Standard User Accounting Metrics rely on a company-wide definition of a daily active user. By default, Statsig defines a DAU as a [user that the Statsig SDK has logged](/sdks/user) any custom event (`log_event`) for. You can [customize the DAU definition](#customizing-the-dau-definition). {% callout type="info" %} **Warehouse Native Users** You're viewing a feature designed for Statsig Cloud users. Warehouse Native customers typically have multiple datasets that uniquely affect how they define active users. Refer to [Retention metrics in Warehouse Native](/statsig-warehouse-native/metrics/retention) . {% /callout %} ## Notation and conventions * The first day a new user was active is Day Zero (D0), with subsequent days labeled D1, D2, D3, and so on. * A weekly active user is someone active within the last 7 days (0-6 days), including users active on all 7 days and users active on only a single day. The same definition applies to a monthly active user. * A user with a single session that spans midnight (with qualifying events at 11:59 PM and 12:01 AM) qualifies as a daily active user on both days. * Statsig reserves the right to limit tracking to 100M unique IDs per unit type per year. ## Default user accounting metrics in Statsig ### General user metrics | **Metric Name** | **Type** | **Description** | |-----------------------------|--------------------|-------------------------------| | `daily_active_user` | Count | Users who were active on a given calendar day (DAU). | | `weekly_active_user` | Count | Users who were active at least once in the past 7 days (WAU). | | `monthly_active_user` | Count | Users who were active at least once in the past 28 days (MAU). | | `new_dau` | Count | Users who became active for the first time on a specific day. | | `new_wau` | Count | Users who became active for the first time within the last 7 days. | | `new_mau_28d` | Count | Users who became active for the first time within the last 28 days. | | `daily_user_stickiness` | Stickiness (Rolling) | Fraction of the prior day's users who are active on the next day. Rolling day-to-day repeat engagement (**not** DAU/MAU or DAU/WAU). | | `weekly_user_stickiness` | Stickiness (Rolling) | Fraction of the previous week's users who have been active within the last 7 days. This metric tracks rolling week-over-week repeat engagement (**not** WAU/MAU). The previous week is defined as 8-14 days before the metric date. | | `monthly_user_stickiness` | Stickiness (Rolling) | Fraction of the previous month's users who have been active within the last 28 days. This metric tracks rolling month-over-month repeat engagement (**not** DAU/MAU). The previous month is defined as 29-56 days before the metric date. | | `d1_retention_rate` | Retention (Rolling)| % of new users from 1 day ago who were active at least once today. Rolling Day 2 window retention. | | `WAU @ D14 Retention Rate` | Retention (Rolling)| % of new users from 13 days ago who were active at least once in days 8–14. Rolling Week 2 window retention. | | `MAU @ D56 Retention Rate` | Retention (Rolling) | % of new users from 56 days ago who were active at least once during days 29–56. Rolling Month 2 retention. | | `L7` | L-ness | Average number of days a user was active in the last 7 days (value range: 0–7). | | `L14` | L-ness | Average number of days a user was active in the last 14 days (value range: 0–14). | | `L28` | L-ness | Average number of days a user was active in the last 28 days (value range: 0–28). | These user metrics are useful for understanding long-term user behavior. However, several of these metrics don't behave well as daily experimentation metrics, because metrics like L7 are highly correlated across days. For example, a user with L7 = 7 on a given day will have either L7 = 6 or L7 = 7 the following day: this isn't a true daily independent variable. Such metrics are more likely to trigger false positive and false negative results. This limitation applies generally to stickiness and L-ness metrics. ## Customizing the DAU definition You can customize the DAU definition in the Statsig Console by specifying or excluding a set of Statsig and custom events. Find this setting in *Project Settings*. You need the required privileges to edit it. {% figure %} ![DAU definition configuration interface](/images/metrics/user/8239e1f5-133c-4ae2-914a-df5864159ccf.png) {% /figure %} By default, all events qualify a user as active. There are two ways to customize this: 1. **Excluding specific events:** Some companies exclude specific events that aren't significant user interactions (for example, `homepage_visit` or `notification_sent`). Expand the event list and uncheck events you don't want to include. You can also toggle whether future events (not shown in the list) should be included or excluded. 2. **Including specific events:** Some companies use a narrow definition of an active user (for example, event = `login`). Select the set of events and turn off **Include New Events by Default**. Changes to the DAU definition take effect on the date of the change. Historical data remains unchanged. Statsig doesn't support backfill. Set your DAU definition before running any experiments or rolling out any features. ================================================================================ # Metric Directionality URL: https://docs.statsig.com/metrics/directionality Updated: 2026-07-11 ================================================================================ ## Setting metric directionality By default, increases to a metric value are assumed to be good and appear in green in experiment results. For many metrics, a decrease is the wanted outcome: for example, page load times and most performance metrics. For these metrics, you can set the direction you want the metric to move, and experiment results update color-coding in metric lifts accordingly. To configure this, go to any metric detail view page and select the **Edit Pencil** next to the *Directionality* section in the right rail to see the **Set Metric Directionality** option. {% figure %} ![Metric directionality configuration interface](/images/metrics/directionality/fa0cbf9f-c84f-4db6-b9fa-84d672358a72.png) {% /figure %} ================================================================================ # Metrics 301 - Real-time Analytics URL: https://docs.statsig.com/metrics/301 Updated: 2026-07-11 ================================================================================ This 301-level user guide shows you: 1. How to use [Events Explorer](/product-analytics/overview) to analyze sampled real-time events emitted by your application 2. How to create [Create User Funnels](/metrics/create-user-funnels) 3. How to create [Create User Flows](/metrics/create-user-flows) Watch this space for more real-time and default product analytics features! {% callout type="info" %} **What's on the roadmap?** Join the [Statsig Slack channel](https://statsig.com/slack) to ask for previews of Statsig's upcoming features or to submit a request. Statsig builds most of the roadmap based on requests from customers! {% /callout %} ================================================================================ # Funnel Metrics URL: https://docs.statsig.com/metrics/create-user-funnels Updated: 2026-07-11 ================================================================================ You can create a custom **funnel metric** from either the **Custom Metrics** creation wizard in the **Metrics Catalog** or through the **Charts** tab. {% figure %} ![Funnel metric creation interface](/images/metrics/create-user-funnels/172472515-c94b264c-85a6-4b78-bf12-769585e4880d.png) {% /figure %} ## Components of Funnel Metrics Funnel metrics have the following components: 1. **Lineage**: Surfaces the events used to generate the funnel 2. **Metric Value**: Metric value represents the overall funnel conversion rate, or the percentage of users who complete a funnel (trigger the end event) relative to all users who start the funnel (trigger the starting event) 3. **Roll-up Window**: Statsig calculates funnel metrics daily. 4. **Conversion Rate between Stages**: This set of metrics track the percentage of users who triggered an event N relative to all users that triggered event N-1 in the funnel {% figure %} ![Funnel components diagram](/images/metrics/create-user-funnels/172446711-5511e394-b353-4d38-97f1-1b681b67042b.png) {% /figure %} After funnels are created and populated, you can view your funnel metric like any other metric in Pulse. You can also expand the funnel metric to view Pulse performance at each step in the funnel. ## User-based Funnel Metrics When counting distinct events, funnel metrics sum the number of events per day over the analysis period. When counting distinct users, funnel metrics sum the number of distinct users per day over the analysis period. For example, if a funnel consists of events A, B, and C in that order, user-based funnel metrics count the number of distinct users who triggered events A, B, and C on a given day. If you're tracking funnel conversion over multiple days, the daily granularity of funnel metrics may not suit your analysis. ## Example In the example below, the **Square** variant shows a lift in the **overall funnel conversion rate**. Expanding the metrics to examine the entire funnel reveals two key insights: * Both the **Square** and **Circle** variants show a lift in top-of-funnel DAU (*Land Page View Start DAU*). However, only the **Square** variant shows statistically significant increase in end-of-funnel DAU (*Purchase Event End DAU*). * The overall funnel conversion rate improvement for **Square** is primarily due to the higher conversion from *Checkout Event* to *Purchase Event* stages in the funnel. {% figure %} ![Funnel experiment results showing Square variant performance](/images/metrics/create-user-funnels/148440643-8e8a24bd-934d-4100-a15a-abcbcc4bb11c.png) {% /figure %} ================================================================================ # User Flows URL: https://docs.statsig.com/metrics/create-user-flows Updated: 2026-07-11 ================================================================================ Statsig's **User Flows** enable you to visualize customer journeys through your application. To create a **User Flow**, 1. Log into the Statsig console at https://console.statsig.com 2. On the left-hand navigation panel, select **Metrics** and then click on the **Charts** tab 3. Click on the **Create** button 4. Enter the chart name and start event of the User Flow that you want to visualize 5. Select the additional events that you want to include in the User Flow (Statsig will automatically determine the sequence of events) 6. Enter the number of days that users typically take to complete these events 7. You can also optionally enter the depth of the flow to define the scope of your analysis 8. Click on **Create** to finish {% figure %} ![User flow creation interface](/images/metrics/create-user-flows/141128226-097e2441-e5ee-4e8d-8725-6d57d3fdeb83.png) {% /figure %} If there is sufficient data for the User Flow, the chart appears in the console within a few minutes. ================================================================================ # Count Distinct Metrics URL: https://docs.statsig.com/metrics/count-distinct Updated: 2026-07-11 ================================================================================ ## Purpose of count\_distinct The `count_distinct` metric reports the number of unique values at unit-level. At group-level, the mean is the sum of unit-level count distinct divided by the count of unique units exposed to the experiment. Because this metric relies on probabilistic HyperLogLog++ (HLL++) sketches, it carries a small estimation error (around 0.2%–0.3%), which may increase as the experiment window grows. By default, precision p = 12 is set for `count_distinct`, which provides good accuracy for most use cases while keeping results stable and resource usage efficient. {% callout type="info" %} Reach out to Statsig to enable sketch-based count distinct metrics for your project. {% /callout %} ## For context, what are sketches? Sketches are probabilistic summaries of large datasets that answer certain queries approximately, quickly, and using little memory (often a fixed-size or sublinear footprint, such as O(log n) or even constant space regardless of dataset size). ## Core principles of HLL++ 1. **Hashing and Leading Zeros** * Each input is hashed uniformly into a 64-bit integer. * The position of the first 1-bit in the hash (the "rank") provides an estimate of how many distinct items precede that hash in sorted order. 2. **Register Array** * The algorithm allocates *m* registers, where *m* = 2^p and *p* is the precision parameter. * Each hashed value maps to one register. The register stores the maximum rank seen so far for values mapped to it. 3. **Sparse and Dense Modes** * **Sparse mode** is used when the number of distinct items is below 2^(p+5). It keeps an exact list of non-zero registers in a compact form. This yields near-zero error for small cardinalities. * **Dense mode** is used when the count exceeds the sparse limit. The sketch is represented as a fixed array of *m* registers, each one byte in size. Error increases slightly but remains tightly bounded. 4. **Estimation and Bias Correction** * The raw estimate is computed as the harmonic mean of 2^(–register\_value) across all registers, multiplied by a constant factor (alpha\_m). * BigQuery applies empirical bias corrections and the HIP (historic inverse probability) estimator to reduce variance and worst-case error. ## Precision, memory, and error relationship The precision parameter *p* controls the number of registers *m* and thus the sketch's size and accuracy: | Precision p | Registers m = 2^p | Memory per Sketch | Approximate Relative Standard Error (1 sigma) | | --- | --- | --- | --- | | 10 | 1,024 | ~1 KB | 0.83 / sqrt(1,024) ≈ 2.6 % | | 12 | 4,096 | ~4 KB | 0.83 / sqrt(4,096) ≈ 1.3 % | | 13 | 8,192 | ~8 KB | 0.83 / sqrt(8,192) ≈ 0.92 % | | 15 | 32,768 | ~32 KB | 0.83 / sqrt(32,768) ≈ 0.46 % | * Memory used grows linearly with *m*. * Error decreases as the inverse square root of *m*. * To halve the error, you must quadruple the number of registers. **Examples of thresholds and steady‑state for common precisions:** * **p = 10** (*m* = 1,024 registers, ~1 KB): * Sparse limit: 2^(10+5) = 32,768 distincts (error ≈ 0%). * Plateau begins at ~5·m = 5,120 distincts. * Steady‑state error: 0.83/√1,024 ≈ 2.6%. * **p = 12** (*m* = 4,096 registers, ~4 KB): * Sparse limit: 2^(12+5) = 131,072 distincts. * Plateau begins at ~5·m = 20,480 distincts. * Steady‑state error: 0.83/√4,096 ≈ 1.3%. * **p = 15** (*m* = 32,768 registers, ~32 KB): * Sparse limit: 2^(15+5) = 1,048,576 distincts. * Plateau begins at ~5·m = 163,840 distincts. * Steady‑state error: 0.83/√32,768 ≈ 0.46%. ## Error behavior over cardinality The relative error of an HLL++ sketch evolves through three phases: * **Exact Counting Phase** * Cardinality ≤ 2^(p+5). The sketch operates in sparse mode with error close to 0%. * **Bias Ramp Phase** * Cardinality between 2^(p+5) and approximately 5·m. The sketch is in dense mode. Systematic bias increases gradually from near 0 up to the maximum bound. * **Steady-State Phase** * Cardinality ≥ 5·m. Relative error stabilizes at the theoretical bound of 0.83 / sqrt(m). Once in this phase, adding more distinct items doesn't change the sketch's register distribution shape, so the error remains effectively constant. In other words, after enough uniques, the sketch "plateaus," and error fluctuations are limited to the estimator's minimal random variance around that fixed bound. ### Example for p = 15 (m = 32,768) * **Exact in sparse mode (≤ 1,048,576 items)** - For up to 2^(p+5) = 2^20 = 1,048,576 distinct elements, HLL++ uses a compact "sparse" representation that records individual hashes directly, resulting in an exact count (0% error). * **Transition to dense mode & rising error** - Beyond 1,048,576 distinct elements, it switches to the full register array of size m = 2^p = 32,768. In this *dense* regime, the Relative Standard Error (RSE) gradually increases from 0% to its theoretical limit. * **Asymptotic error plateau (≈ 0.46%)** - Once in dense mode, the RSE quickly converges to RSE ≈ 0.83/√m = 0.83/√32,768 ≈ 0.46%, and remains at approximately 0.46% regardless of further increases in distinct count. ## Merging sketches over time or partitions To compute distinct counts across multiple segments (for example, daily sketches), merge sketches by taking the element-wise maximum of their registers. This operation is associative and idempotent. Merged sketches preserve the same error bound as individual sketches and don't add additional error. ## Mathematical formulation 1. **Raw Estimate** * *M\[i]* is the value of register *i*. * *alpha\_m* is a bias correction constant dependent on *m*. This is the core HyperLogLog "raw" estimator for the number of distinct elements in a multiset. You take each register value M\_i, compute $2^\{-M\_i}$, sum those values, invert that sum, and multiply by m^2 (where m is the number of registers) to get a harmonic-mean-based estimate. This is then scaled by the bias-correction constant α\_m. In effect, this formula transforms the observed distribution of leading-zero counts into an approximate count of unique items. 2. **HIP Estimator** * Maintains a running sum of inverse probabilities for each new distinct element. * Produces lower variance and a smaller worst-case error constant (0.83 instead of 1.04). 3. **Relative Standard Error (RSE)** RSE ≈ 0.83/√m ## Best practices and limitations * Choose precision *p* based on the maximum expected cardinality and your error tolerance. (Statsig sets this for you by default.) * After Statsig creates a sketch with precision *p*, its precision can't be increased. Sketches can only be merged or downsampled to lower precision. * Long keys or complex objects increase CPU and memory in sparse mode but don't affect dense mode size. * Plan for a noise floor equal to the sketch's relative error when designing experiments or setting thresholds. * Reach out if you believe you require higher precision. ## Summary and recommendations HLL++ sketches provide efficient, bounded-error approximations for `count_distinct` queries, trading a small, predictable error for large savings in memory and compute. * Memory scales as O(m) rather than O(n). * Error scales as O(1/√m). * Merging sketches is exact for unions, with no extra error. Select the smallest precision *p* that meets your accuracy requirements to minimize storage and computation costs. ## Choosing precision for your cardinality When you know the maximum number of unique items (cardinality) ahead of time, pick a precision *p* so that **5 · 2^p** exceeds that cardinality. This ensures the sketch is in its steady‑state phase, where the relative error remains at the bound of 0.83/√(2^p). * **Up to 10,000 uniques** * *p* = 12 (m = 4,096 registers) → memory ~4 KB * Steady state from ~20,480 distincts (5·m) onward * Relative error ≈ 0.83/√4,096 ≈ 1.3 % * **Up to 50,000 uniques** * *p* = 13 (m = 8,192 registers) → memory ~8 KB * Steady state from ~40,960 distincts onward * Relative error ≈ 0.83/√8,192 ≈ 0.92 % * **Up to 100,000 uniques** * *p* = 15 (m = 32,768 registers) → memory ~32 KB * Steady state from ~163,840 distincts onward * Relative error ≈ 0.83/√32,768 ≈ 0.46 % * **Up to 500,000 uniques** * *p* = 16 (m = 65,536 registers) → memory ~64 KB * Steady state from ~327,680 distincts onward * Relative error ≈ 0.83/√65,536 ≈ 0.32 % * **Up to 1,000,000 uniques** * *p* = 17 (m = 131,072 registers) → memory ~128 KB * Steady state from ~655,360 distincts onward * Relative error ≈ 0.83/√131,072 ≈ 0.23 % * **General rule** 1. Compute *m* = 2^p 2. Ensure **5·m ≥ expected cardinality** 3. Verify memory footprint (*m* bytes) fits your budget 4. Confirm relative error (0.83/√m) meets your accuracy target ## How count_distinct fits into your workflow `count_distinct` provides a consistent way to measure breadth at scale. Define a metric once, use it anywhere, and merge across time and partitions with stable behavior. The metric produces answers to “How many unique X?” without requiring custom ETL or one-off SQL. ================================================================================ # Parameter Stores URL: https://docs.statsig.com/client/concepts/parameter-stores Updated: 2026-07-11 ================================================================================ Parameter Stores provide a way to organize and manage parameters in your web or mobile app through the Statsig console. Available for JS, React, React Native, Android, iOS, and Dart SDKs on the client side and Java, Node, Python, and Rust on the server side. Reach out in [Slack](https://statsig.com/slack) if you want support for a specific language. ## What is a Parameter Store? Rather than thinking in terms of Statsig entities like Feature Gates, Experiments, Layers, or Dynamic Configs, Parameter Stores let you focus on **parameters**: the values in your app that need to be configurable remotely. Parameter Stores **decouple your code from configuration**. This abstraction lets you run experiments, adjust gating, or change values dynamically **without hardcoding any experiment or gate names**. Instead, you define parameters that can be remapped remotely to any value or any Statsig entity. ## An Example: Parameterizing the Statsig Website Release cycles are often more constrained on mobile platforms. As an example, the Statsig website marketing team frequently requests updates, so the team parameterizes text, images, buttons, colors, and more: {% figure %} ![Parameter Store Mapping](/images/tutorials/parameter-stores/param_stores_mapping.png) {% /figure %} To run an experiment, point these variables directly at experiments to start an A/B test without writing any code: {% figure %} ![Parameter Store Experiment Mapping](/images/tutorials/parameter-stores/param_stores.gif) {% /figure %} The experiment on the tagline is now running without any code change. Access the parameter in code like this: ```javascript const homepageStore = StatsigClient.getParameterStore("www_homepage"); const tagline = homepageStore.get("tagline", <optional_default_value>); ``` ## How to Use Parameter Stores Here’s a suggested workflow: 1. **Create a Parameter Store**: Set up a Parameter Store for your team or project. Parameter Stores are designed to hold related parameters in one object. 2. **Identify Configuration Variables**: Consider which variables you want to decouple from your app and control through Statsig rather than hardcoding them in your app. These could include: * A boolean parameter to control access to a new feature. Even if you're used to using Feature Gates for boolean feature management, start with a boolean parameter instead. * A string parameter for text resources that you may want to swap or experiment with. * A number parameter for inputs such as the number of onboarding steps, a list length to truncate, and more. 3. **Start with Static Values**: Begin with a static value for each parameter (what you would have hardcoded in the app). Use this static value initially. 4. **Remap When Ready**: After your app is shipped and the feature is ready, remap the parameter to a Feature Gate, Experiment, Dynamic Config, or Layer to test and target different variants. 5. **Update in Real-Time**: After experimenting, you can update the static value or gate the feature for specific app versions. This can be done in real time across mobile apps that are already released. ## Why Use Parameter Stores? Parameter Stores are inspired by solutions such as Facebook’s Mobile Config, Uber’s experimentation approach, and Firebase Remote Config. Leading mobile companies use these patterns to: * Move faster * Maintain backward compatibility * Experiment more freely The extra setup in the Statsig console takes more initial effort than creating a gate, but saves time after the app is shipped. Parameter Stores ensure that no Statsig entity values are hardcoded, giving you flexibility to update parameters without a new release. This is particularly useful for mobile apps, where app store release cycles create delays unlike backends or websites. ## Supported SDK Versions Parameter Stores are available in the following SDKs: * **Android SDK** v4.33.0+ * **iOS SDK** v1.45.0+ * **@statsig/js-client**, **@statsig/react-bindings**, **@statsig/react-native-bindings**, **@statsig/expo-bindings** v1.4.0+ * **Dart SDK** v1.2.1+ * **Statsig Server Core SDKs** are gradually adding support, available in: * **com.statsig:javacore** 0.1.0+ * **statsig-rust** 0.1.0+ * **@statsig/statsig-node-core** 0.1.0+ * **statsig-python-core** 0.5.0+ * **statsig-dotnet-core** 0.6.1+ Parameter Stores aren't available in non-server-core server SDKs, and aren't available when bootstrapping a client SDK from one of those SDKs. For support in a specific language, reach out in [Slack](https://statsig.com/slack). *** ================================================================================ # Build your first Device-level Experiment URL: https://docs.statsig.com/guides/first-device-level-experiment Updated: 2026-07-11 ================================================================================ When you can't identify a user through a user ID, device-level experiments let you randomize experiments based on a consistent identifier for the user's device. Statsig can automatically generate a stable ID, but Statsig recommends using your own cookie or logged-out ID when possible. Device-level experiments are ideal in scenarios such as: * **Anonymous or first-time users**: When users haven't signed in yet or are browsing anonymously. * **Cross-device consistency**: Ensuring the same experience on the same device, regardless of user sign-in status. You can implement a device-level experiment almost exactly like a traditional user-level experiment. The key difference is setting the experiment’s ID type. This example uses `stableID` as the ID type, but you can substitute your own identifier if you have one. ## Step 1: Create a Device-level Experiment 1. **Log into the Statsig Console**: Visit [Statsig Console](https://console.statsig.com/) and navigate to **Experiments+** on the left-hand sidebar. 2. **Create a New Experiment**: * Click on **Create** and fill out the **name** and **description** of your experiment. * Enable the **Use Stable ID** option during setup. * Click **Create** to proceed. {% figure %} ![Stable ID Setup](/images/tutorials/device-level-experiment/device-level-stable-id.png) {% /figure %} 3. **Define Experiment Metrics**: Add a hypothesis, primary metrics, and secondary metrics in the **Scorecard** section, the same as for a user-level experiment. 4. **Set Groups and Parameters**: * In the **Groups** section, define the parameters for your experiment. For instance, you can experiment with a simple boolean parameter like `"enabled"`. {% figure %} ![Experiment Groups](/images/tutorials/device-level-experiment/device-level-groups.png) {% /figure %} 5. **Set Allocation**: * By default, the experiment targets 100% of your user base. Adjust the allocation if needed. Start with a smaller rollout until you're confident in the new variant. {% figure %} ![Allocation Panel](/images/tutorials/device-level-experiment/device-level-allocation.png) {% /figure %} 6. **Save and Start**: Once you've configured everything, click **Save** to finalize your experiment. When you're ready to launch, click **Start** to roll it out. ## Step 2: Initialize the SDK in your application After setting up your experiment in the Statsig console, integrate it into your client application using one of Statsig’s SDKs. **Important:** * Set **`userID`** only for authenticated, logged-in users. * For logged-out or anonymous users, use **`stableID`** (Statsig’s auto-generated device ID) or your own custom deviceID to identify the device. Refer to [customID types](/guides/experiment-on-custom-id-types) if you have your own deviceID. * Always pass all known IDs to the SDK. Statsig uses the correct one for evaluation based on the experiment or gate’s ID type. * If you do rely on stableID, only Statsig client SDKs (javascript, react, mobile, etc) generate it. Server SDKs can't generate this ID for you. * **User attributes**: You can pass additional attributes like `appVersion`, and `custom` properties for experiment targeting. ### Example (JavaScript): ```javascript const user = { userID: userID: isLoggedIn() ? getLoggedInUserID() : undefined, // Optional attributes to help with targeting appVersion: "1.0.0", custom: { promoCode: "New30Off" } }; // Initialize the Statsig Client const client = new StatsigClient(sdkKey, user, { environment: { tier: "production" } }); await client.initializeAsync(); ``` {% callout type="info" %} If your app collects other relevant attributes (e.g., device type, region), pass them in the `user` object to improve experiment precision. {% /callout %} ## Step 3 (Optional): Update User Info for Logged-in Users When a user signs in or creates an account, call the SDK’s updateUser method to attach userID and other logged-in attributes. This enables user-level experiments and gates to evaluate with the authenticated identifier. ### Example (JavaScript): ```javascript const updatedUser = { ...user, // continue to send the deviceID! userID: loggedInID, email: signUpEmail }; // Update the user object await client.updateUserAsync(user); ``` {% callout type="note" %} Adding userID after login enables user-level experiments/gates to target and evaluate using userID. *Device-level* experiment evaluations remain based on stableID (or deviceID) and don't change when you add userID, as long as you continue to pass that identifier as well! This preserves consistency of device-level bucketing. {% /callout %} *** ================================================================================ # Experiment on custom Unit ID types URL: https://docs.statsig.com/guides/experiment-on-custom-id-types Updated: 2026-07-11 ================================================================================ In some cases, you may want to randomize experiment bucketing using a custom Unit ID instead of the default `userID` or Statsig-generated Stable ID. For example, if you run a task management tool for companies and want to experiment on company-wide behaviors, you might use `companyID` as the Unit ID. Using `companyID` ensures all users from the same company get the same experience, so you can measure overall productivity impacts at the company level. Custom ID types allow for this flexibility in your experiments and feature gates, enabling you to randomize and control rollouts based on any identifier you need. ### When to use custom Unit IDs * **Organization-wide experiments**: Group users by `companyID` for company-wide consistency. * **Session-based experiments**: Use a session or device ID to control the experiment within a specific session. * **Group-level rollouts**: Target teams, regions, or other specific cohorts using a relevant ID type. Follow these three steps to set up an experiment with a custom Unit ID. The following examples use `companyID`, but you can replace it with any ID relevant to your use case. {% callout type="note" %} These steps also apply to feature gates, allowing you to partially roll out features based on custom Unit IDs. {% /callout %} *** ### Step 1: Add `companyID` as a Custom Unit ID 1. **Log into the Statsig Console**: Head over to [Statsig Console](https://console.statsig.com/) and navigate to **Project Settings**. 2. **Find Custom Unit IDs**: Under **Manage Account** > **Info**, look for the **Custom Unit IDs** section. {% figure %} ![Custom Unit ID Settings](/images/tutorials/custom-id-types/console-add-custom-id.png) {% /figure %} 3. **Add a New Custom Unit ID**: * Click the **Edit** button. * Enter `companyID` as the new ID type and provide a description. * Save the changes. {% figure %} ![Add Custom ID](/images/tutorials/custom-id-types/custom-id-add.png) {% /figure %} After adding a custom Unit ID, you can use it across all Statsig configurations: experiments, gates, layers, dynamic configs, and autotunes. You only need to configure it once per project. Statsig then makes the custom Unit ID available for targeting and analysis across experiments, feature flags, and dynamic configs. ### Step 2: Select `companyID` as the ID Type in Your Experiment 1. **Create a New Experiment**: * Navigate to **Experiments+** in the Statsig Console. * When setting up a new experiment, find the **ID Type** dropdown and select `companyID`. {% figure %} ![Select Custom ID Type](/images/tutorials/custom-id-types/custom-id-selection.png) {% /figure %} 2. **Complete Experiment Setup**: * Finish configuring your experiment as you would for any [user-level experiment](/guides/abn-tests). * Define your hypothesis, metrics, and target audience. After completing the setup, click **Save**. Choosing the right custom Unit ID ensures Statsig places all users with the same `companyID` in the same experiment group, making it easier to measure performance at the group level. *** ### Step 3: Provide `companyID` in the Statsig SDK To use the custom Unit ID in your application, provide it when initializing the Statsig SDK. The `customIDs` field in the Statsig user object lets you pass the `companyID` (or any other custom ID) along with the usual user information. #### Example (JavaScript): ```javascript var user = { userID: "some_user_id", // Standard user identifier customIDs: { companyID: "some_company_id" // Custom ID for grouping }, // Other attributes (optional) email: "user@example.com", appVersion: "1.0.0" }; // Initialize the Statsig Client const client = new StatsigClient(sdkKey, user); await client.initializeAsync(); ``` * **`customIDs` field**: This allows you to pass a dictionary of custom IDs, including `companyID`, to ensure the experiment targets users based on that ID. After providing the necessary IDs in the SDK, you can start logging events and fetching experiment configurations based on the custom Unit ID. *** ================================================================================ # Environment-based Evaluation URL: https://docs.statsig.com/guides/using-environments Updated: 2026-07-11 ================================================================================ Statsig SDKs let you set the environment tier for your app during initialization. This lets you evaluate feature gates, dynamic configs, and experiments differently in non-production environments like development or staging. Configure the appropriate environment in your code and adjust feature rules in the Statsig Console. *** ## Configure environments in the SDK There are two key ways to set up environments within your app: 1. **Environment-specific SDK keys**: These determine which rule sets are downloaded by the SDK based on the environment. 2. **Environment tier at SDK initialization**: This defines how rules are evaluated for the app. ### 1. Environment-specific SDK keys Environment-specific SDK keys control which rules Statsig sends to the SDK. For example, an SDK initialized with a development environment key doesn't receive rules set for staging or production environments. For more information, go to [Per-Environment API Keys](#per-environment-api-keys) below. ### 2. Environment tier parameter SDK keys can correspond to multiple environments. Explicitly set the environment tier during SDK initialization to ensure Statsig applies the correct rules. All SDKs accept an `SDK Key` and an optional `StatsigOptions` dictionary. The `StatsigOptions` parameter includes the `environment` key, which has a `tier` field. This tier corresponds to one of your pre-configured environments (e.g., development, staging). {% callout type="note" %} If the environment tier is unset, all checks and event logs default to "production." {% /callout %} Here's an example of setting the environment tier in your code for the **development** environment: #### Example (JS Client SDK): ```javascript const client = new StatsigClient(<SDK_KEY>, user, { environment: { tier: 'development' } }); ``` #### Example (Node Server SDK): ```javascript await statsig.initialize(<SDK_KEY>, { environment: { tier: 'development' } }); ``` Go to your language-specific SDK documentation for details. *** ## Using environments in feature gates To configure environment-specific rules for a feature gate, follow these steps: 1. **Create a new feature gate**: In the Statsig Console, create a new Feature Gate. For example, name it "development mode" to target only your development environment. {% figure %} ![Feature Gate](/images/tutorials/environments/environments-feature-gate.png) {% /figure %} 2. **Specify environments**: When configuring the rule, check the **Specify Environments** box and select the environments you want to target. By default, rules are enabled for all environments unless specified otherwise. {% figure %} ![Specify Environment](/images/tutorials/environments/environments-specify.png) {% /figure %} 3. **Save your settings**: After saving, the environments where the rule is enabled appear below the rule name. {% figure %} ![Enabled Environments](/images/tutorials/environments/environments-enabled.png) {% /figure %} You can also filter rules by environment using the filter in the upper-right corner of the Feature Gate UI. To edit the target environments of a rule, click **...** next to the rule name and select **Edit Rule**. {% figure %} ![Edit Rule](/images/tutorials/environments/environments-edit-rule.png) {% /figure %} *** ## Configuring environments By default, Statsig provides three environments: **Development**, **Staging**, and **Production**. You can add more environments or rename the default ones, but the **Production** environment can't be deleted or modified. ### Steps to add or edit environments 1. Go to **Project Settings** → [**Environments & Keys**](https://console.statsig.com/api_keys). {% figure %} ![Project Settings](/images/tutorials/environments/environments-project-settings.png) {% /figure %} 2. Click **Edit** to add new environments or reorder the existing ones using drag-and-drop. {% figure %} ![Edit Environments](/images/tutorials/environments/environments-edit-environments.png) {% /figure %} {% callout type="note" %} Reordering environments doesn't affect any rule logic, but it helps convey the rollout hierarchy (e.g., development → staging → production) to your teams. {% /callout %} *** ## Per-environment API keys Statsig lets you create per-environment API keys to enhance security and privacy. SDKs initialized with a specific environment key only access the rules relevant to that environment. ### Steps to generate environment-specific API keys 1. Go to **Project Settings** → [**Environments & Keys**](https://console.statsig.com/api_keys). 2. Click **Generate New Key** and specify the environment for which you want to generate the API key. {% figure %} ![Generate API Key](/images/tutorials/environments/environments-generate-key.png) {% /figure %} {% callout type="note" %} The default environments (Development, Staging, and Production) share the same server and client-side API keys. You can generate new keys for custom environments as needed. {% /callout %} *** ================================================================================ # Ingesting Open Telemetry Data URL: https://docs.statsig.com/server/concepts/open_telemetry Updated: 2026-07-11 ================================================================================ {% callout type="tip" %} Setting up OTEL can be complex. If you have questions, contact us on Slack. {% /callout %} ## How OpenTelemetry integration works This guide walks you through setting up the OpenTelemetry Collector in your Kubernetes environment and configuring it to send telemetry data to Statsig using the official `otlphttp` exporter. *** ## ✅ Prerequisites * A running Kubernetes cluster (e.g., GKE, EKS, Minikube, etc.) * Helm installed, or another mechanism to apply helm charts to your cluster, like ArgoCD * API Access to a Statsig project with a Server SDK Secret Key * Optional: `kubectl` configured and connected to the cluster, for validating and debugging setup *** ## 1. Setup OpenTelemetry Collector on Kubernetes The recommended setup follows the official OpenTelemetry Kubernetes guide to install the OpenTelemetry Collector in both **DaemonSet** and **Deployment** modes using the official Helm chart. Tested `values.yaml` files for both deployment modes are available in Step 3. **📘 Official Guide:** [OpenTelemetry Collector for Kubernetes – Getting Started](https://opentelemetry.io/docs/platforms/kubernetes/getting-started/) ### Why both? * **DaemonSet Collector**: Runs one instance per node to collect host-level telemetry (e.g., logs, pod and node metrics). * **Deployment Collector**: Runs one instance per cluster to gather telemetry related to the cluster as a whole. Configure at least the receivers below for a useful Statsig-powered observability platform for your Kubernetes workloads. The setup uses [Presets](https://opentelemetry.io/docs/platforms/kubernetes/helm/collector/#presets) from the official chart and customizes some components, as shown in the sample values files in Step 3. * Minimum for scraping logs: * [Filelog Receiver](https://opentelemetry.io/docs/platforms/kubernetes/getting-started/#filelog-receiver) * [Kubernetes Attributes Processor](https://opentelemetry.io/docs/platforms/kubernetes/getting-started/#kubernetes-attributes-processor) * Minimum for scraping kubernetes metrics * [KubeletStats Receiver](https://opentelemetry.io/docs/platforms/kubernetes/getting-started/#kubeletstats-receiver) * [Kubernetes Cluster Receiver](https://opentelemetry.io/docs/platforms/kubernetes/getting-started/#kubernetes-cluster-receiver) * Minimum for metrics about Otel Collector itself * [OTLP Receiver](https://opentelemetry.io/docs/platforms/kubernetes/getting-started/#otlp-receiver) for [scraping internal metrics](https://opentelemetry.io/docs/collector/internal-telemetry/#configure-internal-metrics) (alternatively scraped through prometheus receiver) *** ## 2. Configure exporting telemetry to Statsig with OTLP HTTP Exporter Statsig supports receiving OTLP-formatted, JSON-encoded telemetry at the endpoint `https://api.statsig.com/otlp`. The OpenTelemetry Collector supports sending telemetry to the Statsig endpoint using the official `otlphttp` exporter. ### Authentication Requests are authenticated using the request header `statsig-api-key` and a valid SDK Server Secret key generated from [console.statsig.com](http://console.statsig.com) (under *Settings > Keys & Environments*). Keep your secret key secure using a Kubernetes Secret management provider and make it available to the OpenTelemetry collector pod’s environment. ### Example Exporter Config ```yaml exporters: otlphttp: endpoint: https://api.statsig.com/otlp encoding: json headers: statsig-api-key: ${env:STATSIG_SERVER_SDK_SECRET} ``` ## 3. Example Helm Chart Values for a quick and correct setup The following two complete and tested `values.yaml` configurations work with the OpenTelemetry Helm charts: ### 🔗 Deployment Collector 👉 [values-gateway.yaml](https://gist.github.com/karan-statsig/d5c70b7fd100ab445985b620dcb7e8c6) ### 🔗 DaemonSet Collector 👉 [values-agent.yaml](https://gist.github.com/karan-statsig/f02ee95fef00aecde402829b5a4c60e8) Use them with the Helm chart version `0.75.1` like this: ```yaml helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts helm repo update # Install Deployment Collector helm install otel-deployment open-telemetry/opentelemetry-collector \ --version 0.75.1 \ -f values-gateway.yaml -n otel --create-namespace # Install DaemonSet Collector helm install otel-daemonset open-telemetry/opentelemetry-collector \ --version 0.75.1 \ -f values-agent.yaml -n otel ``` ## 4. Verify the Setup Check that all pods are running: ```bash kubectl get pods -n otel ``` Check logs for a specific collector pod and confirm no errors are reported: ```bash kubectl logs -n otel $pod_name ``` ## 5. Explore Explore your logs and metrics at [console.statsig.com](http://console.statsig.com) using the Logs Explorer and Metrics Explorer products under *Analytics* in the sidebar. *** ## 🔗 Resources * [OpenTelemetry Collector Documentation](https://opentelemetry.io/docs/collector/) * [Important Concepts for Kubernetes](https://opentelemetry.io/docs/platforms/kubernetes/collector/components/) * [Helm Chart Reference](https://github.com/open-telemetry/opentelemetry-helm-charts) * [Collector Configuration Reference](https://opentelemetry.io/docs/collector/configuration) * [OTLP Protocol Specification](https://opentelemetry.io/docs/specs/otlp/) ================================================================================ # Testing your Gates/Experiments URL: https://docs.statsig.com/guides/testing Updated: 2026-07-11 ================================================================================ Statsig provides tools that let you test configurations quickly while ensuring reliable outcomes. This page highlights key features to help you test efficiently. *** ## Overrides: test features, experiments, or holdouts Overrides allow you to manually configure [features](/feature-flags/overrides#adding-an-override), [experiments](/experiments/setup/overrides), or [holdouts](/experiments/holdouts-introduction) for testing purposes. This method enables safe testing without affecting live production data or skewing experiment results. Statsig excludes overrides from Pulse analysis to maintain unbiased results. * Use **Segments** to target overrides to pre-production environments or specific groups (e.g., employees) for testing. {% figure %} ![Feature Override Example](/images/tutorials/testing/testing-feature-override.png) {% /figure %} {% figure %} ![Experiment Override Example](/images/tutorials/testing/testing-experiment-override.png) {% /figure %} For more details on adding overrides, go to: * [Feature Overrides](/feature-flags/overrides#adding-an-override) * [Experiment Overrides](/experiments/setup/overrides) * [Holdout Overrides](/experiments/holdouts-introduction) *** ## Unit testing with Statsig Statsig's server SDKs offer a `localMode` feature that disables network access, ensuring that tests run locally and independently of production systems. When `localMode` is active, the SDK returns default values, allowing you to mock features and experiments in a controlled test environment. ### Override APIs for testing You can use the `overrideGate` and `overrideConfig` APIs to set specific overrides for users or globally during testing. ```js function overrideGate( gateName: string, value: boolean, userID?: string, ): void; ``` ```js function overrideConfig( configName: string, value: object, userID?: string, ): void; ``` For example, to override a gate for testing: ```js statsig.overrideGate("example_gate", true); ``` * For more information on mocking Statsig for testing, go to [Node.js Server SDK](/server/nodejsServerSDK#how-can-i-mock-statsig-for-testing) or [JavaScript Client SDK](/client/javascript-sdk#testing). *** ## Environments: configuring Development, Staging, and Production Statsig lets you assign environments to feature gates, experiments, and events. By default, Statsig assigns checks without a defined environment to **Production**. You can customize environments (such as **Development**, **Staging**, or **Production**) and use them to target different versions of feature gates or segments. * Non-production events appear in diagnostics and Statsig uses them to track cumulative exposures and metric results when testing experiments in lower environments with **Enable for Environments**. Production data is prioritized for final Pulse result analyses. ### Customizing environments You can map your internal environments to Statsig's built-in environments or create custom mappings. Common setups include: * Assigning Dev One boxes to **Development**. * Creating an **Early Access** slice (e.g., 1% of production users) as part of the **Production** environment for phased rollouts. For more details on environments, go to the [Statsig blog post](https://blog.statsig.com/environments-on-statsig-6a818805b3c2). *** ================================================================================ # Migration Overview URL: https://docs.statsig.com/guides/migration-overview Updated: 2026-07-11 ================================================================================ Statsig combines feature flags, experimentation, and product analytics in one platform. Migrating ensures your data, flag, experiments, and decision workflows live in a single source of truth. This guide outlines the overall migration process. For provider-specific steps (Amplitude, LaunchDarkly, etc.), refer to the dedicated guides. ## Migration phases ### 1. Audit and plan * Identify the datasets, events, and feature flags you want to move * Decide what needs full historical backfill vs. what can start fresh * Document any dashboards or KPIs that need rebuilding in Statsig ### 2. Set up live data * Implement Statsig SDKs to start streaming new events and feature flag evaluations * Validate critical events are firing with the correct schema * Use Statsig for the newly recorded events and flags ### 3. Import historical data * Export data from your existing tool (S3 or warehouse is preferred) * Transform the schema to Statsig's event format (`event`, `user`, `timestamp`, `metadata`) * Import using Statsig's Event Webhook, S3 ingestion, or warehouse ingestion ### 4. Validate and decommission * Compare metrics between your legacy tool and Statsig to ensure parity * Rebuild dashboards and charts in Statsig * Decommission old pipelines once Statsig is your single source of truth ## Best practices * **Start small**: Run a pilot project or test migration before backfilling all history * **Align IDs early**: Ensure `userID` and `stableID` mapping is consistent. Identity mismatches are the most common failure point * **Shard historical imports**: Break large datasets into daily partitions for stability * **Rebuild insights intentionally**: Don't port all events and flags directly. Use migration as a chance to clean up stale data * **Plan change management**: Teams need time to adjust workflows, queries, and dashboards so migrate for 1-2 teams before championing in the broader org ## Provider-specific guides * [Migrate from Amplitude](/guides/migrate-from-amplitude) * [Migrate from Mixpanel](/guides/migrate-from-mixpanel) * [Migrate from LaunchDarkly (Feature Flags)](/guides/migrate-from-launchdarkly) * Additional guides coming soon ## Get help Statsig has helped many customers migrate from other tools. For tailored guidance, reach out to the support team, your sales contact, or through the [Slack community](https://statsig.com/slack). ================================================================================ # Migrate your analytics data from Amplitude URL: https://docs.statsig.com/guides/migrate-from-amplitude Updated: 2026-07-11 ================================================================================ Statsig is an all-in-one platform that combines analytics, experimentation, and feature flagging. Migrating Amplitude data into Statsig usually involves two steps: export and ingest. This guide provides the essentials. For anything beyond these basics, contact Statsig. ## Step 1. Export your data from Amplitude Amplitude offers a few different export methods. Pick the one that matches your data size and setup: **1. S3 Export** For high volume backfills, dump your Amplitude data into an S3 bucket. **2. Warehouse Export** If your Amplitude data is already in Snowflake, BigQuery, or Redshift, you can skip file downloads. Statsig ingests directly from these warehouses (refer to Step 2.1). **3. Export API** Use Amplitude's Export API to pull gzipped JSON * **Limit**: 4 GB per request so use hourly windows for large ranges * **Example**: ```bash curl --location --request GET 'https://amplitude.com/api/2/export?start=<starttime>&end=<endtime>' \ -u '{api_key}:{secret_key}' ``` **4. UI Download (CSV/JSON)** Go to Organization Settings → Project → Export Data * Best for small datasets or initial testing ## Step 2. Transform your data Amplitude and Statsig store events in slightly different formats. Map your Amplitude data to Statsig's format before importing, regardless of which import method you use in Step 3. | Amplitude field | Statsig field | |-----------------|---------------| | `event_type` | `event` | | `event_time` | `timestamp` (ms since epoch) | | `user_id` | `user.userID` | | `device_id` | `user.stableID` | | `event_properties` | `metadata` | | `user_properties` | `user` fields | **Before transform** ```json // Amplitude event { "event_type": "purchase", "user_id": "123", "device_id": "device_abc", "event_time": "2023-08-17T00:00:00Z", "event_properties": { "amount": 25, "currency": "USD" }, "user_properties": { "plan": "premium" } } ``` **After transform** ```json // Statsig event { "event": "purchase", "user": { "userID": "123", "stableID": "device_abc", "plan": "premium" }, "timestamp": 1692230400000, "metadata": { "amount": 25, "currency": "USD" } } ``` ## Step 3. Import into Statsig After your data is in Statsig event format, choose an import path based on how you exported: | If you exported from Amplitude via... | Import into Statsig using... | Best when... | |---------------------------------------|------------------------------|--------------| | S3 export | S3 ingestion | You're backfilling large datasets | | Warehouse (Snowflake/BQ/Redshift) | Warehouse ingestion | Your Amplitude data already lives in a warehouse | | Export API | Event Webhook | You're moving a few days/weeks of data programmatically | | UI download (CSV/JSON) | Event Webhook | You're testing or moving a small slice of data | **S3 ingestion** * Ensure files are transformed to the Statsig schema in Parquet, JSON, or CSV format, then follow the [Statsig S3 ingestion](/data-warehouse-ingestion/s3) steps. * Shard your Amplitude raw data into one day per directory before ingesting into Statsig. **Warehouse ingestion** * [Connect your warehouse to Statsig](/data-warehouse-ingestion/introduction) * Point Statsig at a query that outputs events in the expected schema * Statsig ingests on a recurring schedule **UI download or Export API** To get these events into Statsig, replay them through the [Event Webhook](/http-api/overview#post-event-webhook). Take each row or JSON object, reshape it to Statsig format, and send it one at a time or in small batches. This approach is best for test runs or initial migrations, not for millions of events. ```bash curl -X POST https://api.statsig.com/v1/webhooks/event_webhook \ -H "Content-Type: application/json" \ -H "STATSIG-API-KEY: $STATSIG_SERVER_SECRET" \ -d '{ "event": "signup", "user": { "userID": "abc" }, "timestamp": 1692230400000 }' ``` ## Get migration help If you are unsure how to approach this migration, contact the Statsig team. Statsig has worked closely with customers migrating from Amplitude and can provide hands-on support. Reach out to the support team, your sales contact, or through the [Slack community](https://statsig.com/slack). ================================================================================ # Migrate your analytics data from Mixpanel URL: https://docs.statsig.com/guides/migrate-from-mixpanel Updated: 2026-07-11 ================================================================================ Migrating from Mixpanel to Statsig gives teams a unified platform that combines analytics, experimentation, and feature flagging, which enables faster and more informed decisions without data silos. Migrating Mixpanel data into Statsig usually involves three steps: export, transform, and ingest. This guide provides the essentials. For anything beyond these basics, contact Statsig. {% callout type="note" %} This guide covers only importing raw events into Statsig. Statsig doesn't support importing user/group profiles, dashboards, reports, etc. Once your raw events are in the Statsig project, you can re-create your critical dashboards there. {% /callout %} ## Step 1. Export your data from Mixpanel Mixpanel offers a few different export methods. Pick the one that matches your data size and setup: **1. CSV Export** Export small batches of events as CSV using the Events tab → query events → click "Export" button. **2. Export API** Use Mixpanel's [Raw Event Export API](https://developer.mixpanel.com/reference/raw-event-export) to pull JSONL data: * **Limit**: Export one day's data at a time for optimal performance * **Format**: JSONL where each line is a valid JSON object ```bash curl --location --request GET 'https://data.mixpanel.com/api/2.0/export?from_date=2023-01-01&to_date=2023-01-01' \ -u '{project_id}:{service_account_secret}' ``` **3. Data Pipelines (Bulk Export)** For large data volumes, use Mixpanel's [Data Pipelines](https://docs.mixpanel.com/docs/data-pipelines) feature to export to: * Cloud Storage (AWS S3, Google Cloud Storage, Azure Blob Storage) * Data Warehouse (BigQuery, Redshift, Snowflake) ## Step 2. Transform your data Mixpanel and Statsig store events in slightly different formats. Map your Mixpanel data to Statsig's format: | Mixpanel field | Statsig field | |----------------|---------------| | `event` | `event` | | `properties.time` | `timestamp` (ms since epoch) | | `properties.distinct_id` or `properties.user_id` | `user.userID` | | `properties.device_id` | `user.stableID` | | `properties.*` (other fields) | `metadata` | **Before transform** ```json // Mixpanel event { "event": "Signed up", "properties": { "time": 1618716477, "distinct_id": "user-123", "device_id": "xyz", "Referred_by": "Friend", "URL": "website.com/signup" } } ``` **After transform** ```json // Statsig event { "event": "Signed up", "user": { "userID": "user-123", "stableID": "xyz" }, "timestamp": 1618716477000, "metadata": { "Referred_by": "Friend", "URL": "website.com/signup" } } ``` ## Step 3. Import into Statsig After your data is in Statsig event format, choose an import path: | If you exported from Mixpanel via... | Import into Statsig using... | Best when... | |--------------------------------------|------------------------------|--------------| | S3 export | [S3 ingestion](/data-warehouse-ingestion/s3) | You're backfilling large datasets | | Warehouse (Snowflake/BigQuery/Redshift) | [Warehouse ingestion](/data-warehouse-ingestion/introduction) | Your Mixpanel data already lives in a warehouse | | Export API | [Event Webhook](/http-api/overview) | You're moving a few days/weeks of data programmatically | | CSV download | [Event Webhook](/http-api/overview) | You're testing or moving a small slice of data | **Event Webhook (for API/CSV exports)** ```bash curl -X POST https://api.statsig.com/v1/webhooks/event_webhook \ -H "Content-Type: application/json" \ -H "STATSIG-API-KEY: $STATSIG_SERVER_SECRET" \ -d '{ "event": "Signed up", "user": { "userID": "user-123", "stableID": "xyz" }, "timestamp": 1618716477000, "metadata": { "Referred_by": "Friend", "URL": "website.com/signup" } }' ``` **Important notes:** * **S3 ingestion**: Shard your Mixpanel data into 1 day's data per directory for Statsig * **Scale gradually**: After small tests, backfill in chunks to manage loads * **Future tracking**: After historical import, switch Mixpanel code calls to Statsig SDKs ## Get migration help If you are unsure how to approach this migration, contact the Statsig team. Statsig has experience helping customers migrate from Mixpanel. Reach out to the support team, your sales contact, or through the [Slack community](https://statsig.com/slack). ================================================================================ # LaunchDarkly Migration Guide URL: https://docs.statsig.com/guides/migrate-from-launchdarkly Updated: 2026-07-11 ================================================================================ ## How this guide is organized This guide covers the following topics: 1. Conceptual differences between LaunchDarkly and Statsig 2. Deciding what to migrate vs. not 3. Importing flags into Statsig 4. Flipping evaluation from LaunchDarkly to Statsig 5. How to run the migration process ## Conceptual differences between LaunchDarkly and Statsig LaunchDarkly and Statsig structure their feature management data models differently in several key ways: **Environment**: LaunchDarkly treats environments as a top-level concept where flags and segments must be duplicated and managed separately across environments. Statsig uses a centralized model where flags and configs handle environment-specific logic in their targeting rules. **Flag types**: LaunchDarkly uses a mix of boolean, multivariate, and JSON flags. Statsig distinguishes between Feature Gates (boolean) and Dynamic Configs (typed multivariate configs with JSON values). **Targeting**: LaunchDarkly relies on Contexts to evaluate flags. Statsig evaluates based on a StatsigUser object. #### Side by side comparison | LaunchDarkly concept | Can we migrate? | Statsig notes | |---------------------|-----------------|---------------| | Project | ✅ Yes | Convert to Project | | Environment | ✅ Yes | Convert to Environment (mark critical as production in Statsig) | | Boolean Flags | ✅ Yes | Convert to Feature Gates | | String, Number, and JSON Flags | ✅ Yes | Convert to Dynamic Configs | | Segments | ✅ Yes | Convert to Segments (Big ID list segments won't be imported) | | Targeting Rules | ✅ Yes | Convert to Rules | | Context kind | ✅ Yes | Convert to Custom Unit ID in Statsig | | Context attribute | ✅ Yes | Convert to Custom Fields in Statsig | | Flag owner, tags, teams, and history | ❌ No | Statsig doesn't preserve any metadata or historical versions of a flag during migration | #### User Context mapping example LaunchDarkly supports multi-kind, structured user contexts. Statsig requires a user object to achieve this. In Statsig, User ID or Custom ID is equivalent to LD's key. Known top-level fields in Statsig include userID, email, ip, userAgent, and custom. All other fields go under the custom object. **Example 1: LD User context to Statsig User object conversion** ```javascript // 1 - LD User context { "kind": "user", "key": "user-key-123abc", "name": "Anna", "email": "anna@globalhealthexample.com", "organization": "Global Health Services", "jobFunction": "doctor", "device": "iPad" } // 1 - Statsig User object { "userID": "user-key-123abc", "email": "anna@globalhealthexample.com", "custom": { "name": "Anna", "organization": "Global Health Services", "jobFunction": "doctor", "device": "iPad" } } ``` **Example 2: LD Multi context kind to Statsig User object conversion** ```javascript // 2 - LD Multi context kind { "kind": "multi", "user": { "key": "user_abc", "name": "Anna", "email": "abc@company.com", "region": "us-east" }, "org": { "key": "org_xyz", "tier": "enterprise" } } // 2 - Statsig user object { "userID": "user_abc", "email": "abc@company.com", "customIDs": { "org_id": "org_xyz" }, "custom": { "user_name": "Anna", "user_region": "us-east", "org_tier": "enterprise" } } ``` In Statsig, `email` is a top-level reserved field on the user object: place it directly as `email` (not `user_email`). Statsig expects `userID`, `email`, `ip`, and `userAgent` at the top level for user targeting and analytics. ## Deciding what to migrate vs. not Before migrating flags from LaunchDarkly to Statsig, audit your existing flags and remove those that are no longer needed. Many organizations accumulate flag debt over time from stale flags, deprecated toggles, and legacy kill switches. Migration is an opportunity to start fresh with only active, valuable flags. Use filters such as "Lifecycle" and "Type" in LaunchDarkly to identify which flags are worth importing into Statsig. The following decision framework can help you decide which flags to import. The migration script follows this framework by default, but you can modify it as needed. {% figure %} ![Migration Decision Framework](/images/tutorials/migration-launchdarkly/migration-decision-framework.png) {% /figure %} ## Importing flags into Statsig Use the official import tool to import feature flags from LaunchDarkly into Statsig. The tool fetches flags from LaunchDarkly, translates them into Statsig's format, creates corresponding feature gates in Statsig, and tracks migration status and details in a CSV file. Two options are available: 1. **[Open source script](/guides/open-source-script) (Recommended)**: Use this option to customize the integration logic. The script outputs a CSV of all your LaunchDarkly flags with migration status and URLs to the flag in LaunchDarkly and the gate in Statsig. This option imports all environments. 2. **[Statsig console](/guides/ui-based-tool)**: A UI-based wizard for importing LaunchDarkly feature flags and segments into Statsig. The wizard reports which gates and segments were migrated. This option imports only the production environment. > If you are migrating from a different system, recreate flags manually in Statsig. After cleaning up in the previous step, you should have a smaller set of flags to migrate. Contact the Statsig team over email or Slack if you need assistance. ## Flipping evaluation from LaunchDarkly to Statsig After your flags are imported into Statsig, update the evaluation logic in your application. Rather than replacing every LaunchDarkly evaluation call with a Statsig call at once, introduce a wrapper that supports gradual migration. This lets you run both systems in parallel, compare outputs, and switch over incrementally with minimal risk. The wrapper approach provides several key benefits: * **Parallel execution**: Run both systems simultaneously to validate behavior * **Gradual rollout**: Migrate flags one at a time or by percentage * **Easy rollback**: Quickly revert to LaunchDarkly if issues arise * **Consistent interface**: Maintain existing application code structure #### Implementation guide **1. Before migration: LaunchDarkly Evaluation** Here's what a typical LaunchDarkly setup might look like: ```javascript import { LDClient } from 'launchdarkly-js-client-sdk'; const ldClient = LDClient.initialize('client-key', { key: 'user_abc', custom: { plan: 'pro' } }); ldClient.on('ready', () => { const isNewHomepageEnabled = ldClient.variation('new_homepage_flag', false); const buttonColor = ldClient.variation('button_config', 'gray'); }); ``` **2. Create the Migration Wrapper** Create a comprehensive wrapper (`featureWrapper.js`) that handles both systems. The wrapper should check Statsig first, and if unavailable, fallback to LaunchDarkly: ```javascript import { getLDClient } from './launchdarklyService'; import { getStatsigClient } from './statsigService'; export const wrapperFlags = { // For boolean flags wrapperGetFlag(flagKey, defaultValue = false) { const statsigClient = getStatsigClient(); if (statsigClient) { return statsigClient.checkGate(flagKey); } const ldClient = getLDClient(); if (ldClient) { return ldClient.variation(flagKey, defaultValue); } return defaultValue; }, // For dynamic configs (string, number, or json flags) wrapperGetConfig(user, configKey) { const statsigClient = getStatsigClient(); if (statsigClient) { const config = statsigClient.getConfig(user, configKey); return { get: (paramKey, defaultValue) => config.get(paramKey, defaultValue) }; } const ldClient = getLDClient(); if (ldClient) { return { get: (paramKey, defaultValue) => { const ldKey = `$\{configKey\}.${paramKey}`; return ldClient.variation(ldKey, defaultValue); } }; } return { get: (_key, defaultValue) => defaultValue }; } }; ``` **3. Refactor application code to use the wrapper** After the wrapper is in place, route all flag checks through it. The application logic doesn't change: only the mechanism by which flags are retrieved changes. ```javascript import { wrapperFlags } from './featureWrapper'; const user = { userID: 'user_abc', custom: { plan: 'pro' } }; // Boolean gate if (wrapperFlags.wrapperGetFlag('new_homepage_flag', user)) { showNewHomepage(); } // Multivariate config const buttonConfig = wrapperFlags.wrapperGetConfig('button_config', user); const buttonColor = buttonConfig.get('color', 'gray'); ``` **4. Validate and gradually cut over** After you have validated that Statsig is working as expected and that migrated flags return correct values, begin migrating more flags. Repeat the above steps for 2–3 engineering teams to confirm that different use cases are covered. After flags have been maintained in both systems long enough to confirm stability, phase out LaunchDarkly. 1. **Remove LaunchDarkly fallback from the wrapper** - Update the wrapper functions to rely solely on Statsig. This simplifies the logic and ensures LaunchDarkly is no longer queried in production. 2. **Delete LD initialization logic and SDK imports** - Any references to `LDClient.initialize` or `ldClient.variation` can now be safely removed. ## How to run the migration process Use a phased rollout so each team can adopt Statsig independently. This approach supports gradual migration, scoped validation, and shared learnings across the organization. | Phase | Description | Who | Duration | |-------|-------------|-----|----------| | 1. Auditing existing flags in LaunchDarkly | Each team reviews their LaunchDarkly flags and identifies which flags are worth migrating | Individual Teams | Ongoing (2-3 days per team) | | 2. Start creating all new flags in Statsig | Starting immediately, all new flags to be created in Statsig to avoid using legacy system out of habit | Org-wide | 1 week | | 3. Pilot migration with one team | Select one team to migrate a small set of LaunchDarkly flags to Statsig using the wrapper. Validate that migrated flags work as expected | Pilot team | 1–2 weeks | | 4. Org-Wide migration and cutover | Repeat migration for more teams in waves. Create training docs and guides for org wide adoption. Once Statsig is stable and adopted org-wide, remove LaunchDarkly fallback from wrapper | All teams + central guidance | 3–4 weeks (rolling) | If you need additional assistance or want to discuss your specific case, contact Statsig. ================================================================================ # Open Source Script URL: https://docs.statsig.com/guides/open-source-script Updated: 2026-07-11 ================================================================================ [This package](https://github.com/statsig-io/migrations) is designed to help automate migration of feature flags from LaunchDarkly to Statsig. It fetches feature flags from LaunchDarkly, translates them into Statsig's format, and creates corresponding feature gates in Statsig. ## Considerations This script works by default. Start with a test environment of 5–10 flags. Before running the script at scale, consider the following: * **IMPORTANT**: If you don't need to customize the import script, use [Statsig's in-console tool](/guides/ui-based-tool) instead. * The script uses a tag `Imported from LaunchDarkly` to identify migrated flags in Statsig. Ensure this tag is unique and recognizable. * The script includes a function to delete all Statsig feature gates with a specific tag. Use this with caution to clean up after a test or failed migration. * The script requires API keys for both LaunchDarkly and Statsig. Keep these keys secure. ## Installation To run the script, you need Node.js and npm installed on your system. Execute directly: ```bash npx @statsig/migrations --from launchdarkly --launchdarkly-project-id default <more-arguments> ``` ## Configuration * Provide your [LaunchDarkly API key](https://docs.launchdarkly.com/home/account/api) and [Statsig Console API key](/console-api/introduction) in the script * Map LaunchDarkly environments to Statsig environments that aren't already the same by using `--environment-name-mapping` to the script. * Map LaunchDarkly context kind to Statsig's custom unit ids and custom fields. You can find the detailed instructions for these steps on the [Github repo](https://github.com/statsig-io/migrations). ## Running the script To execute the migration script, run the following command in your terminal: ```bash node index.js ``` The script performs the following actions: 1. Fetch all feature flags from LaunchDarkly. 2. Translate each flag into Statsig's format. 3. Create feature gates in Statsig. 4. Write the migration status and details to a CSV file named `flag_migration_tracker.csv`. ## Example translations The following examples show how LaunchDarkly feature flags are translated into Statsig feature gates: **Example 1:** LaunchDarkly flag with email and name targeting conditions {% figure %} ![Example 1 - LaunchDarkly to Statsig translation](/images/tutorials/migration-launchdarkly/open-source/example-translation-1.png) {% /figure %} **Example 2:** LaunchDarkly flag with country-based targeting (off flag) {% figure %} ![Example 2 - LaunchDarkly to Statsig translation](/images/tutorials/migration-launchdarkly/open-source/example-translation-2.png) {% /figure %} ## Troubleshooting If you encounter issues during the migration, check the following: * Confirm that the API keys are correct and have the necessary permissions. * Review the error messages in the console to diagnose the problem. Pull requests and feedback are welcome. ================================================================================ # UI-Based Tool URL: https://docs.statsig.com/guides/ui-based-tool Updated: 2026-07-11 ================================================================================ This guide covers how to use Statsig's built-in LaunchDarkly migration tool. This UI-based tool only imports the "production" environment. ## What you need Review the full checklist in the [LaunchDarkly Migration Guide](/guides/migrate-from-launchdarkly#what-you-need), then gather: 1. Your project key. Projects in LaunchDarkly have a Name (for example, "My Mobile App") and a Key (for example, my\_mobile\_app). 2. A read-only access token for this project. Create one in LaunchDarkly under Account Settings > Authorization and limit the scope to read-only. 3. A Statsig project. Try this in a test project first. ## How it works These screens mirror the [console walkthrough](/guides/migrate-from-launchdarkly#how-it-works): 1. If you don't have any feature gates in your project, Statsig prompts you to import Feature Gates. {% figure %} ![Import Feature Gates prompt](/images/tutorials/migration-launchdarkly/ui-tool/ui-based-tool1.png) {% /figure %} 2. Select LaunchDarkly as the platform you want to migrate from. {% figure %} ![Platform selection interface](/images/tutorials/migration-launchdarkly/ui-tool/ui-based-tool2.png) {% /figure %} 3. Enter your LaunchDarkly Project Key and API Key/access token. {% figure %} ![LaunchDarkly credentials input form](/images/tutorials/migration-launchdarkly/ui-tool/ui-based-tool3.png) {% /figure %} 4. Preview the migration summary. The tool highlights which gates can and can't be migrated. Gates that aren't migrated include those with segments (coming soon) and those with non-Boolean flags. {% figure %} ![Migration summary preview screen](/images/tutorials/migration-launchdarkly/ui-tool/ui-based-tool4.png) {% /figure %} 5. Finish migration of the gates. All migrated gates are tagged "Migrated" so you can identify them. {% figure %} ![Migration completion confirmation](/images/tutorials/migration-launchdarkly/ui-tool/ui-based-tool5.png) {% /figure %} ================================================================================ # FAQ URL: https://docs.statsig.com/faq Updated: 2026-07-11 ================================================================================ ## SDKs and APIs ### How does bucketing in the Statsig SDKs work? Refer to [How Evaluation Works](/sdks/how-evaluation-works). *** ### Can I add a layer to a running experiment? No. Layers are fixed after an experiment starts to preserve the integrity of results. Statsig may support editing layers in the future. *** ### Can I rename an existing experiment or feature gate? Yes. When renaming an entity (for example, a feature gate, experiment, or layer), only its display name changes. The underlying ID referenced in your code remains unchanged because IDs are immutable, which prevents breaking existing implementations. *** ### Why define parameters instead of reading the experiment group? Parameters let you iterate quickly without code changes and support richer experiment setups. For example: ```js // Group-based approach — requires code changes for each variant if (otherEngine.getExperiment('button_color_test').getGroup() === 'Control') { color = 'BLACK'; } else if (otherEngine.getExperiment('button_color_test').getGroup() === 'Blue') { color = 'BLUE'; } // Statsig parameter approach — variants can be changed from the console const color = statsig.getExperiment('button_color_test').getString('button_color', 'BLACK'); ``` *** ### Why aren't exposures or custom events showing up? In short-lived environments (scripts, edge workers), the process may exit before events flush. Call `statsig.flush()` before shutdown. For more detail, refer to the [Node.js server SDK docs](/server/nodejsServerSDK#flushing-events). *** ### My SDK language isn't listed. Can I still use Statsig? Likely yes. Post in the <a href="https://statsig.com/slack" target="_blank">Statsig Slack community</a> and the team will discuss options. *** ### How do I retrieve all exposures for a user? The [Users tab](https://console.statsig.com/users) shows historical exposures. For hypothetical assignments (e.g., to bootstrap clients) you can call `getClientInitializeResponse` on the server. Pass `{ hash: 'none' }` if you need readable keys: ```js const assignments = statsig.getClientInitializeResponse(user, 'client-key', { hash: 'none' }); ``` *** ### What happens if I check a config that doesn't exist? The SDK returns defaults: `false` for gates and the supplied fallback for experiments/layers. The evaluation reason shown is `Unrecognized`. Refer to [SDK debugging](/sdks/debugging#evaluation-reason) for more detail. This applies to deleted, archived, or unseen configs (for example, configs filtered by [target apps](/sdks/target-apps)). *** ## Feature gates ### If I change the rollout percentage, do existing users keep their result? Yes. Increasing the pass percentage (for example, 10% to 20%) keeps the original 10% and adds new traffic until the new percentage is reached. Decreasing it removes the newest slice first. To reshuffle all users, you must resalt the gate. Experiments behave differently; use targeting gates for deterministic control. *** ## Statistics ### What statistical tests does Statsig use? Statsig uses a two-sample Z-test for most experiments and [Welch's t-test](/experiments/statistical-methods/p-value#welchs-t-test) when sample sizes are small or variances differ. *** ### How does Statsig handle low sample sizes? Statsig falls back to [Welch's t-test](/experiments/statistical-methods/p-value#welchs-t-test) and offers CUPED/winsorization to boost power. *** ### When should I use one-sided vs. two-sided tests? Use one-sided when you only care about movement in a single direction; it increases power but hides movement in the opposite direction. *** ## Experimentation ### How do I get started with an A/B test? If the feature isn't live yet, wrap it in a [feature gate](/guides/first-feature) and roll out. If it's already in production, create an [experiment](/guides/abn-tests). Results appear in the Pulse view. *** ### Can I target experiments to specific users (e.g., iOS only)? Yes. Use a feature gate with targeting rules as a pre-filter for your experiment. {% figure %} ![Targeting iOS users in an experiment setup](/images/experiments/power-analysis/navigation.png) {% /figure %} *** ## Billing ### What counts as a [billable event](https://statsig.com/pricing#faq)? Any gate/experiment check or event logged through the SDK or APIs. Pre-computed metrics and custom metrics based on existing data also count. *** ### How do I monitor and manage billable volume? 1. Export usage from the **Usage and Billing** tab. 2. Pivot by event to identify heavy hitters. 3. Admins receive alerts at 50/75/100% of contract. {% figure %} ![Usage dashboard showing event volume](/images/guides/first-feature/diagnostics-stream.png) {% /figure %} *** ### How many projects can I create with a Pro subscription? Each Statsig Pro plan unlocks one project with pro features and 5M events. Additional projects require their own upgrade. Enterprise plans can cover multiple projects. [Contact us](https://statsig.com/contact/demo) to discuss options. *** ## Platform usability ### When should I create a new project? Projects have distinct boundaries. If you're using the same userIDs and metrics across surfaces, apps or environments, put them in the same project. Create a new project when you're managing a separate product with unique user IDs and metrics. For example, if you have a marketing website (anonymous users) and a product (signed-in users), you may want to separate them. However, if you want to track success across both you should manage them in the same project (for example, from user signup on the marketing website to user engagement within the product). Reasons to avoid creating a new project: * Segregating by environment: Statsig supports environments natively, and you can customize them. You can turn features or experiments on and off by environment. * Segregating by platform: If you have an iOS app and a web app, collecting data in the same project lets you view data by platform and understand whether you increased the overall metric or moved users from one platform to the other. *** ### How can I monitor the health of Statsig and get support? You can check the live operational status of Statsig at <a href="http://status.statsig.com" target="_blank">status.statsig.com</a>. To report an issue or receive help, reach out directly in our <a href="https://statsig.com/slack" target="_blank">Slack Community</a>. *** ================================================================================ # About Warehouse Native URL: https://docs.statsig.com/statsig-warehouse-native/introduction Updated: 2026-07-11 ================================================================================ {% callout type="note" %} Warehouse Native is part of Statsig's Enterprise tier. [Contact us](https://statsig.com/contact/demo) to get started. {% /callout %} Statsig Warehouse Native is an enterprise-grade experimentation platform that runs analysis in your data warehouse. It integrates easily with your existing datasets and any source of experiment assignment data – including a powerful integration with Statsig's SDKs and real-time logging infrastructure. Warehouse Native shares core features with Statsig Cloud but focuses on specific scenarios around running experiments on top of your warehouse. ### Use cases for Statsig Warehouse Native * End-to-end experimentation platform covering targeting, experiment setup and assignment, and analysis. * Features include Feature Flagging, Automated/Protected Rollouts, Native Holdouts, Mutual Exclusion, and comprehensive analysis tools. * Modern statistical engine for running analyses on existing experiments from third-party or internal systems. * Statsig offers a full suite of experiment measurement tools, including CUPED, Stratified Sampling, Switchback Tests, and more. If you aren't sure whether Statsig Warehouse Native or Statsig Cloud is a better fit for your experimentation needs, reach out to the support team, your sales contact, or the [Slack community](https://statsig.com/slack) for guidance. ## How Warehouse Native works Statsig Warehouse Native runs experimentation compute jobs directly in your data warehouse, using your existing datasets to calculate metrics and enrich experiment analysis based on your data. {% figure %} ![Warehouse Native data flow architecture diagram](/images/whn/data_flow_whn.png) {% /figure %} ### SDK integration Using [Statsig SDKs](/sdks/getting-started) with Statsig Warehouse Native is similar to integrating with Statsig Cloud: 1. **Set Up Targeting and Experiments**: Create and manage experiments using the Statsig console. 2. **Initialize the SDK**: Integrate the Statsig SDK on your client or server-side applications. 3. **Targeting and Assignment**: Call the Statsig SDK to assign users to variants. * Optionally provide a logging callback to store logs in your warehouse or use Statsig's real-time infrastructure for instant diagnostics and safe rollouts. The resulting assignment and (optional) event logging data ends up in your warehouse, where you can connect it to other datasets for analysis. If you log to Statsig, data is exported on-demand for real-time analysis or in scheduled batch jobs. ### Experiment setup Running experiments on Warehouse Native involves several key steps: 1. **[Connect Statsig to Your Warehouse](/statsig-warehouse-native/guides/quick-start#step-1-connect-your-warehouse)**: Integrate Statsig with your data warehouse to access relevant datasets. 2. **Create Metrics**: Define the input data and configure experiment metrics. 3. **Log Exposures**: Log experiment exposures with Statsig or point to existing assignments in your warehouse. 4. **State Your Hypothesis**: Formulate a hypothesis and choose scorecard metrics to evaluate the experiment. 5. **Run Pulse Analysis**: Execute a Pulse analysis to test your hypotheses and measure the impact on scorecard metrics. ### Advanced analysis tools Statsig’s [data analysis tools](/experiments/interpreting-results/read-results) run directly in your data warehouse. All queries, intermediate datasets, and final results generated by Statsig are available in your warehouse for auditing and custom analysis. * **Pulse Analysis**: Pulse helps you test your hypotheses and measure the impact of changes on your scorecard metrics. * **Metrics Explorer**: Visualize experiment metrics for your entire population and drill down into user behavior. Metrics Explorer is part of a broader suite of product observability tools, including Session Replay, advanced filtering, and custom dashboards. * **Exposure Analysis**: Analyze your experiment's first exposures to debug and gain insights on your experiments in Metrics Explorer. Drill down into certain properties or understand how your experiments interact with one another. ## Start using Warehouse Native {% callout type="info" %} Reach out to the support team, your sales contact, or the [Slack community](https://statsig.com/slack) with any questions. {% /callout %} Use the following guides to start running or analyzing an experiment: {% card-grid %} {% card title="Quick Start" href="/statsig-warehouse-native/guides/quick-start" icon="speed" %} Connect Statsig to your warehouse and get test results in minutes {% /card %} {% card title="Evaluate Statsig" href="/statsig-warehouse-native/guides/running_a_poc" icon="warehouse" %} Evaluate how Statsig Warehouse Native can work for you {% /card %} {% card title="Learn About Experimentation" href="/statsig-warehouse-native/features/statistics" icon="science" %} Learn about Statsig's modern Stats Engine {% /card %} {% /card-grid %} *** ================================================================================ # Warehouse Native Quickstart URL: https://docs.statsig.com/statsig-warehouse-native/guides/quick-start Updated: 2026-07-11 ================================================================================ This page walks you through connecting your data, configuring a metric, and getting experiment results with Statsig Warehouse Native. All you'll need is a table in your warehouse that has metric or event logging data. ## Step 1: Connect your warehouse Statsig uses your warehouse to store and analyze your experiment data; you have total control and visibility over the data itself. To connect your warehouse, go to your warehouse's setup page. * [Snowflake](/statsig-warehouse-native/connecting-your-warehouse/snowflake) * [Athena](/statsig-warehouse-native/connecting-your-warehouse/athena) * [BigQuery](/statsig-warehouse-native/connecting-your-warehouse/bigquery) * [Databricks](/statsig-warehouse-native/connecting-your-warehouse/databricks) * [Redshift](/statsig-warehouse-native/connecting-your-warehouse/redshift) ## Step 2: Connect to data To connect your event or metric data, you'll create a [Metric Source](/statsig-warehouse-native/configuration/metric-sources). Go to the *Metric Sources* page and click **Create** to make a new Metric Source. {% figure %} ![Create metric source button](/images/statsig-warehouse-native/guides/quick-start/66229d92-99a8-45c6-9a1c-7f8585465338.png) {% /figure %} If you have a table, use the `Table` metric source type and put in the path to your table. Otherwise, you can write a query to access or generate some test data. Press **Analyze** to generate samples from your table, then map required columns (Timestamp and UserID) so Statsig can connect your metric data to your assignment data. {% figure %} ![Metric source configuration mapping timestamp and user ID](/images/statsig-warehouse-native/guides/quick-start/c0982d71-71c5-4899-bed9-a72c7db59515.png) {% /figure %} Save your changes, and you've connected to your data! ## Step 3: Make a metric Now that you've connected to data, you can build metrics on top of this data. You can configure this programmatically later; for now, go to your *Metrics Catalog* and click **Create** to make a new metric. {% figure %} ![Metrics catalog create button](/images/statsig-warehouse-native/guides/quick-start/8b997a0c-22e9-409d-8918-d21a95af1367.png) {% /figure %} Point to the metric source you configured, name your metric, and press **Create**. This takes you to the new metric's page, where you can configure your metric. To get started, Statsig recommends making a [count metric](/statsig-warehouse-native/metrics/count). A count metric counts the number of rows in your metric source. This is useful for event logging. For example, you might filter to a "purchase" event to count the number of times users purchased an item. Select "Count" and save - or, feel free to pause here and explore the options here. ## Step 4: Connect an experiment Next, you'll connect to experiment data. If you have a table with exposures you've already logged, use that. Ensure you've logged the same identifier there as you used in your metric source. Otherwise, you can quickly follow the guide to setting up an [A/A test](/experiments/types/aa-test), using the same data you used for your exposures. This should generate a neutral experiment result. Go to *Assignment Sources* and create a new one. {% figure %} ![Create assignment source page](/images/statsig-warehouse-native/guides/quick-start/fe0e4c5d-e2a6-4943-9b09-cb81dbadee9e.png) {% /figure %} If using an AA test, follow the instructions to randomly assign users to groups. If you're using existing assignments, write a query to pull the data from your logs and map the unit, group, experiment, and timestamp columns. {% figure %} ![Assignment source query configuration](/images/statsig-warehouse-native/guides/quick-start/08e3f20c-1590-4b08-ad60-fa96ecbf15cb.png) {% /figure %} Pressing **Save and Scan** saves your new source and detects experiments that exist on the source. This takes seconds to a few minutes; once it's done, scroll down to look through the experiments Statsig found. {% figure %} ![Detected experiments table](/images/statsig-warehouse-native/guides/quick-start/8be2f079-4440-4742-9727-08e25b65c84b.png) {% /figure %} ## Step 5: Analyze your experiment Press **Create** on your experiment of interest to start creating your experiment. {% figure %} ![Experiment creation wizard](/images/statsig-warehouse-native/guides/quick-start/fc6e7092-b621-494a-bedb-8cd03a64d510.png) {% /figure %} Add a display name and hypothesis, then press **Create**. This takes you to the final setup step, where you specify the metrics for your experiment. Choose the metric you created in step 3. Statsig automatically detects the group split, but if the detected split is incorrect you can manually adjust it to the intended value. {% figure %} ![Experiment setup selecting metrics and group split](/images/statsig-warehouse-native/guides/quick-start/287bc60b-23a6-4681-ac26-282ce8b88c13.png) {% /figure %} Press **Save and Analyze**, and Statsig starts calculating Pulse Results. You can track the progress in the loading bar at the bottom of the experiment's results page. ## Step 6: Read results If everything worked, you should see: * Your hypothesis. This lives at the top of the results page to give context and guide interpretation of the results. * Cumulative exposures. This shows you the number of unique units exposed to each group, and the balance between groups * Your scorecard. This shows a quick summary of the observed differences in metrics between your experiment groups, with access to additional views and raw statistics {% figure %} ![Experiment results showing hypothesis and exposures](/images/statsig-warehouse-native/guides/quick-start/38f4f438-2592-44cb-9063-d3bc97da404b.png) {% /figure %} Explore the product and docs to learn more about these features: * Click into a result's error bar to view raw statistics, timeseries, and projected [timeline impact](/experiments/statistical-methods/topline-impact) * Hover over a metric to get detailed context on its inputs and how the pulse result was calculated * Go to the *Diagnostics* tab to view the [checks](/statsig-warehouse-native/features/monitor-an-experiment) Statsig automatically ran to validate your experiment results. * Select the reloaded timestamp to view the run time and query cost of your Pulse analysis, and the SQL queries used to calculate the results. * Go to the *Explore* tab to start filtering data, exploring results by dimensions, or running other follow-up analyses. * Go to the *Summary* tab to start putting together a [report](/statsig-warehouse-native/features/reports) to share the results of your analysis. * Start a discussion or add context, either in the *Discussion* tab or with in-context comments on top of the results themselves. The example experiment was not an AA test, and there was an experimental impact. The result is statistically significant, with an estimated lift of +12.89% ±0.93% from control to test. Select the result to view additional details, such as the expected change to the overall topline metric value if the experiment ships. {% figure %} ![Detailed metric lift view with significance](/images/statsig-warehouse-native/guides/quick-start/33e5fc2a-9b36-43f9-a6c2-2936867a7980.png) {% /figure %} You've completed the Quick Start guide. ================================================================================ # Comparing Warehouse Native and Cloud URL: https://docs.statsig.com/statsig-warehouse-native/native-vs-cloud Updated: 2026-07-11 ================================================================================ Statsig Warehouse Native and Statsig Cloud share many capabilities, but there are differences between the platforms. This page describes those differences to help you choose the right product for your experimentation needs. If a feature is critical for your evaluation, contact the support team, your sales contact, or the [Slack community](https://statsig.com/slack). ## Feature gates | Feature | Cloud vs. WHN Status | | ------------------------- | -------------------- | | Targeting | Identical | | Overrides | Identical | | SDKs | Identical | | Diagnostics | Identical | | Pulse | Identical | | Gate lifecycle management | Identical | ## E2E experiments (with SDK) | Feature | Cloud vs. WHN Status | | ------- | ---------------------------- | | Pulse | WHN offers on-demand reloads | ## Pulse (scorecards) | Feature | Cloud vs. WHN Status | | ----------------------- | ------------------------------------------------ | | CUPED | Identical | | CURE | WHN only for now | | Topline Impact | Identical | | Daily Time Series | Identical | | Frequentist vs Bayesian | Identical | | Sequential Testing | Identical | | BF Correction | Identical | | Power Analysis | Identical | | Custom Queries | Identical | | Guardrail Metric Alerts | Available in WHN, based on your pipeline cadence | | Export to CSV | Identical | | Share Links | Identical | ## Analysis only experiments (without SDK) | Feature | Cloud vs. WHN Status | | --------------------------- | -------------------- | | Overrides | Not relevant w/o SDK | | Diagnostics (SDK Related) | Not relevant w/o SDK | | Diagnostics (Data specific) | WHN has additional | | Capture Hypothesis | Identical | | Capture Images | Identical | | Experiment Groups | Identical | | Experiment Parameters | Not relevant w/o SDK | | SRM Checks | Identical | | View SQL | WHN-only | ## Core features | Feature | Cloud vs. WHN Status | | -------------- | -------------------- | | Layers | Identical | | Dynamic Config | Identical | | Dashboards | Identical | | Segments | Identical | | Holdouts | Identical | ## Insights | Feature | Cloud vs. WHN Status | | ------------------------- | -------------------- | | Experiment Timeline | Identical | | Knowledge Bank | Identical | | Metric Insights | Identical | | Metric Correlations | Identical | | Metric Impacts | Identical | | Active feature analysis | Identical | | Historic feature analysis | Identical | ## Metrics | Feature | Cloud vs. WHN Status | | ----------------------------------------- | -------------------- | | Metrics Explorer | Substantially Identical | | SDK Events Logstream | Identical | | Autocreate metric from SDK Events | Cloud only for now | | User Accounting Metrics (DAU, Stickiness) | Cloud only for now | | Automated A/A Tests | Cloud only for now | | Entity Property Source ID mapping | WHN only | ## Organization & Settings | Feature | Cloud vs. WHN Status | | ----------------- | -------------------- | | SSO | Identical | | Experiment Policy | Identical | | Change Reviews | Identical | | Custom UnitIDs | Identical | | Custom RBAC | Identical | ## Integrations & admin | Feature | Cloud vs. WHN Status | | ---------------------------------------- | -------------------- | | Change mgmt (Slack, Datadog, Github etc) | Identical | | CDBs (Segment/Rudderstack etc) | Not relevant for WHN | | Custom Environments | Identical | | Target Applications | Identical | | Console API | Identical | | Audit Log | Identical | | Audit Log API | Identical | | Autotune (MAB) | Identical | | Users Tab | Identical | ================================================================================ # Running a Warehouse Native POC URL: https://docs.statsig.com/statsig-warehouse-native/guides/running_a_poc Updated: 2026-07-11 ================================================================================ ## What Statsig Warehouse Native does **Statsig Warehouse Native** enables customers with existing metric logs to run analysis on their existing metric data, and optionally bring previous assignment data or offline experiments into the platform. Statsig WHN has two types of experiments: <ol> <li>**Assign and Analyze**: You can run an experiment on **web/mobile/app** and use Statsig’s SDKs to assign (bucket or randomize) users, and then analyze results.</li> <li>**Analyze**: You can run an experiment elsewhere (***your own SDK, email, direct mail, sms, ivr, etc.***) and use Statsig to analyze that data and calculate experiment results. Statsig reads this assignment data from your warehouse in [this format](/statsig-warehouse-native/configuration/assignment-sources#example-data); Statsig calls these [Assignment Sources](/statsig-warehouse-native/configuration/assignment-sources).</li> </ol> If you have a pre-existing experiment in your warehouse, start with an **Analyze** experiment. This is an effective way (less than 1 day) to establish a connection between Statsig and your warehouse, and to experience [experiment results](/experiments/interpreting-results/read-results) in the Statsig console. After that, run an **Assign and Analyze** experiment using Statsig's SDKs (typically an A/A test). The A/A **Assign and Analyze** experiment lets you test Statsig's SDKs and implementation process with your engineering and product team. ## Steps to running an effective proof of concept with Warehouse Native {% figure %} ![Warehouse Native POC workflow diagram](/images/statsig-warehouse-native/guides/running_a_poc/723b6c60-7151-4716-8970-0e52c391991d.png) {% /figure %} Follow these steps when planning your Warehouse Native implementation: 1. **Define your experiment(s) and metrics for validation** - A proof of concept determines if Statsig fits your experimentation needs; running an experiment is the most direct path for evaluation. **Responsible Party**: Typically a product or engineering lead. - Plan to run 1-2 production level experiments to validate. Past experiments, A/A tests or upcoming projects or product changes are great opportunities to implement a Statsig experiment! - Identify your hypothesis and [metrics](/statsig-warehouse-native/configuration/metrics) to validate this hypothesis. Statsig joins these metrics with unit assignment/exposure data and runs them through the stats engine. - If your team plans on running **analysis only**, identify the user **assignment** data that Statsig joins with the metric data. - This approach can yield results for analysis in as little as **30 minutes,** assuming data is readily available for ingestion - If your team plans on using the **Assign and Analyze** experimentation option, identify **where** the experiment will run. Typically, **web-based** experiments are easier to evaluate, but Statsig also has SDK support for server and mobile SDKs. - **Note**: It’s important the implementing team understands how the SDKs operate before executing a proof of concept. The [client](/client/introduction) and [server](/server/introduction) docs can help orient your team. - A typical evaluation takes **2-4 weeks** to account for experiment design, implementation, time to bake, and analysis. To ensure a successful POC, [have a well scoped plan](/statsig-warehouse-native/guides/running_a_poc#2-phase-0-scope--prepare-your-poc) and ensure the right teams are included to assist along the way. - Read [experimentation best practices](https://statsig.com/blog/product-experimentation-best-practices) to get an idea of how to best succeed. 2. **Connect the warehouse** - To query data and operate within your warehouse, you’ll need to allocate resources and connect to Statsig. You may choose to utilize an existing prod database or create a separate cluster specifically for experimentation (if you don’t already have one). - Statsig requires a role and the following access: - Read access to metric and exposure data - Write access so Statsig can write results and exposures back to the warehouse - Access to run jobs and query data - Find more guidance on [connecting with your specific warehouse vendor here](/statsig-warehouse-native/guides/connect). - Review the [data pipeline overview](/statsig-warehouse-native/analysis-tools/pipeline-overview) to understand how data flows for warehouse native jobs. 3. **Connect metric sources and define metrics** - Once the data warehouse has been connected, you can begin defining metric and assignment sources (if applicable) in Statsig. Statsig expects specific schemas to correctly map the data to its pipelines: {% figure %} ![Schema requirements diagram for metric sources](/images/statsig-warehouse-native/guides/running_a_poc/4fa8c5e2-c19f-4ef6-be70-f31b61170e7c.png) {% /figure %} Beyond these columns, the schema is flexible and can accept additional columns. You can use **Metadata** to filter metrics and perform more granular analysis. - Review guides for creating a [metric](/statsig-warehouse-native/configuration/metric-sources) and [assignment](/statsig-warehouse-native/configuration/assignment-sources) source - Follow the [data best practices](/statsig-warehouse-native/guides/best-practices) to ensure your queries are running efficiently. {% callout type="info" %} This section is important to review and can prevent unnecessary infrastructure costs! {% /callout %} After metric sources have been connected, configure [metrics](/statsig-warehouse-native/configuration/metrics) to perform various aggregations (e.g., Sum, Mean, Count, Unique Users) that represent what you’re trying to measure in your experiments. - [Supported metric types](/statsig-warehouse-native/configuration/metrics#metric-types) and ways to [configure them](/statsig-warehouse-native/configuration/metrics) - Use [cohort metrics](/statsig-warehouse-native/features/cohort-metrics) to measure impact during a certain time frame per user. 4. **Create and roll out an experiment** - In step 1, you defined the planned experiment(s) and the metrics used to validate them. With metrics and assignment sources configured, you can now create the experiment(s). Refer to the [detailed guide for experiment setup](/statsig-warehouse-native/guides/experiments), and consider these things as you complete this step: - Create your hypothesis and select the **experiment (assignment) source** - If using the SDK for assignment, the SDK itself serves as the assignment source. - [Custom IDs](/guides/experiment-on-custom-id-types) can be used but must first be configured; for example: `device_id`, `vehicleId`. - Refer to [advanced settings](/statsig-warehouse-native/features/configure-an-experiment#advanced-settings) for the many ways you can configure your experiment - As you roll out your experiment, you can [monitor the status with health checks](/statsig-warehouse-native/features/monitor-an-experiment) and get a readout of live exposures as they come through the SDK. - If you’re hoping to quickly validate the platform, you can create and run a [quick A/A test](/statsig-warehouse-native/guides/aatest). 5. **Read results** - Once the experiment has successfully run, read the results and ensure everything looks reasonable. Was your hypothesis validated or are the results surprising? Are the results easy to interpret and navigate for the teams involved? Refer to the [section on Pulse](/statsig-warehouse-native/guides/pulse) to get an idea of the high-level analytics capabilities. A few things to note: - You can slice results further using the [*Explore* tab](/statsig-warehouse-native/guides/pulse#explore), which lets you break down results by specific user and event properties. - You can configure exposure and metric data to be forwarded to your warehouse. - The [*Health Checks* (diagnostics) tab](/statsig-warehouse-native/guides/pulse#health-checks) surfaces the SQL used to generate results so you can validate any analysis performed on your systems. 6. **Finalize evaluation and next steps:** Ultimately, a POC validates a set of evaluation criteria that determines whether Statsig is a good fit for your team’s workflows. The following graphic provides high-level guidance on what to look for during your evaluation phase. {% figure %} ![POC evaluation checklist graphic](/images/statsig-warehouse-native/guides/running_a_poc/c5f86db5-6bf9-4a5e-9709-ede36384b3ca.png) {% /figure %} To move forward, convert your POC environment to a production-level implementation. Refer to the [production guide](/statsig-warehouse-native/guides/production) for details. Statsig support is available through the [community Slack channel](https://statsig.com/slack). ================================================================================ # How to Run a Playground Evaluation URL: https://docs.statsig.com/statsig-warehouse-native/guides/playground_eval Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Playground evaluations are only recommended for Enterprise-tier customers. {% /callout %} Many customers evaluating Statsig have sensitive data and long lead times to test on production data due to privacy and data security requirements. Statsig offers a read-only [Demo Project](https://console.statsig.com/whn_demo), but the demo project doesn't let you fully experience the warehouse native product. To get full access, contact support to request a Playground Evaluation project. This is a project hosted by Statsig with sample, near-real-time data sources that you can use to set up experiments, analyze metrics, and evaluate Statsig internally. You'll be an admin and can add teammates to explore the shared project together. ## Playground projects Playground projects come with some boilerplate connections hooked up: * A real-time event streaming metric source * A daily user-level metric source * An assignment source with a handful of experiments * A handful of pre-loaded metrics and experiments {% callout type="note" %} Playground projects don't come with any assign-and-analyze experiments, so some features that require SDK usage (e.g. Stratified Sampling) aren't available. You can use a normal demo project with an SDK on a local script to generate data for those use cases. {% /callout %} ## Evaluation Follow steps 2 and later from the [quick start](/statsig-warehouse-native/guides/quick-start) guide, starting with the tables available to the playground project. This gives you a sense of what running experiments and managing metrics in Statsig involves. After completing those steps, refer to the [POC guide](/statsig-warehouse-native/guides/running_a_poc) and simulate the steps relevant to your evaluation. Exploring the Pulse UI together has been useful for teams. The Statsig team welcomes any questions or feedback. ================================================================================ # Working With the SDK URL: https://docs.statsig.com/statsig-warehouse-native/guides/sdks Updated: 2026-07-11 ================================================================================ Warehouse Native works with any of Statsig's SDKs for logging events and getting feature flags or experiment assignments. This page is a brief overview of how Warehouse Native works with Statsig's SDKs. Refer to the [client](../../client/introduction) or [server](../../server/introduction) SDK docs for help setting up SDKs. ## Data Forwarding When you first set up your data connection, Statsig creates tables to forward datasets to and generates assignment and metric sources that include any user-level metadata fields you log as part of your evaluation. You can find the configuration for these datasets and the table Statsig creates or outputs data to in the advanced section of the warehouse connection page. {% figure %} ![Choose Groups](/images/whn/data_forwarding_whn.png) {% /figure %} ## Exposures Statsig calculates deduplicated first-exposure rollups for you on a daily basis and exports that miniaturized dataset to your warehouse. Additionally, on every Pulse load on the first day of an experiment, Statsig exports deduplicated exposures for the day to your warehouse in near real-time, up to 1 million exposures. ## Log Events You can also use Statsig's powerful event logging to send events to the Statsig SDK; Statsig later exports them to your warehouse for analysis. ================================================================================ # Synthetic A/A Test URL: https://docs.statsig.com/statsig-warehouse-native/guides/aatest Updated: 2026-07-11 ================================================================================ To create a quick A/A test, randomly split existing users you already have events for. Create an assignment source using the script below and you'll be ready to analyze your A/A test in minutes and see Pulse scorecards populate. Example script: ```sql SELECT user_id, timestamp, 'AA_Test_1' AS experiment_name, --CAST('AA_Test_1' as varchar) AS experiment_name for Redshift warehouse CASE WHEN <random_logic> THEN 'Control' ELSE 'Test' END AS GroupAssignment FROM <my_event/metrics_table> ``` Replace `<random_logic>` with the following based on your warehouse: * Bigquery: `mod(abs(farm_fingerprint(cast(user_id as string))), 100) < 50` * Redshift: `mod(abs(farmFingerprint64(cast(user_id as varchar))), 100) < 50` * Snowflake: `mod(abs(hash(cast(user_id as string))), 100) < 50` * Databricks: `mod(abs(hash(cast(user_id as string))), 100) < 50` * Athena: `mod(abs(cast(conv(substr(md5(cast(user_id as varchar)), 1, 16), 16, 10) as bigint)), 100) < 50` ================================================================================ # Running Email Experiments URL: https://docs.statsig.com/statsig-warehouse-native/guides/email-experiments Updated: 2026-07-11 ================================================================================ Email marketing experimentation is a common use case that Statsig Warehouse Native handles by analyzing the downstream outcomes of an email experiment. This guide covers the key considerations for configuring this type of experiment. ### Data access offered by your marketing platform > Does the platform offer the ability to ETL email engagement events (sends, deliveries, email opens) to your data warehouse either directly, or through a CDP or other pipeline? Data access may be critical to the viability and quality of your experiment, depending on your specific use case. In particular, `email open` events are useful for controlling the population of users Statsig counts for analysis. For example, if you send 20,000 emails and only 12,000 are delivered and opened, counting only the 12,000 email-open users in the test population (rather than the entire recipient list) avoids diluting the population and achieves more statistical power. Use email open events to manually create an assignment source for an `Analyze Only` experiment, or to [filter your analysis population](/statsig-warehouse-native/configuration/qualifying-events/#filter-by-qualifying-events) for an `Analyze Only` experiment. ### Configuring scorecard metrics > What are your key down-funnel (for example, on-site or in-app) success metrics for the experiment, and can you unify email recipients with their subsequent down-funnel interactions? Email open events are useful for filtering the test population or for monitoring purposes, but aren't typically a primary metric unless the experiment is testing different subject lines. Consider which down-funnel metrics to include as primary metrics for deciding on your experiment. Attributing down-funnel interactions that occur before user login can be complex, but [ID resolution in Statsig](/statsig-warehouse-native/features/id-resolution/) solves this. Down-funnel metrics that occur after login are easy to attribute to the experiment, because you'll have the user ID and email address to map directly to the identifiers on your assignment source. ### Designing test groups and running the test > What options does your email platform offer for defining test and control groups? Does the platform require you to upload a list of users for each group, or does it natively support A/B cohort definitions? If your platform offers native A/B split capabilities, configure your test as an `Analyze Only` experiment in Statsig. Select **Analyze** and leave the **Experiment Completed** checkbox unchecked. Statsig then continues computing results from the first exposure time until you decide to conclude the experiment. If you need to manually create the test groups, set up an `Assign and Analyze` experiment and use Statsig's SDKs to generate your test and control lists. Loop over all intended recipients in code and call `getExperiment`. This captures each test group assignment and automatically writes the assignment events to your data warehouse through the [Statsig managed exposure pipeline](/statsig-warehouse-native/connecting-your-warehouse/forwarded-data). Select **Start** before running your assignments script, then keep the test in a running state until you decide to conclude the experiment. ================================================================================ # Running Analysis Across Unit Types, AKA Cluster Experiments URL: https://docs.statsig.com/metrics/different-id Updated: 2026-07-11 ================================================================================ There are two common scenarios where the experiment assignment unit differs from the analysis unit: 1. Measuring session-level metrics for a user-level experiment. Ratio metrics are commonly used to solve this (covered on this page). 2. Measuring logged-in metrics (eg. revenue) on a logged-out experiment. There are two solutions: a. Running the experiment at the [device-level](/guides/first-device-level-experiment), with device-level metrics collected even after the user is logged-in. b. Using [ID resolution](/statsig-warehouse-native/features/id-resolution). This page explains how to set up the first scenario using Warehouse Native. {% figure %} ![Workflow diagram for analyzing metrics at different ID levels](/images/metrics/different-id/0b75615f-2b66-44f4-b6e0-e0bd3e555199.png) {% /figure %} ## Example: Organizations and Users Scenario: * Your metrics source has both `org_id` and `user_id`. * The relationship between `org_id` and `user_id` is 1-to-many. A single `org_id` can be associated with multiple users (`user_id`), but a `user_id` is only associated with a single `org_id`. * Statsig assigns your experiment at the `org_id` level. * You are interested in understanding the treatment effect at the `user_id` level, such as revenue per user. ### 1. Set up the metric source with `org_id` as an ID type * In this table, each row of data should have both `org_id` and `user_id`. {% figure %} ![Metric source table setup with org_id and user_id fields](/images/metrics/different-id/a99a4577-8be5-4001-ac4d-2297f3b2fff0.png) {% /figure %} ### 2. Choose your assignment source, where the unit of assignment is `org_id`. {% figure %} ![Assignment source configuration selecting org_id unit](/images/metrics/different-id/16472cd7-1aa1-44a2-9a6b-0f789ac5308e.png) {% /figure %} ### 3. Define your metric of revenue per `user_id` * Your denominator should be `count distinct user_id` instead of `unit count`, because the latter is equivalent to `count distinct org_id` in an `org_id` level experiment. {% figure %} ![Metric definition screenshot showing revenue per user_id formula](/images/metrics/different-id/ca4c9076-28e1-4cf8-8aa1-2127def7d771.png) {% /figure %} ### 4. Set up the experiment with `org_id` {% figure %} ![Experiment setup specifying org_id as unit type](/images/metrics/different-id/02f9c6bb-0b32-4caf-a529-5bacc2a56d44.png) {% /figure %} ## How the Stats Engine handles cluster experiments The Stats Engine uses the delta method to calculate variance and confidence intervals. * For mean metrics, the Stats Engine records the number of observations per exposed unit in the records column of the staging data. This value acts as the denominator or cluster-size value for delta calculations. * For general ratio metrics, the Stats Engine tracks the two-component metrics (the ratio and the denominator) as independent metrics, then combines them during the pulse analysis to derive a single metric. For more information about the delta method, go to [Statsig - Delta Method Methodology](/experiments/statistical-methods/methodologies/delta-method). The delta method accounts for the covariance between the numerator and the denominator (more users per org is correlated with more revenue). Refer to section 3 of [this paper](https://alexdeng.github.io/public/files/kdd2018-dm.pdf) for details. This approach is also relevant for analyzing event-level outcomes, such as average purchase value, where randomization occurs at the user level, and each user may experience multiple session events. ================================================================================ # Setup Checklist URL: https://docs.statsig.com/statsig-warehouse-native/guides/checklist Updated: 2026-07-11 ================================================================================ After you've connected your warehouse and set up both metrics and assignment sources, verify your setup by checking the following items: 1. Primary keys 2. Timestamps 3. Duplication 4. Data availability After completing these checks, your offline results should align with those in the Statsig console when advanced features are disabled. ## 1. Primary keys When setting up an experiment, you can select the unit of assignment, acting as the primary key to join the assignment with metrics. The assignment source and the metrics source must use the same primary key. In an Analyze-Only experiment, this primary key can be selected from the unit IDs defined by your assignment source. * Ensure the unit ID in your assignment source matches the unit ID in your metrics source. In an Assign and Analyze experiment, the Statsig SDK generates the primary key (unit ID). * You can verify this unit ID in the statsig\_forwarded\_exposures table within the assignment sources. * You must either forward the unit ID to the SDK ([docs](/client/introduction)) or use the SDK to manage your features and correspondingly generate the metrics table. ## 2. Timestamps Analyze metric data only after a user has been exposed to the experiment. Pre-experiment data has no average treatment effect, so including it dilutes results. Statsig uses a timestamp-based join for this purpose, with an option for a date-based join for daily data. The join should look like the SQL snippet below: ```sql expandable WITH metrics as (...), exposures as (...), joined_data as ( SELECT exposures.unit_id, exposures.experiment_id, exposures.group_id, metrics.timestamp, metrics.value FROM exposures JOIN metrics ON ( exposures.unit_id = metrics.unit_id AND metrics.timestamp >= exposures.first_timestamp ) ) SELECT group_id, SUM(value) as value FROM joined_data GROUP BY group_id; ``` ## 3. Exposure duplication Exposure data must be deduplicated before joining to ensure a single record per user. Many vendors also manage crossover users (users present in more than one experiment group) by removing them from analysis or alerting when this occurs with high frequency. ```sql expandable SELECT unit_id, experiment_id, MIN(timestamp) as first_timestamp, COUNT(distinct group_id) as groups FROM <exposures_table> GROUP BY unit_id, experiment_id, group_id HAVING COUNT(distinct group_id) = 1; ``` ## 4. Data availability When comparing a platform analysis to an **existing** experiment analysis that was run in the past, the underlying data may have fallen out of retention or been deleted. To check this, compare the table's retention policy to the analysis dates used in your original experiment analysis to confirm the data still exists. ## Verify that results match After completing the above four steps, your offline analysis should produce results that match those in the Statsig console. {% callout type="note" %} The Statsig Console includes several advanced features, such as [winsorization](/experiments/statistical-methods/methodologies/winsorization#winsorization-statsig-whn), [CUPED](/experiments/statistical-methods/variance-reduction#cuped), and [the delta method](/experiments/statistical-methods/variance#ratio-and-mean-metrics) to address ratio metrics. Disable these features initially when comparing results. {% /callout %} [This article](https://www.statsig.com/blog/how-to-analyze-an-experiment-from-databricks-tables) provides an example of conducting offline calculations in Databricks. If you have additional questions, send Statsig a Slack message. ================================================================================ # Warehouse Native Debugging Guide URL: https://docs.statsig.com/statsig-warehouse-native/guides/debugging Updated: 2026-07-11 ================================================================================ ## Common debugging scenarios When interacting with complex data sources, you may encounter unexpected results. Statsig proactively monitors for these and notifies you if something looks off. This guide helps you identify what went wrong and reconcile differences in results between Statsig's analysis and your own. ## Common issues that cause errors or missing data ### Mismatched IDs In many cases, you may have multiple versions of the same ID across tables. For example, the user `abc123` in one source might be `USER_abc123` in another, or certain loggers may hash ID values for privacy reasons. This usually triggers a Statsig alert that it was unable to join data between sources. To resolve this: * Go to the relevant sources and run the sample queries to check for obvious ID mismatches. * If no mismatch is obvious, find the job that is unable to join sources and copy the SQL. Working through the SQL query should pinpoint where the mismatch is occurring. ### Conflicting filters Statsig allows a high degree of customization in explore queries and on explore pages. This can lead to scenarios where two conflicting filters are added that, together, never pass. For example: * You have a metric with a cohort window from 7 to 13 days, but set up your experiment analysis to run on users' first 6 days. These two filters return no users. * You create a count metric filtered to `event='purchase'`, and then create a local metric that filters to `event='checkout'`. This set of filters also returns a null set. To resolve conflicting filters, go to the Unit-Level Aggregations jobs (where filters are rendered) and search for the metric name of interest in the SQL code. ## Common points of confusion Many data scientists run their own analyses using the Statsig staging data in their warehouse. In some cases, they see very different results. A few common methodology differences tend to explain these discrepancies: ### Join conditions Statsig joins events and metrics to exposures based on the event or metric input data occurring after the user was exposed to the experiment being analyzed. If you enable the Treat Timestamp as Date setting, the join is done by comparing dates. By default, Statsig uses a timestamp. Not having a time filter, or using a date when Statsig uses a timestamp (or vice versa), can yield very different results. ### Winsorization/capping You can configure metrics to be capped or winsorized in Statsig. If you have extreme outliers, you'll see significant differences in results if you don't apply this procedure. You can apply winsorization yourself, or create a local metric (or clone the metric) without winsorization. ### CUPED CUPED can greatly reduce variance, and for aggregation (non-ratio) metrics can shift the group-level mean estimates if there is pre-experiment bias. You can disable CUPED in the scorecard at any time to view the non-CUPED results for comparison. ### Metric types In some cases, users have assumed that their input data is binomial, meaning the metric is only 1 or 0 at a unit/user level. If this isn't the case and the metric is configured as a count or sum, the result can be much higher variance than expected. ================================================================================ # View SQL URL: https://docs.statsig.com/statsig-warehouse-native/guides/sql Updated: 2026-07-11 ================================================================================ Statsig Warehouse Native runs SQL in your warehouse to generate experiment results. The queries are fully transparent and visible in the console, so you can trace any result back to its calculation, artifacts, and raw events. The [pipeline-overview](/statsig-warehouse-native/analysis-tools/pipeline-overview) describes how queries are orchestrated to produce experiment results. To debug or inspect details, find the experiment-specific SQL by going to the experiment’s Pulse view, then selecting view history, then view details on a specific pulse load. That page shows all jobs that contributed to that pulse load. {% figure %} ![Pulse view SQL query details interface](/images/statsig-warehouse-native/guides/sql/f34e1265-e303-4705-b8cd-1a170a2af91e.png) {% /figure %} A few queries with a \[Health Check] prefix automatically check the health of the experiment. Go to [Monitor an Experiment](/statsig-warehouse-native/features/monitor-an-experiment) to learn what these queries do. For experiment result calculation queries, below are one-sentence summaries of what each query does. * First exposure: Generate the first exposure time of this experiment at the unit level, given the assignment source. * Exposure summary: Summarize the first exposures at the group level and generate cumulative exposures. * User-level calculations: Calculate unit-level metrics at unit \_ metric \_ day grain. * Windowed rollups: Rollup unit-level metrics at different time windows. * Pluse calculation: Generate the sample parameters that are necessary for calculating treatment effects, such as units, total, mean, standard deviation, population variance, covariance, etc. ================================================================================ # Bootstrapping Your Experimentation Program URL: https://docs.statsig.com/statsig-warehouse-native/guides/experimentation-program Updated: 2026-07-11 ================================================================================ This guide outlines a proven approach to starting an experimentation program using Statsig at your company. You can get started with Statsig’s free tier, which includes up to 5M events per month and no limit on the number of team members. ## 1. Generate product ideas Generating ideas for experimentation is essential to running a successful program. Common methods include dogfooding your product, studying competitors, analyzing existing metrics, and gathering UX research and user feedback. Here are three common frameworks used at Statsig and among customers: ### Generate ideas based on your business context 1. **Turn every upcoming feature into an experiment**: * At Statsig, new features are placed behind feature flags. Statsig’s [Feature Flags](/feature-flags/overview) (also called "gates") automatically convert a feature rollout into an A/B test, measuring the impact on key metrics as the rollout progresses. If your engineering team already uses feature flags for releases, this is the most direct path to your first A/B test. 2. **Work backwards from company goals**: * Many customers begin by aligning experiments with strategic company goals. For example, if the goal is to drive adoption growth, break down the goal into actionable metrics (e.g., total revenue, monthly active users) and run experiments that can move those metrics incrementally. Instead of targeting broad goals like revenue, focus on specific, actionable metrics like checkout completions or user onboarding time. 3. **Break down your business levers**: * Decompose key drivers of your business to reveal more granular areas where you can experiment. For example, an e-commerce business might focus on increasing the checkout rate, which could lead to experiments on improving the CTA, reducing friction, or building urgency. {% figure %} ![Business levers breakdown diagram](/images/statsig-warehouse-native/guides/experimentation-program/162062869-ead09b8f-0d2e-4c01-8bed-5c40039abe56.png) {% /figure %} ### Prioritize your ideas Use a simple return-on-investment (RoI) approach to prioritize your experimentation ideas: * Choose ideas that are **simple to execute**, **easy to measure**, and **high in potential**. * Start with ideas that are easy to implement. After running a few experiments, focus on higher-impact or more controversial ones. Generating a prioritized list of ideas accounts for more than half the work in running a successful experimentation program. {% callout type="tip" %} **(Optional) Create a Backlog** You can create an experimentation backlog from your list of ideas. Because running experiments often generates more ideas, treat the backlog as optional and useful for future planning. {% /callout %} ## 2. Run a simple A/B test ### Clarify your hypothesis Start by formulating a hypothesis for one of your high-priority ideas. A well-framed hypothesis defines the expected outcomes of your experiment, which makes it easier to align with stakeholders and evaluate results objectively. A hypothesis consists of three key elements: **Action**, **Predicted Outcome**, and **Rationale**. Example: | Element | Example | | --------------------| ---------------------------------------------------------------------------------- | | **Action** | If we reduce the number of fields on the sign-up page | | **Predicted Outcome**| Then the percentage of users that complete sign-up will increase | | **Rationale** | Because users will spend less time and effort, leading to higher completion rates | ### Create your experiment To create an A/B test, go to the [Statsig Console](https://console.statsig.com/login): 1. Name your experiment. 2. Define the variants you want to test. 3. Set the percentage of users eligible to participate. ### Set up your application for the experiment To start the experiment: 1. Use a Statsig SDK to assign users to experiment variants. 2. Validate assignments through the *Diagnostics* tab in the Statsig console. Refer to the [quick start guide](/guides/abn-tests) to set this up. To compute results, you can: * Log events directly from your application. * Pipe events to Statsig through a service like Segment. * Configure Statsig to ingest data from your warehouse. ### Validate your experiment configuration After your application is set up: * Validate the configuration through the Statsig console, which provides a live exposure stream as users encounter different variants. * Statsig flags issues, such as missing identifiers or mismatches between exposure and application events, so you don’t miss results. After validation, select **Start** to begin the experiment. {% callout type="tip" %} **(Optional) Set a Target End Date** Set a target end date using Statsig's power calculator. This ensures you make decisions only after the experiment has gathered enough data for reliable results. {% /callout %} {% callout type="tip" %} **Running Experiments in Registration Funnels** If you don't have user IDs for users who haven't signed up yet, create an experiment using a `stableID`, which Statsig SDKs auto-generate. This acts as the unit of analysis. {% /callout %} {% callout type="tip" %} **Selecting Key Metrics** When setting up an experiment, configure the key metrics to evaluate your hypothesis. Statsig computes results for all your metrics, but focusing on key metrics helps avoid cherry-picking. {% /callout %} ### Organize regular experiment reviews Experiment reviews provide a structured environment to discuss results, share insights, and align on decisions. Run weekly or bi-weekly reviews to reinforce a culture of data-driven decision-making and experimentation rigor. ## 3. Share your results Sharing your results widely is key to building momentum for experimentation. Here are a few ways to share insights across your organization: * **Monthly Experimentation Update**: Summarize the number of experiments run, their business impact, and key learnings. * **Slack #experimentation Channel**: Use a dedicated channel to post real-time experiment learnings and foster discussion. * **Weekly Experiment Review**: Open these reviews to a broader audience to increase visibility and foster engagement. ## 4. Accelerate your speed of experimentation ### Cement the value of experimentation As you gain confidence, try balancing ideas with more uncertain outcomes. Counterintuitive results can solidify the value of experimentation and lead to new discoveries. ### Increase experimentation with self-serve experiments Statsig’s results are trustworthy and easy to interpret, making it possible for any team to run experiments. Reduce the barrier to entry for experimentation by educating team members on how to create and run their own experiments. ### Build incentives to run more experiments Encourage engineers and teams to prioritize impact over shipping new features. Reward them for experimentation outcomes rather than the volume of shipped features, ensuring a focus on end-user value. ## 5. Build a culture of learning ### Accelerate learning > “If you double the number of experiments you do per year, you're going to double your inventiveness.” > Jeff Bezos, Founder of Amazon Frequent experimentation leads to faster learning, helping teams discover user preferences and make data-driven product decisions. Even inconclusive results can prompt deeper analysis and more informed hypotheses. ### Measure the impact of your experimentation program Evaluate your program’s success using key criteria such as: | Success Criteria | Score | Weight | | ------------------------------------------------------------------- | ----- | ------ | | Time spent to set up an experiment | | | | Time spent to prepare experiment results | | | | Percentage of decisions made using trustworthy data | | | | Number of experiments run per week | | | | Quality of experiments run | | | | Time spent indexing/searching past results and discussions | | | Use these criteria to score your team's progress, set goals, and demonstrate the productivity boost from a well-implemented experimentation program. ================================================================================ # Forwarded Data URL: https://docs.statsig.com/statsig-warehouse-native/connecting-your-warehouse/forwarded-data Updated: 2026-07-11 ================================================================================ If you log events or exposures through Statsig's SDK, Statsig forwards that data back to your warehouse in near-real time, on demand. ## Setting up tables for forwarded data By default, when you set up a data connection, Statsig automatically creates tables named `exposures` and `events` to receive forwarded SDK data. To rename these tables, go to the data connection page, open the **Advanced** tab, and update the table names. {% figure %} ![Forwarded data table configuration interface](/images/whn/forwarded_data_tables.png) {% /figure %} {% callout type="note" %} If you've already had data exported and change the table name, Statsig writes future data to the new table. {% /callout %} {% callout type="info" %} Statsig doesn't forward non-production exposures or log events to external warehouses. {% /callout %} ## Exposures Logging exposures with Statsig provides real-time diagnostics on the Statsig console and real-time aggregations like exposures by hour. When you run Pulse analysis, Statsig fast-forwards raw exposures to catch up with the real-time stream. The fast-forward provides all users in your experiment and Pulse results as fresh as approximately 15 minutes, assuming events and metrics arrive at the same speed. Statsig performs a just-in-time update of exposures in your warehouse when Pulse is loaded, for the first 1 million exposures logged to the experiment. After that threshold, Statsig batches, deduplicates, and writes exposures to your warehouse once a day. Statsig doesn't deduplicate fast-forwarded exposures, and some fields are missing (notably `user_dimensions`). These fields are included in the subsequent daily load. Each day, Statsig exports a deduplicated digest to your warehouse to ensure consistency. The digest is deduplicated with the fast-forwarded data as part of the standard Pulse pipeline. ## Duplicate exposure records Statsig retains exposure data for 30 days and can't deduplicate beyond that window. After 30 days, Statsig re-sends exposures for units first exposed more than 30 days ago that were re-exposed on a given day. Statsig correctly deduplicates these records during analysis in your warehouse. The table doesn't contain unique user records, even after filtering out fast-forwarded exposures, which can also include duplicate records. {% callout type="info" %} For gates with 0% or 100% rollout, Statsig doesn't forward exposure to your warehouse by default. If you need them, contact the support team, your sales contact, or the [Slack community](https://statsig.com/slack). {% /callout %} ## Events If you use Statsig SDKs to capture custom events, Statsig exports those events to your warehouse hourly. Pulse results on metrics derived from those events are as fresh as approximately 1 hour. ================================================================================ # Connect Your Warehouse URL: https://docs.statsig.com/statsig-warehouse-native/guides/connect Updated: 2026-07-11 ================================================================================ {% callout type="note" %} Warehouse Native is part of Statsig's Enterprise tier. [Contact us](https://statsig.com/contact/demo) to get started. {% /callout %} To run analysis on your warehouse, Statsig needs to connect to your warehouse through a service user. You control the access this user gets to your warehouse. In general, Statsig requires: * Read access to metric data and exposures data * Write access to an isolated Statsig Staging database for writing results and exporting data * Access to run jobs and queries ## Choose your warehouse The following warehouses/tools are supported in Statsig Warehouse Native: * [Snowflake](/statsig-warehouse-native/connecting-your-warehouse/snowflake) * [Athena](/statsig-warehouse-native/connecting-your-warehouse/athena) * [Bigquery](/statsig-warehouse-native/connecting-your-warehouse/bigquery) * [Databricks](/statsig-warehouse-native/connecting-your-warehouse/databricks) * [Redshift](/statsig-warehouse-native/connecting-your-warehouse/redshift) ================================================================================ # Snowflake Connection URL: https://docs.statsig.com/statsig-warehouse-native/connecting-your-warehouse/snowflake Updated: 2026-07-11 ================================================================================ ## How Snowflake connection works To set up a Warehouse Native connection with Snowflake, Statsig needs the following: * Account Name * Database Name * Schema Name * Service User Name * If authenticating using login credentials: * Service User Password * If authenticating using key-pair authentication: * Private Key * Private Key Passphrase (Optional) The service user needs the following permissions: * READ on any tables you are using for experimentation * USAGE/WRITE on a Statsig-specific schema that Statsig uses to materialize temp tables and results {% callout type="info" %} If your data warehouse is IP protected, you must include allowlisting of Statsig IP ranges in your setup steps. {% /callout %} ### Account name For the Account Name field, enter in the format `<id>.<region>.<provider>`. For example: `xy12345.us-central1.gcp` To get this information, navigate to the bottom left of your Snowflake console, as shown below, and copy the link URL: {% figure %} ![Frame 6](/images/statsig-warehouse-native/connecting-your-warehouse/snowflake/187517221-4bb3dce3-8b8f-4f30-b4d4-fd12e5249722.png) {% /figure %} The copied URL will look something like this: `https://xy12345.us-central1.gcp.snowflakecomputing.com` Extract the required Account Name value from this URL. For this example, the Account Name is `xy12345.us-central1.gcp`. {% callout type="info" %} ### Using \<orgname>-\<account\_name> for account name For the Account Name field, you can also enter your Snowflake [account identifier](https://docs.snowflake.com/en/user-guide/admin-account-identifier.html#format-1-preferred-account-name-in-your-organization), which typically takes the form `<orgname>-<account_name>`. To find the `<orgname>` in the Snowflake console, click on your account profile (usually at the bottom left) to view account details as shown below. {% figure %} ![Snowflake account profile interface](/images/statsig-warehouse-native/connecting-your-warehouse/snowflake/195217037-ad630f37-a8fe-4b61-823f-ce0e8c984ed0.png) {% /figure %} {% /callout %} ### Database and schema name Provide the Schema and corresponding Database where Statsig will materialize results. {% figure %} ![Frame 7](/images/statsig-warehouse-native/connecting-your-warehouse/snowflake/187517225-017b4626-eaea-443b-a042-59fd474ae657.png) {% /figure %} ### Key-pair authentication To set up key-pair authentication, follow the [Snowflake documentation](https://docs.snowflake.com/en/user-guide/key-pair-auth) to generate the private and public keys, then set the public key on the service user. Provide the private key in the field below: {% figure %} ![Key-pair authentication configuration interface](/images/statsig-warehouse-native/connecting-your-warehouse/snowflake/1fa7bda1-5d9c-414b-8e2f-7e36b900acfb.png) {% /figure %} ### Boilerplate setup SQL To create a Statsig user with sufficient privileges and a Statsig staging schema, run the following code in a Snowflake worksheet with sysadmin and securityadmin roles. Replace `<USER>` and `<PASSWORD>` with your values, which you copy into the Statsig console. Adjust the warehouse size based on the scope of your data. ```sql expandable BEGIN; -- set up variable values to be used in statements later -- make sure to configure user_name and user_password with your own values SET user_name = '<USERNAME>'; -- REPLACE WITH YOUR OWN VALUE SET user_password = '<PASSWORD>'; -- REPLACE WITH YOUR OWN VALUE SET role_name = 'STATSIG_ROLE'; -- change role to sysadmin for warehouse / database steps USE ROLE sysadmin; -- create a warehouse, database, schema and tables for Statsig CREATE OR REPLACE WAREHOUSE STATSIG WITH warehouse_size='XLARGE'; -- adjust based on your data size CREATE DATABASE IF NOT EXISTS STATSIG_STAGING; CREATE SCHEMA IF NOT EXISTS STATSIG_STAGING.STATSIG_TABLES; -- change current role to securityadmin to create role and user for Statsig's access USE ROLE securityadmin; -- create role for Statsig CREATE ROLE IF NOT EXISTS identifier($role_name); GRANT ROLE identifier($role_name) TO ROLE SYSADMIN; -- create a user for Statsig CREATE USER IF NOT EXISTS identifier($user_name) password = $user_password default_role = $role_name default_namespace = STATSIG_STAGING.STATSIG_TABLES default_warehouse = STATSIG; GRANT ROLE identifier($role_name) TO USER identifier($user_name); -- grant Statsig role access to create warehouse and schema GRANT USAGE ON WAREHOUSE STATSIG TO ROLE identifier($role_name); GRANT USAGE ON SCHEMA STATSIG_STAGING.STATSIG_TABLES TO ROLE identifier($role_name); GRANT CREATE SCHEMA, MONITOR, USAGE ON DATABASE STATSIG_STAGING TO ROLE identifier($role_name); -- ONLY GIVE THIS LEVEL OF ACCESS in the staging schema. GRANT CREATE TABLE, CREATE FUNCTION ON SCHEMA STATSIG_STAGING.STATSIG_TABLES TO ROLE identifier($role_name); GRANT SELECT, UPDATE, INSERT, DELETE ON ALL TABLES IN SCHEMA STATSIG_STAGING.STATSIG_TABLES TO ROLE identifier($role_name); GRANT SELECT, UPDATE, INSERT, DELETE ON FUTURE TABLES IN SCHEMA STATSIG_STAGING.STATSIG_TABLES TO ROLE identifier($role_name); GRANT OWNERSHIP ON FUTURE TABLES IN SCHEMA STATSIG_STAGING.STATSIG_TABLES TO ROLE identifier($role_name); -- grant Statsig role read access to database and schema passed in -- do this at a table level, database level, and/or schema level -- for data Statsig needs to access GRANT USAGE ON DATABASE <DATABASE> TO ROLE identifier($role_name); GRANT USAGE ON SCHEMA <DATABASE>.<SCHEMA> TO ROLE identifier($role_name); GRANT SELECT ON ALL TABLES IN DATABASE <DATABASE> TO ROLE identifier($role_name); GRANT SELECT ON FUTURE TABLES IN DATABASE <DATABASE> TO ROLE identifier($role_name); GRANT SELECT ON ALL VIEWS IN DATABASE <DATABASE> TO ROLE identifier($role_name); GRANT SELECT ON FUTURE VIEWS IN DATABASE <DATABASE> TO ROLE identifier($role_name); COMMIT; ``` ### What IP addresses will Statsig access data warehouses from [Go to FAQ](/data-warehouse-ingestion/faq#what-ip-addresses-will-statsig-access-data-warehouses-from) ### Additional setup for Warehouse Explorer Warehouse Explorer lets you find and bring data from any table into Statsig for ad-hoc analysis. To enable Warehouse Explorer, grant Statsig additional permission to query the INFORMATION\_SCHEMA metadata tables. These tables allow the Statsig user to read schema, column, and table definitions. ```sql GRANT USAGE ON SCHEMA <DATABASE>.INFORMATION_SCHEMA TO ROLE identifier($role_name); ``` ================================================================================ # Athena Connection URL: https://docs.statsig.com/statsig-warehouse-native/connecting-your-warehouse/athena Updated: 2026-07-11 ================================================================================ ## Athena Warehouse Native overview To set up a connection with Athena, Statsig needs the following: * An S3 Bucket * A Glue Database for staging * An S3 Query Result Location * Athena Access Permissions (using an AWS Role or an AWS User) ## Set up Statsig staging structure 1. Create or choose an S3 Bucket. Statsig uses a subfolder inside this S3 Bucket to store all staging data. Statsig has write-access to ONLY this scoped subfolder of this S3 Bucket (specifically labeled `Statsig S3 Folder` in your Data Connection settings in the Statsig Console). 2. Create or choose a Glue Database (can be `default`). Statsig uses this as a staging Database to create and manage tables. Statsig can drop/create tables within ONLY this staging Database. 3. Choose an S3 Query Result Location folder within the S3 Bucket. This S3 location acts as the Output Location for `SELECT` queries run in your Athena Warehouse. You can specify this location either: * Explicitly as an S3 location (ex: `s3://my_bucket/my_query_results_folder/`) * OR as part of a setting within an Athena Workgroup * NOTE that your workgroup must have the 'Query result location' field populated accordingly 4. Add this information, along with your AWS Region, to your Data Connection settings in the Statsig Console. ## Grant permissions to Statsig Grant the following permissions for Statsig from your AWS console: * READ on any tables and data you are using for experimentation * USAGE and WRITE on a Statsig-specific schema used to materialize temp tables and results. This enables caching and incremental loads. You specify which Glue Database and S3 Bucket to use, and Statsig creates a Statsig S3 Subfolder for staging operations. 1. Create an AWS IAM Policy to house the required access permissions This policy contains the permissions required for Statsig to access your warehouse. You can return to this policy later and edit it as needed. * In your AWS IAM Dashboard, select the Policies page under the Access Management tab * Click 'Create policy' * Switch the Policy Editor type from 'Visual' to 'JSON' * Copy and paste the below JSON template block * Replace the placeholders with your setup information and the Statsig S3 Folder (specified in your Statsig project's Data Connection settings) * Specify all S3 locations and Glue Databases of any read-only assignment/metric data * Remove the descriptor comments ```json expandable { "Version": "2012-10-17", "Statement": [ // Allow Statsig to recognize your staging S3 Bucket { "Effect": "Allow", "Action": [ "s3:ListBucket", "s3:GetBucketLocation", "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ], "Resource": "arn:aws:s3:::__S3_BUCKET__" }, // Allow Statsig to read events/exposures data from your S3 Buckets { "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::__PATH_TO_YOUR_READONLY_DATA__/*" }, // Allow Statsig to read events/exposures tables from your Glue Databases { "Effect": "Allow", "Action": [ "glue:GetTable", "glue:GetTables", "glue:GetDatabase", "glue:GetDatabases", "glue:GetPartition", "glue:GetPartitions" ], "Resource": [ "arn:aws:glue:__REGION__:__YOUR_AWS_ACCOUNT_ID__:database/__YOUR_READONLY_DATABASE__", "arn:aws:glue:__REGION__:__YOUR_AWS_ACCOUNT_ID__:table/__YOUR_READONLY_DATABASE__/*" ] }, // Allow Statsig to read/write/use data and tables in an isolated staging S3 subfolder { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "athena:StartQueryExecution", "athena:GetQueryResults", "athena:GetQueryExecution", "athena:StopQueryExecution", "glue:GetTable", "glue:GetTables", "glue:CreateTable", "glue:UpdateTable", "glue:DeleteTable", "glue:GetPartition", "glue:GetPartitions", "glue:CreatePartition", "glue:UpdatePartition", "glue:DeletePartition", "glue:BatchCreatePartition", "glue:BatchDeletePartition", "glue:GetDatabase", "glue:GetDatabases" ], "Resource": [ "arn:aws:s3:::__S3_BUCKET__/__PATH_TO_S3_QUERY_RESULTS_FOLDER__/*", "arn:aws:s3:::__S3_BUCKET__/__STATSIG_S3_FOLDER__/*", "arn:aws:athena:__REGION__:__YOUR_AWS_ACCOUNT_ID__:workgroup/__WORKGROUP_NAME__", "arn:aws:glue:__REGION__:__YOUR_AWS_ACCOUNT_ID__:catalog", "arn:aws:glue:__REGION__:__YOUR_AWS_ACCOUNT_ID__:database/__GLUE_STAGING_DATABASE__", "arn:aws:glue:__REGION__:__YOUR_AWS_ACCOUNT_ID__:table/__GLUE_STAGING_DATABASE__/*" ] } ] } ``` 2. Create an IAM Role or IAM User: {% tabs %} {% tab title="IAM Role" %} With an IAM Role, Statsig assumes your IAM Role through a Statsig Service Account. The Statsig Account ID for this service account is in your Data Connection settings in the Statsig Console. Statsig runs queries on behalf of this IAM Role. Optionally, you can add an External ID condition for added security ([AWS External ID Docs](https://aws.amazon.com/blogs/security/how-to-use-external-id-when-granting-access-to-your-aws-resources/)). Statsig generates this External ID, which you can view in your Data Connection settings in the Statsig Console. * In your AWS IAM Dashboard, select the Roles page under the Access Management tab * Click 'Create role' * Choose 'AWS account' as the Trusted entity type * Choose 'Another AWS account' from the options, and copy the Statsig Account ID from your Statsig console * Optionally, require use of an External ID for connections to this role (also specified in your Statsig console) * Continue to next step of setup, and select your IAM Permissions Policy from earlier * Name, review, and create; your Trust Policy JSON should follow the format below: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "sts:AssumeRole", "Principal": { "AWS": "__STATSIG_ACCOUNT_ID__" }, "Condition": { "StringEquals": { "sts:ExternalId": "__ROLE_EXTERNAL_ID__" } } } ] } ``` * Add the ARN for this IAM Role to your Data Connection settings in the Statsig Console {% /tab %} {% tab title="IAM User" %} With an IAM User, Statsig uses AWS Access Keys to access the IAM User and runs queries on behalf of that user. * In your AWS IAM Dashboard, select the Users page under the Access Management tab * Click 'Create user' * Name your user * On the next step of setup, choose 'Attach policies directly' and select your newly created Permissions Policy * Under the Security Credentials tab of this newly created User, find the Access Keys block * Click 'Create access key', and choose 'Third-party service' from the Use Case options * Add the Access Key and Secret Access Key to your Data Connection settings in the Statsig Console {% /tab %} {% /tabs %} ## Set up reading data from your events/exposures tables 1. Give Statsig read-access to your Glue Database containing any tables you need Statsig to read from. Do this by adding the following to your AWS IAM Permissions Policy: ``` { "Effect": "Allow", "Action": [ "glue:GetTable", "glue:GetTables", "glue:GetDatabase", "glue:GetDatabases", "glue:GetPartition", "glue:GetPartitions" ], "Resource": [ "arn:aws:glue:__REGION__:__YOUR_AWS_ACCOUNT_ID__:database/__YOUR_READONLY_DATABASE__", "arn:aws:glue:__REGION__:__YOUR_AWS_ACCOUNT_ID__:table/__YOUR_READONLY_DATABASE__/*" ] } ``` 2. Give Statsig read-access to your S3 Bucket locations of the tables you need Statsig to read from. Add this to your AWS IAM Permissions Policy: ``` { "Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::__PATH_TO_YOUR_READONLY_DATA__/*" } ``` 3. In Statsig, when setting up Metric or Assignment Sources, select from these tables using `"database"."table"` format. 4. Repeat for any additional tables, or when you need to read a new table from Statsig. {% callout type="info" %} If your data warehouse is IP protected, add Statsig IP ranges to your allow list as part of your setup steps. {% /callout %} ## Additional Athena resources ### S3 bucket encryption guide Statsig supports encryption for all accessed S3 Buckets. To allow Statsig to access encrypted S3 Buckets: 1. From the AWS Key Management Service console, create a new KMS Key using the below cryptographic configuration settings: ([AWS SSE KMS Docs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html?icmpid=docs_s3_hp_batch_ops_create_job_encryption_key_type)) * Key Type: Symmetric * Key Usage: Encrypt and Decrypt * Advanced Options * Key Material Origin = KMS * Regionality = Single-Region Key 2. From the Key Policy tab of your newly created KMS Key, find the Key Administrators box. Click Add, and select the AWS IAM Role/User you provided to Statsig as an administrator. 3. Navigate to your S3 Bucket. From your S3 Bucket Properties tab, find the Default Encryption box. Click Edit, and select the below default encryption settings: * Encryption Type: SSE-KMS * AWS KMS Key: Enter AWS KMS Key ARN * (enter your newly created KMS Key ARN in the box) * Bucket Key: Enable ### Statsig staging architecture details Statsig creates all staging tables within the Glue Staging Database that you create and provide. Statsig creates all tables as table-type `ICEBERG`, except the forwarded exposures and events tables, which are regular `EXTERNAL` Athena tables. Statsig stores all staging data within the `Statsig S3 Folder` of your S3 Bucket. Statsig manages the structure within this subfolder. The folder structure is as follows: * S3 Bucket (you create and provide Statsig the name) * Statsig S3 Folder (Statsig creates and names, name provided in the Data Connection settings in your Statsig Console) * Experiment Folder(s) (Statsig creates a subfolder for each unique experiment you run, named `experiment-<ID>`) * Staging Tables Folders (Statsig puts data for each created staging table in its own subfolder, named intuitively after the data they contain) * `metadata/` * Iceberg Table Metadata * `data/` * Table Data Files (stored as Parquet) * `statsig_forwarded_exposures/` * Forwarded Exposures Table Subfolder (named in the Data Connection settings in your Statsig Console) * Dates in `YYYY-MM-DD` format (This table is partitioned by date, and there is a subfolder for each date that has exposure data sent through Statsig) * Forwarded Exposure Data * `statsig_forwarded_events/` * Forwarded Events Table Subfolder (named in the Data Connection settings in your Statsig Console) * Dates in `YYYY-MM-DD` format * Forwarded Event Data ### What IP addresses does Statsig use to access data warehouses? Go to the [FAQ](/data-warehouse-ingestion/faq#what-ip-addresses-will-statsig-access-data-warehouses-from) for the list of IP addresses. ================================================================================ # Bigquery Connection URL: https://docs.statsig.com/statsig-warehouse-native/connecting-your-warehouse/bigquery Updated: 2026-07-11 ================================================================================ ## How BigQuery connection works To set up a connection with BigQuery, there are two supported methods: * Grant Permissions to a Statsig-owned Service Account * Provide Credentials for Your Own First-Party Service Account In both cases, you need: * Your BigQuery Project ID * The dataset Statsig uses to save temporary tables and materialized results After you choose your method, start by enabling the BigQuery source in your project settings. ## Grant permissions to Statsig's service account Grant Statsig access to your BigQuery data from your Google Cloud console. 1. In your BigQuery's [IAM & Admin settings](https://console.cloud.google.com/iam-admin/), add the Statsig service account you copied in the Statsig Console as a new principal for your project and give it the following roles: * `BigQuery User` {% figure %} ![BigQuery IAM permissions configuration](/images/statsig-warehouse-native/connecting-your-warehouse/bigquery/198107543-b3bcc19a-3231-4128-be42-a5dd52fb168a.png) {% /figure %} <br /> 2. Navigate to your [BigQuery SQL Workspace](https://console.cloud.google.com/bigquery), choose the dataset, click **+ Sharing** > **Permissions** > **Add Principal**, and give the same Statsig service account the "BigQuery Data Viewer" role. Repeat for any datasets you want the service user to access. {% figure %} ![BigQuery dataset permissions interface](/images/integrations/data-imports/bigquery/175113611-90e618ad-f6e8-4005-933e-2a5660a14466.png) {% /figure %} 3. Repeat step 2 to give the "BigQuery Data Editor" role on the dataset Statsig uses for staging data. {% callout type="info" %} If your data warehouse is IP protected, you must include allowlisting of Statsig IP ranges in your setup steps. {% /callout %} The service account has the required permissions to run queries and materialize results. ## Using a first-party service account {% callout type="warning" %} This method isn't recommended. Using service accounts is the [preferred option for enhanced security](https://docs.cloud.google.com/iam/docs/migrate-from-service-account-keys). {% /callout %} 1. On the BQ service accounts page, click 'Manage Keys' for the service account you want to use {% figure %} ![BQ 1st party service account](/images/1stprtybq.png) {% /figure %} 2. Create a new JSON key (which will download a JSON file) {% figure %} ![BQ 1st party service account](/images/1stprtybq2.png) {% /figure %} {% figure %} ![BQ 1st party service account](/images/1stprtybq3.png) {% /figure %} 3. In the **Statsig Settings** -> [Data Connection](https://console.statsig.com/data_connection/connection_setup) tab, paste the above key into the **Service Account Private Key** field under the **Advanced** toggle. ## Additional BigQuery resources ### BigQuery project ID Find your BigQuery Project ID: 1. Click on your Project Dropdown inside your Cloud Console. {% figure %} ![Frame 4](/images/statsig-warehouse-native/connecting-your-warehouse/bigquery/187518062-7027f682-d1fd-445e-9947-897e44ea929e.png) {% /figure %} 2. Copy and paste relevant Project ID from the modal pop-up. {% figure %} ![Frame 5](/images/statsig-warehouse-native/connecting-your-warehouse/bigquery/187517901-9e7fd237-8325-4254-a1bd-c75f0ea08497.png) {% /figure %} {% callout type="info" %} If your data warehouse is IP protected, you must include allowlisting of Statsig IP ranges in your setup steps. {% /callout %} ### Statsig Customer ID on BigQuery C01d5f80s ### IP addresses Statsig accesses data warehouses from [Go to FAQ](/data-warehouse-ingestion/faq#what-ip-addresses-will-statsig-access-data-warehouses-from) ### Additional setup for Warehouse Explorer Warehouse Explorer lets you find and bring data from any table into Statsig for ad-hoc analysis. Statsig reads from `INFORMATION_SCHEMA.COLUMNS`, which requires explicit permission to view table metadata. Grant access by repeating steps 1 and 2 from the [Grant Permissions](/statsig-warehouse-native/connecting-your-warehouse/bigquery#grant-permissions-to-statsig%E2%80%99s-service-account) section for the "BigQuery Metadata Viewer" role. ================================================================================ # Databricks Connection URL: https://docs.statsig.com/statsig-warehouse-native/connecting-your-warehouse/databricks Updated: 2026-07-11 ================================================================================ ## How Databricks connection works To set up a connection with Databricks, you need the following: * Your Databricks Server Hostname * An HTTP Path to a Cluster or SQL Warehouse * A staging database for writing results and intermediate tables into * An access token with read access on your experiment data and write access to the staging database * Use either Serverless SQL Warehouse or an always-on cluster. Statsig uses interactive queries during setup (and some analysis). These queries fail if the cluster takes several minutes to start. Start by enabling the Databricks source in your project settings. ## Databricks access considerations For users whose primary warehouse is a DBFS-based Delta Lake, Databricks permissions are simple to manage. For customers using Databricks as an intermediary to other data sources, ensure that Databricks and the Statsig service user have appropriate access to your storage (for example, S3 for Athena tables). Permissions can be reset over time, which causes errors. To validate permissions, try creating tables through the Statsig console. ## Getting connection information 1. Follow the [Databricks documentation](https://docs.databricks.com/integrations/jdbc-odbc-bi.html#get-connection-details-for-a-cluster) to get the hostname and HTTP path of the cluster you use to run your experimental analysis. You may want to create a dedicated cluster for this purpose. {% figure %} ![credentials](/images/statsig-warehouse-native/connecting-your-warehouse/databricks/242474157-e6329ea8-92ae-43af-95dc-7bce2a26a3e6.png) {% /figure %} <br /> 2. Follow the [personal access token instructions](https://docs.databricks.com/dev-tools/auth.html#databricks-personal-access-tokens) to get the token used to calculate experiment results on your warehouse. Alternatively, follow the [service principal instructions](https://docs.databricks.com/en/administration-guide/users-groups/service-principals.html#manage-personal-access-tokens-for-a-service-principal) to get a token for a service principal. {% figure %} ![databricks info](/images/statsig-warehouse-native/connecting-your-warehouse/databricks/188731186-ecdc0872-de06-4576-b387-fa08bdca447d.png) {% /figure %} 3. Create or choose a database to use. For example, you could run this sql in a notebook: ```sql staging_database_name = '<my_name>' spark.sql(f"CREATE DATABASE IF NOT EXISTS {staging_database_name}") ``` {% callout type="info" %} If your data warehouse is IP protected, you must include allowlisting of Statsig IP ranges in your setup steps. {% /callout %} ### IP addresses Statsig accesses data warehouses from [Go to FAQ](/data-warehouse-ingestion/faq#what-ip-addresses-will-statsig-access-data-warehouses-from) ### Additional setup for Warehouse Explorer Warehouse Explorer lets you find and bring data from any table into Statsig for ad-hoc analysis. When Unity Catalog is enabled, Statsig reads from `<CATALOG_NAME>.information_schema.columns`. You can provide the catalog name in Data Connection settings, under the Advanced section. You may also need to provide read access to the catalog and the `information_schema` schema: ```sql GRANT USE CATALOG ON CATALOG <CATALOG_NAME> TO <STATSIG_USER>; GRANT USE SCHEMA ON SCHEMA <CATALOG_NAME> .information_schema TO <STATSIG_USER>; ``` `<STATSIG_USER>` is the user associated with the access token you provided to Statsig. ================================================================================ # Redshift Connection URL: https://docs.statsig.com/statsig-warehouse-native/connecting-your-warehouse/redshift Updated: 2026-07-11 ================================================================================ ## How Redshift connection works To set up a connection with Redshift, Statsig needs the following information: * Cluster Endpoint * A service user Username * A service user Password * A staging schema that Statsig can write results to {% callout type="note" %} SHA256 passwords aren't supported. Use MD5 to avoid issues. {% /callout %} Find this information in your AWS console within your specific cluster, as shown in the image below. The service user must be able to read the required experiment data and write to the Statsig staging schema you specify. {% figure %} ![Frame 1](/images/statsig-warehouse-native/connecting-your-warehouse/redshift/187515405-17fa7d90-44e1-422f-87a7-cfde090637ed.png) {% /figure %} When you save the connection, Statsig runs a series of test commands to validate permissions: for example, creating a temp table, running a select/delete statement on that table, and dropping the table. {% callout type="note" %} The provided Service Account will require the following attributes: `enable_case_sensitive_identifier`, `enable_case_sensitive_super_attribute`. If these aren't already set, Statsig sets them to TRUE upon setup completion. {% /callout %} ## SSH tunneling For Redshift connections, Statsig supports SSH tunneling into your Redshift cluster for more secure and private database access. To enable access, Statsig requires: * SSH Host * SSH Port * SSH User Statsig uses this information to generate an SSH key. Add this generated key to your `~/.ssh/authorized_keys` file on your SSH proxy machine to enable SSH tunneling. ### What IP addresses will Statsig access data warehouses from {% callout type="info" %} If your data warehouse is IP protected, you must include allowlisting of Statsig IP ranges in your setup steps. {% /callout %} [Go to FAQ](/data-warehouse-ingestion/faq#what-ip-addresses-will-statsig-access-data-warehouses-from) ### Additional setup for Warehouse Explorer Warehouse Explorer lets you find and bring data from any table into Statsig for ad-hoc analysis. To enable Warehouse Explorer, you may need to grant Statsig additional permission to query the `pg_table_def` metadata. Query results include only schemas that Statsig has read access to. A superuser or admin can grant access to additional relevant schemas and table metadata in `pg_table_def` by running: ```sql GRANT USAGE ON SCHEMA <RELEVANT_SCHEMA> TO <STATSIG_USER>; GRANT SELECT ON ALL TABLES IN SCHEMA <RELEVANT_SCHEMA> TO <STATSIG_USER>; ``` `<STATSIG_USER>` is the service user username you provided to Statsig. `<RELEVANT_SCHEMA>` is the name of the schema to include in Warehouse Explorer results. ================================================================================ # Other Warehouses URL: https://docs.statsig.com/statsig-warehouse-native/connecting-your-warehouse/other Updated: 2026-07-11 ================================================================================ ## Other warehouses Statsig's architecture supports adding new warehouses. Statsig is selective about which warehouses to support long-term, but is open to discussing your needs and building a solution for your cloud provider. Statsig accesses your warehouse through API calls and requires the address of your cluster and authentication information, similar to running queries through DataGrip, Python, or other tools. ## Permissions Statsig requires read permissions on any input tables for your analysis. Statsig also requires full CRUD (create/read/update/delete) permissions on a "sandbox" where staging data and intermediate data artifacts are stored as part of experimentation pipelines. ### What IP addresses will Statsig access data warehouses from {% callout type="info" %} If your data warehouse is IP protected, you must include allowlisting of Statsig IP ranges in your setup steps. {% /callout %} [Go to FAQ](/data-warehouse-ingestion/faq#what-ip-addresses-will-statsig-access-data-warehouses-from) ================================================================================ # Egress, Privacy, & Storage URL: https://docs.statsig.com/statsig-warehouse-native/analysis-tools/data-privacy Updated: 2026-07-11 ================================================================================ One advantage of using Statsig Warehouse Native is that user-level data comes directly from your source of truth without being copied or leaving your warehouse. This page walks through how Statsig interacts with your warehouse. ## Permissions The permissions Statsig requires are: * Job Access (where applicable) to run queries as a service user * Read Access on event, metric, and exposure data you want to use for experiment analysis * Statsig only selects from these tables * Read, Write, Delete access in a Statsig Staging environment you specify * In Bigquery this is a dataset * In Snowflake and Redshift this is a schema * In Databricks this is a database, or a database within a separate workspace with a scoped delta share to your production datasets Best practice is to create a new dataset for Statsig Staging to make sure this environment is isolated. Statsig only modifies tables it creates as part of analysis. ## What Statsig reads Statsig only reads two forms of data, both of which are very small (generally in Kilobytes) * Small samples used for validating setup in the following surfaces: * Metrics Tab * Metric Source Tab * Assignment Source Tab * (If using Statsig exposures) Users Tab * Aggregated results at the group or experiment level. This data doesn't contain user IDs and is rolled up at the group, metric, group/metric or experiment level. This includes: * Existing groups and group sizes for experiment setup * Pulse Results * Total exposure counts for power analysis * Daily total metric values * Experiment-level health checks (e.g. distinct metrics, count of exposures) ## Analysis flow During analysis, data stays in your warehouse. Intermediate tables and results are written to the Statsig data staging set. When the results of Health Checks or Pulse become available, Statsig consumes those result sets and stores them on its servers as well (usually \< 1000 rows): {% figure %} ![Analysis Flow](/images/statsig-warehouse-native/analysis-tools/data-privacy/264110212-b9e07098-cd3a-4107-aa3f-6740fc3d8b7a.png) {% /figure %} ## Data retention in Statsig Statsig retains customer data in exposure events for a maximum of 30 days for diagnostics and debugging, when using the SDK for assignment. Statsig automatically removes this data after 30 days. ## Storage management Experimentation staging datasets can generate large amounts of data. The data grows with the number of experiments you run. For example, if you have user-day data and run 100 experiments that all expose every user, you get 100 copies of that data with slight differences based on when users were exposed to each experiment. Statsig includes a table management system to help control storage costs and visibility: * Temporary artifacts (tables generated as part of explore queries or pulse results) are dropped 2-7 days after creation. This gives some buffer to debug, but they don't maintain long-term copies of data * When you finish an experiment by making a decision, Statsig prompts you to delete the tables. By default, Statsig leaves the result sets (on the order of kilobytes) in your warehouse for reference, but you can override this setting. * At any time, you can drop tables for your experiment from the three-dot menu in the experiment. ================================================================================ # Warehouse Storage URL: https://docs.statsig.com/statsig-warehouse-native/warehouse-management/storage Updated: 2026-07-11 ================================================================================ ## How warehouse storage works Statsig uses its sandbox in your warehouse to cache intermediate tables and result tables. This enables incremental reloads (without recalculating metrics for every day of the experiment on each load) and allows you to use these tables for ad-hoc analysis. Statsig stores tables in the sandbox schema or dataset you configured. You can use this to track storage footprint and manage permissions. ## Conventions and usage Statsig shards tables by entity ID. For example, the experiment `early_user_journey_acceleration` has that identifier in its associated table names for scorecard loads. This is a reliable way to look up tables for a given experiment. Statsig writes to special tables that appear in metric sources or assignment sources: * pipeline overview: performance statistics for the jobs Statsig runs * statsig\_forwarded\_events: events logged through statsig.log\_event * statsig\_forwarded\_exposures: exposures from experiments, gates, autotunes, and holdouts * statsig\_forwarded\_switchback\_exposures: switchback-formatted exposures * statsig\_daily\_results: rendered results with statistics like p-value Some of these tables have pre-set names; others you configure in data connection settings. Many users ingest these tables as part of internal pipelines. Configure lookback windows so mutable data doesn't cause issues, because Statsig regularly updates data in these tables and in some cases backfills up to several days when data delays or repairs occur. {% callout type="note" %} Exposures don't necessarily deduplicate. Fast-forwarded exposures duplicate records from daily exports, and Statsig retains only 30 days of history for warehouse native projects. After 30 days, Statsig treats a given unit's exposure as new and re-exports it. {% /callout %} ## Volume Scorecard loads generate a varying number of tables depending on the number of metric sources accessed and the types of metrics loaded. Statsig may also materialize intermediate tables before or after large operations, which reduces compute cost. This can produce a large number of artifacts. Customers running 300+ experiments have encountered default quota limits on vendors like Databricks. You can address this by requesting a quota increase or configuring the TTLs described in the Management section. ## Management Transient tables have a short TTL, usually 1-2 days, and Statsig automatically cleans them up. Other tables are permanent by default. You can clean them up from the experiment in Statsig's console or as part of launching an experiment. You can also configure TTLs per table type in the data connection section of a project's settings. Plan to manage storage using your own warehouse tools in addition to Statsig's systems, for example by cleaning up entities that haven't been accessed or modified in the last month. Ideally this isn't necessary given TTLs, but there are known cases where Statsig's internal tracking can consider a table dropped when it still has a storage footprint. Statsig can't guarantee that all tables will be removed. **How TTLs work** When Statsig creates or modifies a managed table, it schedules a cleanup at the current time plus the TTL. For example, if a Result table is written on 2024-06-01 and Result tables are configured with a 14-day TTL, a deletion is scheduled for 2024-06-15. If that table is modified on 2024-06-07 (for example, through a scorecard reload), the deletion request is reset to 2024-06-21, overwriting the existing one. This means incremental updates on long-running experiments keep their staging data until the experiment stops. Changing TTLs doesn't retroactively affect existing tables' deletion requests. The new TTL applies at the next scorecard load for the relevant experiment. **Types of tables for TTL** * `Result Datasets`: the final tables Statsig creates at the end of an experiment or gate reload, containing aggregated group-metric level data. These are generally small (1 row per metric/day/group/dimension) and useful for post-hoc analysis. * `Intermediate Tables`: all other tables Statsig writes to during an experiment reload. These can be large because they contain user-level data. Statsig reuses them for incremental and metric reloads. * `Transient Datasets`: tables created for one-off queries (most commonly Explore queries and Power Analyses), or temporary datasets used while creating `Intermediate Tables` as a performance optimization. By default, these are dropped after 2-3 days unless overridden with the setting above. **Explore query dependencies**: Explore queries rely on permanent staging tables. These tables reduce the need to recompute data for analysis already performed by the scorecard run. Unlike results tables (which are cached locally on Statsig servers), permanent staging tables must be maintained in your warehouse for Explore queries to function. This avoids reprocessing large volumes of data that may contain PII or other sensitive information. ## Troubleshooting storage issues ### Missing data errors Warehouse Native users may encounter `TABLE_OR_VIEW_NOT_FOUND` errors when required data tables are missing from the warehouse. This typically occurs when: * **Permanent staging tables have been dropped**: Explore queries and advanced analysis require permanent staging tables, not results or transient staging tables. * **TTL settings have expired tables**: Tables with configured time-to-live (TTL) settings get automatically cleaned up. * **Incomplete data loads**: Initial experiment setup or data pipeline issues may prevent table creation. #### Resolution steps **For missing staging tables:** missing permanent staging tables require a full reload to recreate the staging dataset. **For general missing tables:** 1. Check your warehouse's TTL settings in the data connection configuration. 2. Verify that permanent staging tables exist in your configured sandbox schema. 3. If tables were manually dropped, trigger a full data reload. 4. Contact support if tables are still missing after reload, or if you didn't drop them. #### Storage dependencies Warehouse Native uses several types of tables with different storage patterns: * **Permanent staging tables**: required for Explore queries and advanced analysis. * **Transient staging tables**: short-lived intermediate tables with a mix of automatic cleanup (1-2 days TTL) and permanent storage (small tables useful for ad-hoc analysis like regression coefficients). * **Results tables**: output statistics from the pipeline, copied and cached locally on Statsig servers. {% callout type="note" %} Vacuum jobs don't affect staging tables used by Statsig. {% /callout %} ================================================================================ # Warehouse Costs URL: https://docs.statsig.com/statsig-warehouse-native/guides/costs Updated: 2026-07-11 ================================================================================ This page is a high-level summary of how to think about Warehouse Costs and Total Cost of Ownership on Statsig Warehouse Native. There is some overlap with the [best practices](/statsig-warehouse-native/guides/best-practices) page; treat that page as an implementation guide and this page as a high-level overview. ## Why do costs matter? Warehouse Native platforms require you to provide compute and storage to run data jobs like experiment analyses and analytics queries. This is a cost center, and can meaningfully drive up your warehouse bill if queries are unoptimized or poorly integrated with your databases. This is a major concern for many customers considering a Warehouse Native solution for Experimentation or Analytics. Warehouse Native platforms are very flexible, but that flexibility can lead to unexpected data costs. Understanding total cost of ownership is important when evaluating a platform, as many platforms don't treat it as a primary focus. Statsig started as a cloud-only platform with a strong focus on cost efficiency. By continuously optimizing pipelines on the cloud side of its business, Statsig has developed expertise in cost-saving strategies that it passes on to Warehouse Native customers. ## Benchmarks ### Total cost of ownership Warehouse costs typically hover between 5% and 20% of customers' platform spend with Statsig. There are exceptions depending on usage, but these tend to be on the low end. To avoid cherry picking, the examples below are an anonymized list of the top 5 Statsig Warehouse Native customers on Snowflake, by total warehouse spend: | Company Profile | Experiments Run, 12 Months | Estimated % Contract Value Spent on Compute | | ------------------------------------------------------------------------- | -------------------------- | ------------------------------------------- | | Multi-sided marketplace (e.g. job search / contracts), 100M-1B in Revenue | 120 | 12% | | Multinational B2C business, 5B-20B in revenue | 210 | 9% | | B2B SaaS, 100M-1B in Revenue | 250 | 11% | | Online Entertainment, 100M-1B in Revenue | 180 | 6% | | Online Services, 50-100M in Revenue | 130 | 20% | This is fairly representative of what you can expect using Statsig at scale with reasonable adherence to best practices. Costs do scale with number of experiments; companies running fewer experiments generally see a smaller relative TCO. Relative to experiment size, there is significant variation in spend depending on how many metrics you use, what kind of metrics, and how "dense" your metrics are. The table below shows typical costs when teams follow best practices: | Experiment Size | Cost per Experiment Load | Lifetime Cost per Experiment (4 Weeks with Daily Refreshes & Drilldowns) | Lifetime Cost Using [Turbo Mode](../features/turbo) | | ------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------- | | Small (50k or less units) | \< $0.05 | $0.25 - $2.00 | $0.25 - $1.00 | | Medium | \< $0.25 | $1.00 - $10.00 | $1.00 - $5.00 | | Large | $0.25 - $10.00 | $5.00 - $280.00 | $3.00 - $50.00 | | Extremely Large (500 Million+ experimental units) | Low Sample with only a handful of companies - Can't Generalize | " | " | {% callout type="note" %} [Turbo Mode](../features/turbo) is especially efficient for large-scale experiments. It dramatically reduces memory usage, keeping large jobs from spilling to disk and reducing the need for XL clusters to provide enough memory. {% /callout %} ### Industry Statsig aims to be best-in-class for Warehouse Native experimentation costs. Customers who evaluate multiple vendors consistently report that Statsig is 50-66% of the cost of other platforms. In cases where Statsig has been more expensive than competitors, the team investigates quickly. For example, a recent customer accidentally used un-partitioned data for their Statsig proof of concept, which drove up cost relative to another vendor where the tables were partitioned. Statsig followed up by building warnings when source data isn't partitioned. After partitioning, cost went from 120% of the other vendor's cost to under 60%. Multiple customers have migrated to Statsig from other Warehouse Native Experimentation platforms due to cost concerns. Reach out to the Sales team to run an evaluation of relative costs. If you have concerns, or another party claims their solution is more efficient, contact Statsig to discuss. ## Tools for understanding cost Statsig aims to be as transparent as possible about costs. Numerous tools throughout the product help in this regard: ### History In every loaded experiment or gate, you can quickly see the cost of each load. The metrics shown are relevant to your warehouse, for example, slot hours or bytes scanned for BigQuery, uptime for Snowflake, and so on. {% figure %} <img src="/images/statsig-warehouse-native/guides/costs/d2009b64-bbf6-49bf-a3a0-df04b0b89619.png" alt="Pipeline History" width="1209" /> {% /figure %} ### Pipeline overview dashboard Statsig automatically creates a `Pipeline Overview` dashboard in its MEx platform. The underlying data is written to your warehouse, so you can investigate what is driving costs from Statsig. Many customers have built their own monitoring and alerting on top of this dataset and notify Statsig if they see any changes in cost. {% figure %} <img src="/images/statsig-warehouse-native/guides/costs/0c1a1098-d345-458b-b7a0-2cc8bfc1ccfc.png" alt="Pipeline Overview" width="1299" /> {% /figure %} ### Vendor tooling Use standalone compute and storage resources for Statsig in your warehouse. This makes total cost of ownership easy to understand. Some cost information isn't visible to the platform without administrator rights on your warehouse. If there is a mismatch between Statsig's dashboard and your costs, reach out to support. Statsig takes this seriously and works to resolve the gap. ## Tools for managing cost ### Follow UI hints Statsig proactively flags to you if: * A data source is particularly slow. * A metric source is malformed (e.g. a view definition which is broken within your warehouse). * A data source isn't following best practices. Paying attention to these and flagging them quickly prevents wasted compute. In most cases where PoC customers had performance issues, they boiled down to issues like their temp tables for the PoC not being partitioned, or not using macros to filter tables with extremely long data retention. ### Access controls For large organizations, most cost issues are caused by poorly optimized source tables, or using the wrong source when pulling data. Statsig offers access controls so that: * Your data team can control data sources and ensure they are well-behaved. * Verified sources are clearly indicated in the console for end users. ### Following best practices The [best practices](/statsig-warehouse-native/guides/best-practices) cover most situations that lead to high warehouse costs on Statsig. In short, you should aim to: * Cluster or partition source tables. * Use Statsig date macros to guarantee filter push-down. * Use Statsig's [Turbo Mode](../features/turbo) to reduce redundant compute, especially for large or long experiments. ================================================================================ # Data Best Practices URL: https://docs.statsig.com/statsig-warehouse-native/guides/best-practices Updated: 2026-07-11 ================================================================================ ## Cost management Statsig's pipelines use many SQL best practices to reduce cost, and smaller customers can run month-long analyses for a few pennies. Following these best practices helps keep costs under control and consistent. Go to [Compute Cost Transparency](/statsig-warehouse-native/guides/best-practices#compute-cost-transparency) to understand how much compute time your experiments use. ### Follow SQL best practices Statsig uses your SQL to connect to your source data. Here's some common issues: - Avoid using `SELECT *`, and only select the columns you'll need - `SELECT *` leads to a lack of clarity in what you are pulling/require for other users - For warehouse like Bigquery and Snowflake, it can increase the scan/size of materialized assets like CTEs in snowflake - This leads to higher query runtime and higher warehouse bills - Filter to the data you will need in your base query - This reduces the scan and amount of data required in future operations - You can use Statsig Macros (below) to dynamically prune date partitions in subqueries - Group common metric groups into a single Metric Source - Statsig filters your source to the minimal set of data and then creates materializations of experiment-tagged data. To maximize the effectiveness of this strategy, have metric sources that cover common suites of metrics that are usually pulled together. - Cluster/Partition tables to reduce query scope - Most warehouses offer some form of partitioning or indexing along frequently filtered keys. In warehouses with clustering, the recommended strategy is: - Partition/Cluster Assignment Source tables by date, and then your experiment_id column so experiment-level queries can be scoped to that experiment - Partition/Cluster Metric Source tables based on date, and then the fields you expect to use for filters These best practices are generally true across all warehouses, especially as the datasets you use for experimentation scale. ### Materialize tables/views Since Statsig offers a flexible and robust metric creation flow, it's common to write joins or other complex queries in a metric source. As tables and experimental velocity scale, these joins can become expensive because they run across every experiment analysis. To reduce the impact of this, best practice is to: - Materialize the results of the join and reference that in Statsig, so you only have to compute the join once - Use Statsig macros to make sure partitions are pruned before the join, and you only join the data you need ### Use incremental reloads Statsig offers both Full and Incremental reloads. Incremental reloads process only new data and can be significantly cheaper on long-running experiments. Advanced settings in the Pulse advanced settings section (also available as org-level defaults) help you make tradeoffs to reduce total compute cost. For example, "Only calculate the latest day of experiment results" skips timeseries, but can run large full reloads (for example, 1-year on 100M users) in 5 minutes on a Large Snowflake cluster. ### Reload data ad hoc Depending on your warehouse and data size, Statsig Pulse results can be available in as little as 45 seconds. Because pipelines have flat costs, reloading 5 days isn't 5 times as expensive as loading one day. If cost is a concern, choosing which results to schedule versus load on-demand can significantly reduce the amount of processing your warehouse has to do. ### Use Statsig's advanced options By default, Statsig runs a thorough analysis including historical timeseries and context on your results. ### Use metric-level reloads Statsig offers Metric-level reloads; this allows you to add a new metric to an experiment and get its entire history, or restate a single metric after its definition has changed. This is cheaper than a full reload for experiments with many metrics, and is an easy way to check guardrails or analyze follow-up questions post-hoc. ### Use Statsig's macros In Metric and Assignment sources, you can use Statsig Macros to directly inject a DATE() type that is relative to the experiment period being loaded. - `{statsig_start_date}` - `{statsig_end_date}` For example, in an incremental reload from `2023-09-01` to `2023-09-03`, this query: ```sql SELECT user_id, event, ts, dt FROM log_table WHERE dt BETWEEN {statsig_start_date} AND {statsig_end_date} ``` resolves to ```sql SELECT user_id, event, ts, dt FROM log_table WHERE dt BETWEEN DATE('2023-09-01') AND DATE('2023-09-03') ``` This is a powerful tool since you can inject filters into queries with joins or CTEs and be confident that the initial scan will be pruned. ### Avoid contention Resource contention is a common problem for Data Engineering teams. Large runs in the morning to calculate the previous day's data or reload tables are typical. On warehouses with flat resources or scaling limits, Pulse queries can be significantly slower during these windows, and can also slow down core business logic pipelines. The best practice is to assign a scoped resource to Statsig's service user. This has a few advantages: - Costs are easy to understand, because anything billed to that resource is attributable to Statsig. - You can control the maximum spend by controlling the size of the resource, and independently scale the resource as your experimentation velocity increases. - Statsig jobs don't affect your production jobs, and vice versa. If this isn't possible: - Schedule your Statsig runs after your main runs (this also ensures the data in your experiment analysis is fresh). - Use API triggers to launch Statsig analyses after the main run is finished. ## Analytics optimization When using Statsig’s Metric Explorer to visualize data in your warehouse, optimizing table layout and clustering configurations can greatly improve latency. This section describes best practices you can use to improve the performance of analytics queries, with recommendations for some of the most commonly used warehouses. ### BigQuery #### Table layout: partitioning and clustering Partition on event date and cluster on event when defining your events table. This improves performance because most queries filter for the event name and the time it was logged. When defining the partition on event date, truncate the timestamp to day-level granularity instead of using the raw timestamp (which would otherwise have millisecond precision, resulting in very high cardinality). ```sql -- Create an events table partitioned by date and clustered by event. CREATE TABLE dataset.events ( ts TIMESTAMP NOT NULL, event STRING NOT NULL, ... ) PARTITION BY DATE(ts) CLUSTER BY event; ``` BigQuery's support for applying a cluster to an existing table is limited. Adding a cluster to an existing table doesn't automatically recluster the data immediately. If you need to repartition on event date or add a cluster by event, create a new table with the correct partitions and clusters using your current table. ```sql -- Using an existing events table, create a new table that is partitioned by date and clustered by event. CREATE OR REPLACE TABLE dataset.events_new PARTITION BY DATE(ts) CLUSTER BY event AS SELECT * FROM dataset.events; -- (Optional) Swap the name of your new table with the old one for consistency. DROP TABLE dataset.events; ALTER TABLE dataset.events_new RENAME TO events; ``` ### Databricks #### (Preferred) Use liquid clustering When making clustering decisions in your events table layout, [liquid clustering](https://docs.databricks.com/aws/en/delta/clustering) provides a flexible approach that allows you to modify your clustering keys without needing to manually rewrite existing data. Use liquid clustering for your events table as follows: ```sql -- Enable liquid clustering for your events table. ALTER TABLE events SET TBLPROPERTIES ('delta.liquidClustering.enabled' = 'true'); -- Cluster on the event column. ALTER TABLE events ALTER CLUSTER BY (event); -- Trigger clustering using the OPTIMIZE command. OPTIMIZE events; ``` If your events table is frequently being updated, Databricks recommends scheduling `OPTIMIZE` jobs every 1-2 hours. This incrementally applies liquid clustering to your table. #### (Alternative) Partitioning and ZORDER If you choose not to use liquid clustering, partition on a single low-cardinality column such as the event date. Avoid adding more than one column on the partition unless necessary, and don't partition on a column with cardinality exceeding one thousand. Use a generated column to simplify pruning: ```sql -- Define a partition on the event date generated column. CREATE TABLE events (ts TIMESTAMP, event STRING, event_date DATE GENERATED ALWAYS AS (CAST(ts AS DATE))) USING DELTA PARTITIONED BY (event_date); ``` Use `ZORDER` to colocate similar values within a file for a high-cardinality column, which improves query performance through data skipping. Apply this to the event column: ```sql -- ZORDER by the event column to improve data skipping. OPTIMIZE events ZORDER BY (event); ``` Use a scheduled job that runs `OPTIMIZE` on the last week's event data to improve query performance by compacting small data files into fewer, larger files: ```sql -- Periodically optimize the events table based on the last week of data. OPTIMIZE events WHERE event_date >= current_date() - INTERVAL 7 DAYS; ``` Avoid partitioning on a combination of event date and event. Even for small queries, this creates significant overhead: with 500 events, a 30-day query hits 15,000 partitions. Instead, partition only by date as described above and use `ZORDER` on the event. For extra parallelism without introducing too many partitions, `ZORDER` by an additional bucket column, defined as follows: ```sql -- Add bucket column for extra parallelism. ALTER TABLE events ADD COLUMN event_bucket INT GENERATED ALWAYS AS (pmod(hash(event), 16)); -- Rewrite data into new files based on this ZORDER key. OPTIMIZE events ZORDER BY (event_bucket); ``` #### Partition pruning Dynamic File Pruning enables Spark to prune partitions based on filter values at runtime. Enable it if possible using: ```sql -- Turn on Dynamic File Pruning. SET spark.databricks.optimizer.dynamicFilePruning = true; ``` #### Handling skew and joins To rebalance skew and adjust join strategy, turn on Adaptive Query Execution: ```sql -- Turn on Adapative Query Execution SET spark.sql.adaptive.enabled = true; ``` #### Compute choices The Photon query engine allows faster query execution with more efficient use of CPU and memory. If possible, enable it for your compute cluster or SQL warehouse. ### Redshift #### Table design: distribution style and sort key Because Redshift doesn't support partitioning by column, use a sort key instead. Most event tables are append-only and time-based, so use a compound sort key on (timestamp, event) to order data by time. Because all analytics queries filter on timestamp, this allows queries to read only the relevant blocks. Ordering secondarily by event groups rows by event within each time block, which can reduce scan size when filtering by event. Timestamp is kept as the first key so pruning is primarily on timestamp. Because there can be significant skew among event types, use an automatic distribution style rather than distributing on the event column. This allows Redshift to make distribution decisions based on the size of the table and query patterns. Create an events table with these recommendations as follows: ```sql -- Create an events table with an automatic distrbution style and sort key on timestamp, event. CREATE TABLE events ( event VARCHAR NOT NULL, ts TIMESTAMP NOT NULL, ... ) DISTSTYLE AUTO COMPOUND SORTKEY (ts, event); ``` If you already have an events table and want to use the recommended distribution style and sort key, you can't apply those changes by modifying the existing table. Instead, create a new table and copy over data: ```sql -- Create a new events table with the preferred distribution style and sort key. CREATE TABLE events_new ( event VARCHAR NOT NULL, ts TIMESTAMP NOT NULL, ... ) DISTSTYLE AUTO COMPOUND SORTKEY (ts, event); -- Copy over data from the old events table. INSERT INTO events_new (event, ts, ...) SELECT event_id, ts, ... FROM events; -- Rename the two tables in a single transaction. BEGIN; ALTER TABLE events RENAME TO events_old; ALTER TABLE events_new RENAME TO events; COMMIT; -- Drop the old table. DROP TABLE events_old; ``` ### Snowflake #### Clustering keys Because Snowflake doesn't allow explicit partitioning, use clustering keys on the event date and event to improve performance, as most queries filter for the event name and the time it was logged. Cluster on the timestamp truncated to day-level granularity instead of the raw timestamp (which would otherwise have millisecond precision, causing very high cardinality). ```sql -- Cluster events on the timestamp truncated to day and event. ALTER TABLE events CLUSTER BY (DATE_TRUNC('day', ts), event); ``` If you have a high cardinality of unique event types, it might be advisable to add a search optimization on event instead of clustering by that column. Go to the [next section](#managing-high-cardinality-columns) for details. #### Managing high cardinality columns If there are high-cardinality columns that you frequently reference in Metrics Explorer filters, consider using search optimization rather than clustering on those columns. Clustering on high-cardinality columns creates an overly large range of values for each micro-partition, making pruning inefficient. Search optimization generates an auxiliary index for fast lookups instead. ```sql -- Add search optimization for fast lookups. ALTER TABLE events ADD SEARCH OPTIMIZATION ON EQUALITY(some_column); ``` #### Monitoring clustering Use `SYSTEM$CLUSTERING_INFORMATION` to check whether your current clustering scheme is effective. Large values of `average_depth` and `average_overlaps` may indicate the existing table needs reclustering on different keys with lower cardinality. ```sql -- Given an events table clustered on event date and event, check the clustering information. SELECT SYSTEM$CLUSTERING_INFORMATION('YOUR_DATABASE.YOUR_SCHEMA.EVENTS', '(DATE_TRUNC(''day'', ts), event)'); ``` #### Timestamp column If possible, use the `TIMESTAMP_NTZ` (no timezone) type for your timestamp column. This saves space because Snowflake doesn't need to store offset metadata. It can also speed up queries by allowing Snowflake to skip timezone normalization during filtering and pruning of micro-partitions. ### Questions For additional support optimizing your warehouse configuration for analytics, reach out in the Slack support channel for your organization within Statsig Connect. ## Debugging Statsig shows you all the SQL being run and any errors that occur. These are generally caused by changes to underlying tables or Metric Sources that cause a metric query to fail. Here are some best practices for debugging Statsig queries. ### Use the queries from your Statsig console If a Pulse load fails, find all the SQL queries and associated error messages in the *Diagnostics* tab. Select the copy button to run or debug the query in your warehouse. Common errors include: - A query attempting to access a field that no longer exists on a table. - A table not existing, usually because the Statsig user doesn't have permission on a new table. ## Turbo mode [Turbo Mode](../features/turbo) skips some enrichment calculations (in particular some time series rollups) to compute the latest snapshot of your data at low cost. Customers have run experiments on 150+ million users in less than 5 minutes on a Snowflake S cluster. ### Contact support Statsig's support team is responsive and can help you fix your issue and build tools to prevent it in the future, whether it's due to system or user error. ## Compute cost transparency Statsig Warehouse Native lets you get an overview of the compute time experiment analysis incurs in your warehouse. Break this down by experiment, metric source, or type of query to find what to optimize. Common questions the dashboard is designed to answer include: - Which Metric Sources take the most compute time? - What is the split of compute time between full loads, incremental loads, and custom queries? - How is compute time distributed across experiments? You can find this dashboard in the Left Nav under Analytics -> Dashboards -> Pipeline Overview ![Pipeline Overview dashboard interface](/images/statsig-warehouse-native/guides/best-practices/684ae633-8054-4f41-8443-7df63fe81253.png) This dashboard is built using Statsig Product Analytics. You can customize any of these charts or build new ones. A useful addition is to include your average compute cost, so you can convert slot time per experiment into dollar cost per experiment. At the end of every Pulse load / DAG, Statsig uploads a single row to the `pipeline_overview` table for each job executed as part of that run. This table has the following schema: | Column | Type | Description | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ | | ts | timestamp | Timestamp at which the DAG was created. | | job_type | string | Job type (see [Pipeline Overview](/statsig-warehouse-native/analysis-tools/pipeline-overview/)) | | metric_source_id | string | Only applicable for 'Unit-Day Calculations' jobs - the ID of the metric source | | assignment_source_id | string | The Assignment Source ID of the experiment for which Pulse was loaded. | | job_status | string | The final state of the job (`fail` or `success`) | | metrics | string | Metrics processed by the job | | dag_state | string | Final state of the DAG (`success`, `partial_failure`, or `failure`) | | dag_type | string | Type of DAG (`full`, `incremental`, `metric`, `power`, `custom_query`, `autotune`, `assignment_source`, `stratified_sampling`) | | experiment_id | string | ID of the experiment for which Pulse was loaded, if applicable | | dag_start_ds | string | Start of the date range being loaded | | dag_end_ds | string | End of the date range being loaded | | wall_time | number | Total time elapsed between DAG start and finish, in milliseconds | | turbo_mode | boolean | Whether the DAG was run in Turbo Mode | | dag_id | string | Internal identifier for the DAG | | dag_duration | number | Number of days in the date range being loaded | | is_scheduled | boolean | Whether the DAG was triggered by a scheduled run | ================================================================================ # Data & Semantic Layer URL: https://docs.statsig.com/statsig-warehouse-native/configuration/data-and-semantic-layer Updated: 2026-07-11 ================================================================================ When using Statsig Warehouse Native, you configure metrics, experiments, and other schematized data objects that allow Statsig to perform analysis on top of your warehouse. This functions as a lightweight Semantic Layer, which can integrate with popular Semantic Layer tools like Cube or in-house YAML-based stores. This section covers the building blocks of Statsig's configuration layer. {% figure %} ![Warehouse Native data flow architecture diagram](/images/whn/data_flow_whn.png) {% /figure %} ================================================================================ # Metric Sources URL: https://docs.statsig.com/statsig-warehouse-native/configuration/metric-sources Updated: 2026-07-11 ================================================================================ Metric Sources are how you schematize your warehouse data for Statsig, and they serve as the input data for metrics. ## What is a metric source A metric source is a key part of Statsig's semantic layer (and integrations to other Semantic Layers). A metric source consists of a data source, which is either: * A SQL Query that Statsig reads as a view * A warehouse table And configuration around the source: * **\[Required]** identifier and timestamp columns * **\[Optional]** Aliases, partition information * **\[Early Access]** Data quality checks and configuration This is the entry point for your data to be used in parameterized queries for experiment analysis, analytics, and more within the Statsig console. ## Data sources ### Getting data Statsig Metric Sources can use a query or a table as the source of their data. A query-based source reads a SQL query and uses it as a source of truth. Combined with Statsig's built-in [query macros](/statsig-warehouse-native/configuration/query-tools), this provides an efficient and flexible way to create experimental data sources. {% figure %} ![Query-based metric source configuration](/images/statsig-warehouse-native/configuration/metric-sources/2cec3d01-f852-4007-aa3b-de276418593b.png) {% /figure %} For larger or managed datasets, use table sources. Table sources minimize data scan and provide a more 1:1 mapping of "data source" to "metric source". Statsig date macros are automatically applied in each experiment result reload when you use table sources. {% figure %} ![Table-based metric source configuration](/images/statsig-warehouse-native/configuration/metric-sources/04cb0209-580c-4603-a5db-45a08f00f90b.png) {% /figure %} ### Configuring data For any source, select which field to use for the timestamp of the logs or metrics being provided, as well as 1 to N user identifiers to join to experiment data. {% figure %} ![Interface for selecting timestamp and ID fields](/images/statsig-warehouse-native/configuration/metric-sources/9a972d93-3500-4947-89e7-c144b877887f.png) {% /figure %} For table sources, you can optionally provide a partitioning column to reduce data scan, and provide aliases to make your column names more human-readable. {% figure %} ![Partition and alias settings for table source](/images/statsig-warehouse-native/configuration/metric-sources/4b388fac-1124-4606-88f6-a0615bd32b18.png) {% /figure %} ### Types of data Statsig works natively with many different types and granularities of data. Common patterns are: #### 1. Raw event logging (event level data), using the log timestamp as the timestamp, example: | `event_time` | `user_id` | `event_name` | `platform` | `value` | |-----------------------|-----------|------------------|------------|---------| | 2024-03-01 10:05:12 | `u_123` | `page_view` | `web` | `null` | | 2024-03-01 10:05:45 | `u_123` | `button_click` | `web` | `null` | | 2024-03-01 10:07:02 | `u_456` | `purchase` | `ios` | 29.99 | *Metric examples with this source:* *- Number of users with purchase: You can create a [UNIT COUNT](/statsig-warehouse-native/metrics/unit-count-once) metric, with a filter of event\_name = 'purchase'.* *- % of users with page view who clicked: You can create a [RATIO](/statsig-warehouse-native/metrics/ratio) metric with this metric source in both denominator and numerator, and apply event\_name filter accordingly.* #### 2. Fact tables (one row per entity per day), using the date of the row as the timestamp, example: | `order_date` | `order_id` | `user_id` | `order_status` | `items_count` | `revenue` | |--------------|------------|-----------|----------------|---------------|-----------| | 2024-03-01 | `o_10001` | `u_123` | `completed` | 2 | 49.98 | | 2024-03-01 | `o_10002` | `u_456` | `completed` | 1 | 19.99 | | 2024-03-02 | `o_10003` | `u_123` | `refunded` | 1 | 19.99 | *Metric examples with this source:* *- Revenue: You can create a [SUM](/statsig-warehouse-native/metrics/sum) metric on 'revenue' column, with any filters you need.* *- Average order value: You can create a [MEAN](/statsig-warehouse-native/metrics/mean) metric on 'revenue' column, with any filters you need.* #### 3. Aggregated fact tables at unit day granularity, using the date of the row as the timestamp, example: | `date` | `user_id` | `sessions` | `purchases` | `revenue` | |--------------|-----------|------------|-------------|-----------| | 2024-03-01 | `u_123` | 3 | 1 | 29.99 | | 2024-03-01 | `u_456` | 1 | 0 | 0.00 | | 2024-03-02 | `u_123` | 2 | 0 | 0.00 | *Metric examples with this source:* *- Revenue: You can create a [SUM](/statsig-warehouse-native/metrics/sum) metric on 'revenue' column, with any filters you need.* *- Number of users with purchase: You can create a [UNIT COUNT](/statsig-warehouse-native/metrics/unit-count-once) metric, with a filter of purchase > 0.* ### Types of data that require transformation You may have a table that doesn't fit the schemas described above. For example, a wide user dimension table contains one row per user with pre-aggregated or derived behavioral attributes. | `user_id` | `signup_date` | `first_active_date` | `first_page_view_date` | `first_purchase_date` | `last_active_date` | `lifetime_revenue` | `is_power_user` | |-----------|---------------|---------------------|------------------------|-----------------------|--------------------|--------------------|-----------------| | `u_123` | 2023-11-12 | 2023-11-12 | 2023-11-12 | 2023-11-20 | 2024-03-02 | 249.85 | `true` | | `u_456` | 2024-01-05 | 2024-01-06 | 2024-01-05 | 2024-02-10 | 2024-02-18 | 19.99 | `false` | | `u_789` | 2024-02-10 | 2024-02-10 | 2024-02-10 | `null` | 2024-03-01 | 89.97 | `false` | This table type isn’t compatible with Statsig because it doesn’t have a single timestamp column that you can consistently configure across all events (for example, `signup`, `first_active`, and `last_active`). Statsig’s stats engine relies on a timestamp column to join metric data with exposure timestamps, ensuring that Statsig includes only metric events that occur after a user is exposed to an experiment. For example, suppose you configure `signup_date` as the timestamp column for this metric source. If you then attempt to build a funnel metric across multiple events (for example, `signup` → `first_active` → `first_purchase`), Statsig treats all funnel steps as occurring at the signup date, because the metric source can only use a single configured timestamp column. As a result, Statsig ignores event-specific timestamps in other columns (such as `first_active_date` or `first_purchase_date`), and the funnel no longer reflects the true timing of each event. To address this, there are two recommended options: * **Use upstream tables as the metric source**: Define metrics directly from event logs or fact tables where each row represents a single event and includes a clear timestamp. * **Collapse the table into a long format**: Reshape the wide user table into a long table (for example, one row per user per event or per day) with a unified timestamp column that you can configure in Statsig. | `user_id` | `event_type` | `event_timestamp` | |-----------|------------------|-----------------------| | `u_123` | `signup` | 2023-11-12 09:15:00 | | `u_123` | `first_active` | 2023-11-13 10:02:41 | | `u_123` | `last_active` | 2024-03-02 18:45:10 | | `u_456` | `signup` | 2024-01-05 14:22:09 | | `u_456` | `first_active` | 2024-01-06 08:11:54 | ## Managing metric sources In the metric source tab, you can see your metric sources and the metrics and experiments that use them. In some cases, it makes sense to have a broad metric source that you reuse across many metrics with different filters and aggregations. In others, a metric source may exist for one metric, such as a set of specific events for a funnel. {% figure %} ![Metric sources list showing usage across metrics](/images/statsig-warehouse-native/configuration/metric-sources/264087800-18970974-b639-4d73-8977-e54de752ae0a.png) {% /figure %} ### Programmatic updates You can create and modify metric sources through the API as part of your release flow for data systems. This supports the creation of read-only artifacts. Refer to the [console API](/statsig-warehouse-native/configuration/console-api) and [Semantic Layer Sync](/statsig-warehouse-native/configuration/semantic-layer-sync) documentation. ### Governance If you're concerned about granting Statsig broad warehouse access, give Statsig access only to its own staging schema or dataset, and create views or materialize staging tables in that location for the data you want Statsig to read. ## Daily vs. realtime sources When specifying a timestamp, you can also specify if the metric source contains data at a daily or timestamp granularity by toggling the "Treat Timestamp as Date" setting. {% figure %} ![Timestamp granularity configuration interface](/images/statsig-warehouse-native/configuration/metric-sources/f0edfdaf-9531-4583-b440-d05f0f3c3618.png) {% /figure %} When this setting is **not** enabled, the system performs a timestamp-based join. Statsig attributes events to the experiment results based on the exact time they occur relative to the exposure time. For example, if a user is exposed at `2024-01-01T11:00:00` and an event occurs at `2024-01-01T11:01:00`, Statsig attributes the event to the experiment because it occurred after the exposure. If the event occurs at `2024-01-01T10:59:00`, before the exposure, Statsig doesn't attribute it. When the "Treat Timestamp as Date" setting is enabled, the system performs a date-based join. All events occurring on the same calendar day as the exposure, regardless of time, are included in the experiment results. This includes data from the first day of exposures, ensuring Statsig doesn't omit day-1 metrics from the analysis. All Statsig needs to create metrics is a timestamp or date, and a unit (or user) identifier. Context fields let you pull multiple metrics from the same base query, and select values to sum, mean, or group by. | Column Type | Description | Format/Rules | | ---------------------- | ------------------------------------------------------------------- | ------------------------------ | | timestamp | **Required** an identifier of when the metric data occurred | Castable to Timestamp/Date | | unit identifier | **Required** At least one entity to which this metric belongs | Generally a user ID or similar | | additional identifiers | *Optional* Entity identifiers for reuse across identifier types | | | context columns | *Optional* Fields which will be aggregated, filtered, or grouped on | | For example, you could pull from event logging and aggregate the event-level data to create metrics: | timestamp | user\_id | company\_id | event | time\_to\_load | page\_route | | ------------------- | ------------- | ---------- | ---------- | ------------ | ---------- | | 2023-10-10 00:01:01 | my\_user\_17503 | c\_22235455 | page\_load | 207.22 | / | | 2023-10-10 00:02:15 | my\_user\_18821 | c\_22235455 | page\_load | 522.38 | /search | | 2023-10-10 00:02:22 | my\_user\_18821 | c\_22235455 | serp\_click | null | /search | You could create an average TTL metric by averaging time\_to\_load, and group it by page route or filter to specific routes when creating your metric. As another example, you might pre-calculate some metrics at a user-day grain, either to match your source of truth exactly or to add more complex logical fields: | timestamp | user\_id | company\_id | country | page\_loads | satisfaction\_score | revenue\_usd | net\_revenue\_usd | | ---------- | ------------- | ---------- | ------- | ---------- | ------------------ | ----------- | --------------- | | 2023-10-10 | my\_user\_17503 | c\_22235455 | US | 13 | 9 | 130.21 | 112.33 | | 2023-10-10 | my\_user\_18821 | c\_22235455 | CA | 1 | 2 | 0 | 0 | | 2023-10-10 | my\_user\_18828 | c\_190887 | DE | 0 | null | 22.1 | 0 | You can create different metrics by summing and filtering on those daily fields. ## (Very) slow metric sources Statsig uses techniques like Statsig macros, push-down filters (predicate filters), and partition keys to make queries in your warehouse efficient. While metric sources can include joins or complex queries, they should be performant. If they aren't, any metrics based on that metric source become expensive or cause timeouts and failures. The same applies to assignment sources. Statsig flags a metric source as slow if it takes more than 30 seconds to retrieve a sample of up to 100 records from the table. If the query is expensive, consider the following steps in sequence to optimize your metric source: * Include filters based on the partition column. * Use [Statsig macros](/statsig-warehouse-native/guides/best-practices#use-statsigs-macros) in SQL. * Pre-calculate some of the metrics to avoid joins or complex queries. * Upgrade your computing resources if you are on a very small cluster. Do this cautiously. {% callout type="note" %} If Statsig flagged you for a slow assignment source, the same guidance applies. {% /callout %} ================================================================================ # Metrics Overview URL: https://docs.statsig.com/statsig-warehouse-native/configuration/metrics Updated: 2026-07-11 ================================================================================ Metrics are measures of user or system behavior that serve as evaluation criteria for experiments and for performing analysis. Metrics are organized within your Metrics Catalog. {% figure %} ![Metrics catalog dashboard](/images/statsig-warehouse-native/configuration/metrics/8cc2781c-23ac-46d2-b270-0d5d359c8f5b.png) {% /figure %} ## Creating metrics Metrics are a combination of a metric source, an aggregation, and optional filters and advanced settings. The metric source provides the raw data, and the aggregation defines how Statsig aggregates data across different granularities: user-level, group-level, or daily timeseries. Metrics can support multiple units of analysis. For example, you can use a revenue metric for a "User Level" and a "Store Level" experiment, as long as the metric source has a mapping for both ID types. Filters are also a core component of metrics. Statsig offers a rich set of filtering options, including SQL-based filters, so you can reuse the same metric source for many use cases. {% figure %} ![Metric creation flow with aggregation and filters](/images/statsig-warehouse-native/configuration/metrics/fb7526f9-3ffa-4192-8184-1388e3e80db9.png) {% /figure %} ## Using metrics You can use metrics for standalone analysis, as part of your experiment scorecard, or as guardrails for feature releases. You can also group them into collections using tags for easy addition across these surfaces. You can also configure metrics to send alerts globally if any experiment or gate causes a regression. Statsig recommends using tags extensively. A combination of team-level and surface- or product-level tags ensures easy discovery of metrics. To view details about a metric, navigate to the Metrics page to see its definition, related experiments, and a timeseries of the metric value. {% figure %} ![Metric detail page showing definition and timeseries](/images/statsig-warehouse-native/configuration/metrics/f7023ab8-9d3d-4013-8595-b51325b7c954.png) {% /figure %} In the [insights tab](/experiments/exploring-results/aggregated-impact) and in [meta-analysis](/statsig-warehouse-native/features/meta-analysis), you can perform more detailed analysis of how experiments have impacted a metric, and how the metric relates to other metrics in your catalog. {% figure %} ![Insights view summarizing aggregated impact](/images/statsig-warehouse-native/configuration/metrics/94d40316-53bf-484e-8a73-57cc42e22ca6.png) {% /figure %} ## Loading metrics Selecting **Reload Metric Data** causes Statsig to sync your latest data and display it on this page. The default loading window is 90 days, but you can configure a longer period if needed. {% figure %} ![Reload metric data button and schedule settings](/images/statsig-warehouse-native/configuration/metrics/cc73bf53-13ec-44f3-aa2c-606db4a8d4da.png) {% /figure %} To use metrics to measure topline impact, or for ongoing tracking in the metrics page, schedule loads of the metric values. You can trigger a load manually from the metric page, or schedule it from there (3-dot menu > edit scheduled reload). You can also configure this as a project-level setting under Data Connection in your project settings. ## Metric management Without a well-managed Metric Catalog, end users may not know whether they can trust a metric or understand how it's defined. Statsig provides several tools to address this: * \[Verified Metrics] and programmatic management allow you to vet your core metrics and make it clear which metrics set the gold standard * \[[RBAC](/statsig-warehouse-native/features/roles-and-access) and Team Ownership] let you limit the number of potential editors for Metrics, keeping control of core definitions in the experiment team's hands * \[Local Metrics] in Pulse or Explore queries allow you to make experiment-scoped and clearly-labeled changes to metric definitions without adding a large amount of single-user metrics to your metrics catalog * Scorecard hover-over definitions make it trivial to discover, in-context, what settings were applied to a metric and how the calculation was performed. ## Metric types Statsig offers the largest coverage of metric types of any enterprise platform. These fall into 4 categories: ### Aggregations Aggregations are basic unit-level counts or sums that measure user behavior. You can use them as inputs to ratios, especially for [cluster-based experiments](/metrics/different-id) where you want to normalize measures. Statsig computes aggregations at the unit level, then averages them across all units in the experiment during Pulse analysis. Supported aggregations are: * [Count](/statsig-warehouse-native/metrics/count) * [Sum](/statsig-warehouse-native/metrics/sum) * [Logged Sums and Counts](/statsig-warehouse-native/metrics/log) * [Count Distinct](/statsig-warehouse-native/metrics/count-distinct) * [First Value](/statsig-warehouse-native/metrics/latest-value#first-value) * [Latest Value](/statsig-warehouse-native/metrics/latest-value) * Thresholds * A special case of sum/count that measures a 1/0 flag for whether a user passed a threshold value during the experiment - e.g., "the number of users who spent more than $100" ### Unit counts, retention, and conversion Statsig offers many ways to count units in an experiment. This allows you to measure questions like: * Did users sign up more often? * Are more users currently subscribers at the end of the experiment? * How many users refunded in the first week of the experiment? These all fall under the "Unit Count" type, with rollups specifying calculation details. Unit counts produce a 1/0 flag or a sum of user-days at the user level, then Statsig averages them across all units in the experiment during Pulse analysis. Supported unit count types are: * [One-Time Event](/statsig-warehouse-native/metrics/unit-count-once), measuring if a user performed an action at all during the experiment * [Windowed](/statsig-warehouse-native/metrics/unit-count-window), measuring if a user performed an action within some time window after exposure * [Latest Participation](/statsig-warehouse-native/metrics/unit-count-latest), measuring if a user fulfilled some criteria on their latest recorded record (e.g. is this user currently a subscriber) * [Daily Participation](/statsig-warehouse-native/metrics/unit-count-rate), the rate at which units were daily active users during the experiment * [Retention](/statsig-warehouse-native/metrics/retention), the rolling rate of retention on an action or set of actions with a configurable time window ### Ratios and funnels Ratios and funnels measure rates or conversion. Statsig aggregates these differently: they measure the total of a numerator metric divided by the total of a denominator metric across each experiment group. They also apply the delta method to correct for covariance between the component metrics. [Ratio Metrics](/statsig-warehouse-native/metrics/ratio) allow you to measure ratios of two different metrics, giving nuance to results and helping you to normalize results by another measure - for example: * B2C: Average purchase revenue (`SUM(Revenue) / COUNT(orders)`) * B2B: Revenue per User (`SUM(Revenue) / COUNT_DISTINCT(user_id)`) [Funnel Metrics](/statsig-warehouse-native/metrics/funnel) allow you to analyze user conversion through multi-step flows at an overall and stepwise level. Funnel metrics help you identify dropoff points in user journeys and measure conversion rates. Statsig offers session-level funnels, which measure conversion within distinct checkout flows, support conversations, or viewing sessions. ### Performance metrics Teams working on performance problems use Statsig to analyze the impact of their changes at a system level. These metric types can also be useful for user behavior. The types most commonly used here are: * [Percentile Metrics](/statsig-warehouse-native/metrics/percentile) allow you to measure changes in values like the P99.9 - useful for measuring improvements or regressions in latency, TTL, or measuring median change when the mean is skewed * [Mean Metrics](/statsig-warehouse-native/metrics/mean) are an easy shorthand for ratio metrics summing an event-level value and dividing by the total records ## Filters Statsig offers many ways to filter your data. You can write filters in SQL, but leaving metric sources broad and allowing users to construct filters in the UI or through semantic layer syncs gives greater no-code flexibility for metric creation and follow-up analysis, especially when using [local metrics](/metrics/local-metrics). ### Any of / None of These are the equivalent to a SQL `IN` or `NOT IN` statement, and you can also use them for equality/inequality. You can supply 1 to N values to the filter to include or exclude from your result set. ### Inequalities You can compare numerical values using `=`, `>`, `<`, `>=`, or `<=`. Equality can also compare non-numerical values, which Statsig evaluates as a string-casted comparison. ### Null checks Statsig supports checks for `Is Null` or `Non Null`, which can be useful for identifying if a flag was logged or for filtering out partial data. ### Contains/Does Not Contain/Starts With/Ends With These operators are similar to a SQL `LIKE` operator: * Contains checks for if a substring is in a string, e.g. \`field LIKE '%search\_string%' * Does Not Contain checks for if a substring is in a string, e.g. \`field not LIKE '%search\_string%' * Starts With checks for if a substring starts the field, e.g. \`field LIKE 'search\_string%' * Ends With checks for if a substring ends the field, e.g. \`field LIKE '%search\_string' ### Is After Exposure This filter allows you to specify a **secondary** date/timestamp field that has to come after the user's enrollment to the experiment. By default, Statsig only considers metric data where the primary metric timestamp is after the user first saw the experimental intervention. However, you might have another field like "first\_saw\_content\_at". You can use `Is After Exposure` to enforce that this secondary timestamp also takes place after the user's exposure. ### SQL filters SQL filters allow you to inject any SQL filter string into your metric definition, which Statsig validates before saving. This is flexible and lets you interact with complex objects or do complex logic operations as needed to define your metrics. For example, the sql filter `(weight_lb)/pow(height_inches, 2) > 25` would be added to your metric source query for this metric as: ```sql SELECT <columns> FROM <source_table> WHERE <other_filters> AND ((weight_lb)/pow(height_inches, 2) > 25) ``` ## Settings Each metric type's page has specific information on settings relevant to that metric. This section introduces common settings across metrics. ### Breakdowns Most aggregation-type metrics (sum, count, count distinct, unit count, means, ratios, percentiles, first/latest, min, max) allow you to generate a breakdown view of any column automatically. You can specify this column in the metric set up, and then click into the experiment result to see the breakdown. ### Cohorts Cohort settings allow you to specify a window for data collection after a unit's exposure. For example, a 4-6 day cohort window would only count actions from days 4, 5, and 6 after a unit was exposed to an experiment. Refer to the [cohort documentation](/statsig-warehouse-native/features/cohort-metrics) for full details. ### Baking Many metric types support baking. Statsig waits to calculate baked metrics and uses "old" data for them. This is appropriate for cases like credit card chargebacks, where you may adjust your payments dataset to account for chargebacks in a "net revenue" metric. For additional information, go to the [cohort documentation](/statsig-warehouse-native/features/cohort-metrics). Statsig: * doesn't calculate baked metrics until the bake period has elapsed since the user's enrollment * pulls historical data that "just baked" as of that day on a given load * only calculates results for users whose bake window has elapsed to avoid diluting metrics This way, Statsig doesn't show incomplete data in Pulse (either because it is incomplete in the warehouse, or because of late-landing data). ### Thresholding Thresholding converts a sum, count, count-distinct, max/min, or first/latest metric into a 1/0 unit metric based on whether a user's total value meets a specified threshold. For example, to measure how many users spent at least $100 in their first week, specify a 0-6 day cohort metric that sums revenue with a threshold of $100. ### CUPED You can enable/disable CUPED per metric, and also specify the lookback window for which Statsig pulls users' pre-exposure data and uses it as an input to the CUPED regression. ### Winsorization Go to [winsorization](/experiments/statistical-methods/methodologies/winsorization) for full details. Winsorization applies to aggregate metrics and allows you to specify an upper or lower percentile-based threshold, clamping data to that threshold. This reduces inflated variance or skewed means caused by outlier data or buggy logging. ### Capping Capping is an alternate outlier-control method that clamps values to a unit-day cap, specifiable per unit type. For example, you might clamp "total purchase count" to 100 on an auction platform to prevent resellers from skewing your metrics, assuming 100 is a reasonable upper bound for typical users. ================================================================================ # Sum Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/sum Updated: 2026-07-11 ================================================================================ ## Use cases Sums are a common metric type, suited for comparing behavior across groups. Common examples are: * Totalling revenue by summing a `revenue` column from purchase logs * Totalling time spent by summing an aggregated `time_spent` metric on a user-day fact table ## Calculation At the unit level, sum metrics take the SUM of their input column. At the group level, Statsig calculates the mean as the SUM of the unit-level sums, divided by the count of UNIQUE UNITS exposed to the experiment. The SQL for this calculation: ```sql expandable -- Unit Level SELECT unit_id, group_id, SUM(value_column) as value FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp GROUP BY source_data.unit_id, exposure_data.group_id; -- Experiment SELECT group_id, COUNT(distinct unit_id) total_units FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value)/SUM(total_units) as mean FROM unit_data JOIN group_data USING (group_id) GROUP BY group_id; ``` ### Methodology notes Sum metrics use the SQL SUM aggregation. See the Options section below for many advanced options. ## Options * Metric Breakdowns: configure Metadata Columns to group results by for dimensional views in pulse results. * Multi Source: build sums from multiple metric sources, equivalent to a UNION in SQL. Useful when the same measure exists in sharded or disparate tables. * Winsorization: specify a lower and/or upper percentile bound. All values below the lower threshold or above the upper threshold are clamped to reduce the impact of outliers. * Capping: specify an exact cap value (per unit type) to limit a unit's daily value. For example, cap daily purchases at 100 on an e-commerce site to exclude reseller behavior. * CUPED: enable CUPED and specify the lookback window for pre-experiment data inputs. * Thresholding: convert this metric into a 1/0 unit count metric that flags whether the unit's total sum meets or exceeds a given threshold. * [Cohort Windows](/statsig-warehouse-native/features/cohort-metrics): specify a window for data collection after a unit's exposure. For example, a 0-1 day cohort window counts only actions from days 0 and 1 after exposure. Select **Only include units with a completed window** to exclude units from pulse analysis until their cohort window has completed. * [Baked Metrics](/statsig-warehouse-native/features/cohort-metrics#metric-bake-windows): specify how long a metric needs to mature. This is common for chargebacks or cancellations. Statsig delays loading the data until the window has elapsed, and calculates pulse results only after a unit's metric has matured. ================================================================================ # Count Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/count Updated: 2026-07-11 ================================================================================ ### Use cases Count metrics are commonly used to measure how often events occurred. Common examples are: * Counting click frequency by counting event logs filtered to the `click` event * Making a threshold metric of if a user read more than 2 articles ## Calculation At the unit level, count metrics run a COUNT(1) across their metric source. At the group level, Statsig calculates the mean as the SUM of the unit-level count, divided by the count of UNIQUE UNITS exposed to the experiment. The SQL looks like the following: ```sql expandable -- Unit Level SELECT source_data.unit_id, exposure_data.group_id, COUNT(1) as value FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp GROUP BY source_data.unit_id, exposure_data.group_id; -- Experiment SELECT group_id, COUNT(distinct unit_id) total_units FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value)/SUM(total_units) as mean FROM unit_data JOIN group_data USING (group_id) GROUP BY group_id; ``` ### Methodology notes Count metrics use the SQL COUNT aggregation. Many advanced options are available. ## Options * Metric Breakdowns * You can configure Metadata Columns to group results by, getting easy access to dimensional views in pulse results * Multi Source * Counts can be built with inputs from multiple metric sources; think of this as a UNION in SQL. This can be useful if you have the same measure in sharded or disparate tables * Winsorization * Specify a lower and/or upper percentile bound to winsorize at. Statsig clamps all values below the lower threshold, or above the upper threshold, to that threshold to reduce the outsized impact of outliers on your analysis. * Capping * Specify an exact cap value (per unit type) to limit a unit's daily value for the count. For example, you might want daily purchases to be capped at a high value like 100 on an e-commerce site to ignore reseller behavior * CUPED * Specify if you want to calculate CUPED, and the lookback window for CUPED's pre-experiment data inputs * Thresholding * Turn this metric into a 1/0 unit count metric counting if the unit's total count equals to or surpasses (>=) a given threshold * [Cohort Windows](/statsig-warehouse-native/features/cohort-metrics) * You can specify a window for data collection after a unit's exposure. For example, a 0-1 day cohort window would only count actions from days 0 and 1 after a unit was exposed to an experiment * Select **Only include units with a completed window** to remove units from pulse analysis for this metric until the cohort window has completed. * [Baked Metrics](/statsig-warehouse-native/features/cohort-metrics#metric-bake-windows) * [Baked Metrics](/statsig-warehouse-native/features/cohort-metrics#metric-bake-windows) let you specify how long a metric needs to mature. This is common in situations like chargebacks or cancellations. Statsig delays loading the data until the window has elapsed, and only calculates pulse results for that metric after a unit's metric has matured. ================================================================================ # Count Distinct Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/count-distinct Updated: 2026-07-11 ================================================================================ {% callout type="warning" %} Count-distinct metrics are more expensive to compute than [count](./count) or [unique unit count](./unit-count-once) metrics, especially for very long experiments If you want to count distinct occurrences of the experiment's unit of assignment (e.g. the user\_id in a user\_id experiment), you should use a unit\_count metrics instead. This achieves the same result, but more efficiently calculates and stores the metric data. In many cases a count metric serves as a close proxy to count-distinct; you can also set up a data source to track unique instances of a key to avoid re-running the distinct operation across experiment analyses. {% /callout %} ### Use cases Count distinct metrics have two primary use cases: * Measuring interactions and surface area. For example, you might count the number of entities a user has engaged with on a video streaming platform, or measure if a new recommendation engine increases the diversity of products clicked. * As a denominator in ratio metrics, especially common when you want to normalize by a unit other than your experiment's unit of analysis. For example, a B2B experiment might run at the company level but measure "Clicks per USER" using a ratio metric of COUNT(clicks)/COUNT\_DISTINCT(user\_id). ## Calculation At the unit level, count distinct metrics use COUNT\_DISTINCT on their input column. At the group level, Statsig calculates the mean as the SUM of the unit-level COUNT\_DISTINCTs, divided by the count of UNIQUE UNITS exposed to the experiment. The SQL looks like the following: ```sql expandable -- Unit Level SELECT source_data.unit_id, exposure_data.group_id, COUNT(distinct source_data.value_column) as value FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp GROUP BY source_data.unit_id, exposure_data.group_id; -- Experiment SELECT group_id, COUNT(distinct unit_id) total_units FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value)/SUM(total_units) as mean FROM unit_data JOIN group_data USING (group_id) GROUP BY group_id; ``` ### Methodology notes In the metrics page view, Statsig uses APPROX\_COUNT\_DISTINCT (or equivalent) to avoid large compute jobs on analytical count distinct. The approximate error is acceptably small for topline estimates. For experiment result loads, the calculation is analytical and exact to avoid jitter or bias from approximation error. ## Options * Metric Breakdowns * You can configure Metadata Columns to group results by, getting easy access to dimensional views in pulse results * Winsorization * Specify a lower and/or upper percentile bound to winsorize at. Statsig clamps all values below the lower threshold, or above the upper threshold, to that threshold to reduce the outsized impact of outliers on your analysis. * CUPED * Specify if you want to calculate CUPED, and the lookback window for CUPED's pre-experiment data inputs * Thresholding * Turn this metric into a 1/0 unit count metric counting if the unit's total count equals to or surpasses (>=) a given threshold * [Cohort Windows](/statsig-warehouse-native/features/cohort-metrics) * You can specify a window for data collection after a unit's exposure. For example, a 0-1 day cohort window would only count actions from days 0 and 1 after a unit was exposed to an experiment * Select **Only include units with a completed window** to remove units from pulse analysis for this metric until the cohort window has completed. ## Limits Count distinct metrics are available in most experiments, except for Switchbacks. ================================================================================ # Unit Count (One-Time Event) Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/unit-count-once Updated: 2026-07-11 ================================================================================ ## Use cases This is an extremely common metric type, used to measure participation rates among users in the experiment. ## Calculation At the unit level, unit count metrics create a 1/0 flag for if they participated. At the group level, Statsig calculates the mean as the SUM of the unit-level flags, divided by the count of UNIQUE UNITS exposed to the experiment. The SQL for this calculation: ```sql expandable -- Unit Level SELECT distinct source_data.unit_id, exposure_data.group_id, 1 as value FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp ; -- Experiment SELECT group_id, COUNT(distinct unit_id) total_units FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value)/SUM(total_units) as mean FROM unit_data JOIN group_data USING (group_id) GROUP BY group_id; ``` ### Methodology notes Statsig calculates daily and days-since-exposure views for user-day metrics per day. If a user is active on all 14 days of an experiment, they contribute 1 to the overall cumulative numerator, but 1 to each day of the daily view. The daily view doesn't deduplicate users. This yields a more intuitive interpretation and produces less sparse timeseries data on long experiments. However, this can cause mix-shift effects where the daily trend moves in the opposite direction of the cumulative timeseries, because daily returning users have already been counted and don't add metric value to the cumulative view. Unit count metrics use the SQL SUM aggregation. See the Options section below for many advanced options. ## Options * Metric Breakdowns: configure Metadata Columns to group results by for dimensional views in pulse results. * Multi Source: build metrics from multiple metric sources, equivalent to a UNION in SQL. Useful when the same measure exists in sharded or disparate tables. * Rollup Mode: controls the specific way that Unit Count metrics are aggregated. ================================================================================ # Unit Count (Window) Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/unit-count-window Updated: 2026-07-11 ================================================================================ ## Use cases This is an extremely common metric type, used to measure participation rates early in experiments, or to measure retention later into an experiment (e.g. did a user come back in their second week). ## Calculation At the unit level, unit count metrics create a 1/0 flag for if they participated during the time window. The time window is relative to the unit's first exposure. Statsig doesn't consider subsequent exposures. At the group level, Statsig calculates the mean as the SUM of the unit-level flags, divided by the count of UNIQUE UNITS exposed to the experiment. The SQL for this calculation: ```sql expandable -- Unit Level SELECT distinct source_data.unit_id, exposure_data.group_id, MAX(if(source_data.in_time_window, 1, 0)) as value FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp GROUP BY source_data.unit_id, exposure_data.group_id; ; -- Experiment SELECT group_id, COUNT(distinct unit_id) total_units FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value)/SUM(total_units) as mean FROM unit_data JOIN group_data USING (group_id) GROUP BY group_id; ``` ## Options * Metric Breakdowns: configure Metadata Columns to group results by for dimensional views in pulse results. * Multi Source: build metrics from multiple metric sources, equivalent to a UNION in SQL. Useful when the same measure exists in sharded or disparate tables. * Rollup Mode: controls the specific way that Unit Count metrics are aggregated. ================================================================================ # Unit Count (Latest Value) Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/unit-count-latest Updated: 2026-07-11 ================================================================================ ## Use cases This metric type is useful for measuring state. For example, use it to determine whether a test variant currently has more active subscribers or active users than the control group. ## Calculation At the unit level, unit count metrics create a 1/0 flag for if they participated on a given day. Statsig carries this forward to the current date if data is behind. At the group level, Statsig calculates the mean as the SUM of the unit-level flags, divided by the count of UNIQUE UNITS exposed to the experiment. The SQL for this calculation: ```sql expandable -- Unit Level SELECT unit_id, group_id, if(passes_filter, 1, 0) as value FROM ( SELECT source_data.*, exposures_data.group_id, source_data.date = MAX(source_data.date) over (partition by source_data.unit_id) as is_latest_date FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp ) WHERE is_latest_date = 1 GROUP BY source_data.unit_id, exposure_data.group_id; -- Experiment SELECT group_id, COUNT(distinct unit_id) total_units FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value)/SUM(total_units) as mean FROM unit_data JOIN group_data USING (group_id) GROUP BY group_id; ``` ## Options * Metric Breakdowns: configure Metadata Columns to group results by for dimensional views in pulse results. * Multi Source: build metrics from multiple metric sources, equivalent to a UNION in SQL. Useful when the same measure exists in sharded or disparate tables. * Rollup Mode: controls the specific way that Unit Count metrics are aggregated. ================================================================================ # Unit Count (Daily Participation) Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/unit-count-rate Updated: 2026-07-11 ================================================================================ ## Use cases This metric type is an analogue for DAU. It is useful for growth accounting and for measuring ongoing usage and retention. ## Calculation At the unit level, unit count metrics create a 1/0 flag for if they participated on a given day. At the group level, Statsig calculates the mean as the SUM of the unit-level flags divided by each unit's days in the experiment, then divided by the count of UNIQUE UNITS exposed to the experiment. The SQL for this calculation: ```sql expandable -- Unit Level SELECT distinct source_data.unit_id, exposure_data.group_id COUNT(distinct source_data.date) as value FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp GROUP BY source_data.unit_id, exposure_data.group_id; -- Experiment SELECT group_id, unit_id, date_diff(first_exposure_ds, today()) + 1 AS days_exposed FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value/days_exposed)/COUNT(distinct unit_id) as mean FROM unit_data JOIN group_data USING (group_id, unit_id) GROUP BY group_id; ``` ## Options * Metric Breakdowns: configure Metadata Columns to group results by for dimensional views in pulse results. * Multi Source: build metrics from multiple metric sources, equivalent to a UNION in SQL. Useful when the same measure exists in sharded or disparate tables. * Rollup Mode: controls the specific way that Unit Count metrics are aggregated. ================================================================================ # Mean Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/mean Updated: 2026-07-11 ================================================================================ ### Use cases Mean metrics are most often used on event-level data, for example to measure "Average Time to Load" or "Average Purchase Value". ## Calculation At the unit level, mean metrics SUM their value column, and COUNT records where the value column is non-null. At the group level, Statsig calculates the mean as the SUM of the unit-level sums, and the SUM of the unit-level counts. The SQL looks like the following: ```sql expandable -- Unit Level SELECT source_data.unit_id, exposure_data.group_id, SUM(source_data.value_column) as value, COUNT(source_data.value_column) as records FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp WHERE value_column IS NOT NULL GROUP BY source_data.unit_id, exposure_data.group_id; -- Group Level SELECT group_id, SUM(value)/SUM(records) as mean FROM unit_data GROUP BY group_id; ``` ### Methodology notes Internally, mean metrics function like a SUM/COUNT [Ratio](./ratio) metric. Mean metrics have the delta method applied to account for covariance between unit-level numerators and denominators. ## Options * Metric Breakdowns * You can configure Metadata Columns to group results by, getting easy access to dimensional views in pulse results * Winsorization * Specify a lower and/or upper percentile bound to winsorize at. Statsig clamps all values below the lower threshold, or above the upper threshold, to that threshold to reduce the outsized impact of outliers on your analysis * [Cohort Windows](/statsig-warehouse-native/features/cohort-metrics) * You can specify a window for data collection after a unit's exposure. For example, a 0-1 day cohort window would only count actions from days 0 and 1 after a unit was exposed to an experiment * Select **Only include units with a completed window** to remove units from pulse analysis for this metric until the cohort window has completed * [Baked Metrics](/statsig-warehouse-native/features/cohort-metrics#metric-bake-windows) * [Baked Metrics](/statsig-warehouse-native/features/cohort-metrics#metric-bake-windows) let you specify how long a metric needs to mature. This is common in situations like chargebacks or cancellations. Statsig delays loading the data until the window has elapsed, and only calculates pulse results for that metric after a unit's metric has matured. ================================================================================ # Ratio Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/ratio Updated: 2026-07-11 ================================================================================ ## Use cases Ratio metrics provide more nuanced understanding: revenue went up and purchase volume increased, but did revenue per purchase increase in a meaningful way? Ratio metrics also normalize metrics. For example, if you rent out devices like scooters, you might run a scooter-level experiment but want to measure average revenue per distinct rider. You can create a metric of SUM(revenue)/COUNT\_DISTINCT(rider\_id) to calculate this normalized metric and reduce the influence of scooters in popular areas that get many riders. ## Calculation At the unit level, ratio metrics calculate the unit-level aggregation of both component metrics. At the group level, the mean is the total group value of the first metric divided by the total group value of the second metric. {% callout type="note" %} The denominator is not the number of units in the experiment; the normalization is by the denominator metric. {% /callout %} The SQL for this calculation: ```sql expandable -- Denominator (Checkouts) SELECT source_data.unit_id, exposure_data.group_id, COUNT(1) as denominator FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp GROUP BY source_data.unit_id, exposure_data.group_id; -- Numerator (Revenue) SELECT unit_id, group_id, SUM(revenue) as numerator FROM source_data GROUP BY unit_id, group_id; -- Group Level SELECT group_id, SUM(numerator)/SUM(denominator) as mean FROM denominator -- full outer join depending on settings LEFT JOIN numerator USING (group_id) GROUP BY group_id; ``` ### Methodology notes Ratio metrics require adjustment because the numerator and denominator may covary at the unit level. Statsig uses the delta method to estimate this adjustment. By default, Statsig treats ratio metrics as a conversion rate (unordered), counting numerator events only for units that also performed the denominator event. In Statsig Warehouse Native, you can configure this behavior through Advanced settings at the metric level. ## Options * Treat as conversion rate (unordered): includes numerator events only if the unit also performed the denominator event, regardless of order. Uncheck for a simple ratio, which counts numerator events for all units even if they never performed the denominator event. * [Cohort Windows](/statsig-warehouse-native/features/cohort-metrics) (Numerator and Denominator): specify a window for data collection after a unit's exposure. For example, a 0-1 day cohort window counts only actions from days 0 and 1 after exposure. Select **Only include units with a completed window** to exclude units from pulse analysis until their cohort window has completed. * Winsorization: specify a lower and/or upper percentile bound. You can set thresholds independently for the numerator and denominator. All values below the lower threshold or above the upper threshold are clamped to reduce the impact of outliers. * [Baked Metrics](/statsig-warehouse-native/features/cohort-metrics#metric-bake-windows): specify how long a metric needs to mature. This is common for chargebacks or cancellations. Statsig delays loading the data until the window has elapsed, and calculates pulse results only after a unit's metric has matured. ================================================================================ # Funnel++ URL: https://docs.statsig.com/statsig-warehouse-native/metrics/funnel Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Funnel++ is a Statsig Warehouse Native feature. {% /callout %} ## How funnel metrics work Funnel metrics measure user journeys through a series of steps. For configuration guidance, refer to [funnel metrics](/statsig-warehouse-native/features/funnel-metrics). Statsig provides advanced capabilities within the statistical framework used in pulse analysis. These include: * Configurable completion windows per-step * Session controls - going beyond user-based conversion * Built-in allowance for timestamp noise ### Use cases Use funnel metrics to understand how users move through your product. For example, you might want to measure: * User conversion through a subscription flow, for example Start -> Description Page -> Payment Info -> Confirm, to measure if an experiment causes users to drop off less, and at which steps. * User conversion from "logged out visitor" to "first logged-in-article-read" using Statsig's [ID Resolution](/statsig-warehouse-native/features/funnel-metrics). * Session-level conversion for checking out a vacation property, to understand conversion for every unique checkout flow, not only at the user level. ## Calculation At the unit level, funnel metrics calculate, for each step of the funnel, if the unit completed that step some time after all previous steps were completed in order. This creates a series of step flags. If using session funnels, those step flags are instead counts of unique sessions. At the group level, Statsig calculates the stepwise mean as the units for the next step divided by the units for the current step. Statsig calculates the overall mean as the units/sessions that completed the funnel divided by the unit/sessions that started the funnel. {% callout type="note" %} For each step, the *first* occurrence after the previous step is treated as the canonical trigger and timestamp for that event going forward for subsequent timestamp comparisons. {% /callout %} The SQL looks like the following: ```sql expandable -- Unit Level, per step SELECT distinct source_data.unit_id, source_data.funnel_session_id, -- optional source_data.funnel_step_id, IF(`<Completed All Steps Up to Current Step In Order>`, 1, 0) as numerator, IF(`<Completed Previous Steps In Order>`, 1, 0) as denominator FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp ; --Group Level SELECT group_id, funnel_step_id, SUM(numerator)/SUM(denominator) as mean FROM unit_data GROUP BY group_id; ``` ### Methodology notes Conversion metrics require adjustment due to potential unit-level covariance between the numerator and the denominator. Statsig uses the delta method to estimate this adjustment. By default, Statsig only includes numerators from metrics with non-null, non-zero denominators. This is configurable in the advanced settings. Funnels in experiment-analysis order strictly. For example, in the funnel A->B->C, all subsequent timestamp comparisons are based on the FIRST occurrence of A. If a user has an A event on day 0, with no other events, and then A/B/C all occur in order on day 5, this funnel won't count as completed if there is a 1-day conversion window from A->B since the time from the first A to the first B is 5 days. ## Options * Conversion window * A step-level setting specifying how long this step has to occur after the previous step. For example, in the funnel A->B->C, if B has a conversion window of one hour, Statsig only counts it if it occurs within 1 hour of A. * Use Strict Event Ordering * Whether to use >= or > when comparing step timestamps. Strict mode allows you to have two subsequent steps of the same event without it "automatically" passing * Timestamp Allowance * Allow some amount of buffer, in milliseconds, between funnel steps. Event logs can have noise on timestamps, so sometimes events may be logged slightly out of order. This setting helps to correct for this issue. * Count Distinct Mode * Whether to count sessions or units. For sessions, you must provide a session identifier field on each step * Calculation window (optional) * How long a unit has to complete the funnel after starting, and whether the funnel starts when the unit is exposed to the experiment or when they trigger the first event in the funnel * Treat Exposure as Initial Funnel Event * With this setting enabled, the first step of the funnel is the exposure event of the experiment. This makes it easy to measure the conversion rate to the first event, and additionally normalizes the final outcome per experiment-user. This setting is incompatible with session-based funnels. * Measure time to complete * Switches the funnel mode into measuring the average time for users to complete a funnel. This creates a ratio metric, where the numerator is the sum of funnel seconds-to-complete, and the denominator is the number of completed funnels. This can be useful in isolation, or when paired with a conversion measurement to understand the change to both completion rate and time to complete. ## Visualization In addition to breaking down lift by funnel steps, you can create funnel visualizations by step. {% figure %} ![Funnel visualization by step breakdown](/images/statsig-warehouse-native/metrics/funnel/c38ca372-5323-408f-89b6-121300b4e60c.png) {% /figure %} {% figure %} ![Funnel step analysis chart](/images/statsig-warehouse-native/metrics/funnel/8e10442f-be78-455e-84a3-053e4a61434a.png) {% /figure %} ================================================================================ # Percentile Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/percentile Updated: 2026-07-11 ================================================================================ ### Use cases The mean of a metric can be acceptable while the tail end (for example, the 99th percentile) is not. Consider a website with an average TTL of 300ms but a p99 TTL of 1 minute. A small portion of the population has an unusable experience that doesn't appear in the mean, making it difficult to measure whether the p99 value changed with traditional A/B/n metrics. Percentile metrics are common as guardrail metrics for performance regression, and for measuring improvements from investments in performance and infrastructure. ## Calculation For percentile metrics, there is no unit-level calculation; Statsig runs the analysis at the group level. The SQL looks like the following: ```sql expandable -- Group Level SELECT exposures_data.group_id, PERCENTILE(user_data.value, percentile_level) as value, COUNT(distinct user_data.user_id) as population FROM user_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp WHERE value IS NOT NULL GROUP BY group_id; ``` ### Methodology notes Percentile metrics use the outer CI method to estimate a confidence interval and significance. Deng et al. describe the methodology in section 4 of [this paper](https://arxiv.org/pdf/1803.06336). {% callout type="note" %} Some metrics aren't well-formed for this approach, because the method assumes the underlying distribution is continuous. For example, if your data has 1/3 of rows with a value of 0, 1/3 with a value of 5, and 1/3 with a value of 10, Statsig doesn't calculate significance for a median or p99 metric because there is no local variability. {% /callout %} ## Options * Metric Breakdowns * You can configure Metadata Columns to group results by, getting easy access to dimensional views in pulse results ================================================================================ # Log Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/log Updated: 2026-07-11 ================================================================================ ### Use cases Log metrics are useful for understanding if the distribution of a log-normal or tail-driven metric has shifted. Statsig calculates the metric as a conditional mean: a ratio metric where the numerator is the sum of unit-level log values and the denominator is 1 for units with a valid log. Statsig filters out records with a 0 denominator, because imputing 0s for logs doesn't work without a treatment such as an inverse hyperbolic sine function. Common uses include revenue, time spent, or other metrics where a small portion of users drives most of the value but bulk improvements matter. Log metrics measure relative change per unit: an increase of 1 corresponds to a multiplication by the log's base. ## Calculation At the unit level, count metrics run a COUNT(1) or SUM(`value`) across their metric source. At the group level, Statsig calculates the mean as the SUM of the log of the unit-level value, divided by the count of units with a unit-level value that log is valid for (exists, and is greater than 0). The SQL for a count metric looks like the following: ```sql expandable -- Unit Level SELECT source_data.unit_id, exposure_data.group_id, COUNT(1) as value FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp GROUP BY unit_id, group_id; -- Group Level SELECT group_id, -- divide the sum of the logged values by the count of participating units SUM(LOG(value, <base>))/COUNT(1) as mean FROM unit_data WHERE value > 0 -- the filter is implicit from the CTE, but let's make it explicit -- a sum metric might have negative values GROUP BY group_id; ``` ### Methodology notes Log metrics can be difficult to interpret and to extrapolate to topline values. Use log metrics together with the raw or winsorized SUM and COUNT metric. There are a few ways to handle 0s in a log metric. A transformation like [IHS](https://en.wikipedia.org/wiki/Inverse_hyperbolic_functions) can approximate the behavior of log for large values while accepting 0s as inputs. Alternatively, you can scope the analysis to non-zero units. Statsig uses the second approach for ease of interpretation, because log properties are broadly understood. This means there is a potential confounding factor of participation rate. To mitigate this, Statsig presents results the same as for [ratio](/statsig-warehouse-native/metrics/ratio) metrics, including statistics for the overall result and the implicit numerators and denominators. ## Options Non-log options depend on whether the metric is a Sum or Count. * Custom log Base * You can configure a custom base for the log operation. Defaults to LN ================================================================================ # First or Latest Value Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/latest-value Updated: 2026-07-11 ================================================================================ ## Use cases ### First Value First value metrics track the initial experience a user has with your product. This could be first purchase value, initial time-to-load, or any other initial result you are expecting to influence with your change. ### Latest Value Latest value metrics track how an experiment is impacting the state of your population. For example, you might want to measure if the test group has a higher net account balance, or if the current loyalty rewards balance is higher for users in one arm of an experiment. You can use latest value metrics to approximate user statuses, such as "is\_subscriber", if you have a Metric Source with a 1/0 flag for all users. Statsig recommends the [unit count](./unit-count-latest) equivalent for this use case, because you can filter on a sparse dataset and Statsig handles imputing and tracking 0s. ## Calculation At the unit level, first value metrics calculate each day's first non-null value within cohort bounds. Similarly, latest value metrics calculate each day's latest non-null value within any cohort bounds. For pulse, Statsig carries the first value forward from the first date with data, and determines the latest value by taking the latest value from the latest day available. At the group level, Statsig calculates the mean as the SUM of the unit-level values, divided by the count of UNIQUE UNITS exposed to the experiment. The SQL looks like the following: ```sql expandable -- Unit Level SELECT source_data.unit_id, exposure_data.group_id, LATEST_VALUE(source_data.value_field) as value -- or FIRST_VALUE FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp WHERE value_field IS NOT NULL GROUP BY source_data.unit_id, exposure_data.group_id; -- Experiment SELECT group_id, COUNT(distinct unit_id) total_units FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value)/SUM(total_units) as mean FROM unit_data JOIN group_data USING (group_id) GROUP BY group_id; ``` ### Methodology notes Statsig treats users without a value as 0s. If an existing value is present, the user retains that value unless a 0 or a new value is provided later. ## Options * Metric Breakdowns * You can configure Metadata Columns to group results by, getting easy access to dimensional views in pulse results * [Cohort Windows](/statsig-warehouse-native/features/cohort-metrics) * You can specify a window for data collection after a unit's exposure. For example, a 0-1 day cohort window would only count actions from days 0 and 1 after a unit was exposed to an experiment * Select **Only include units with a completed window** to remove units from pulse analysis for this metric until the cohort window has completed * [CUPED](/experiments/statistical-methods/methodologies/cuped) * Specify if you want to calculate CUPED, and the lookback window for CUPED's pre-experiment data inputs ### Special Case: Surrogate Metrics You can use latest value metrics to implement surrogate metrics. Surrogate metrics (aka proxy metrics or predictive metrics) are a prediction of some long term metric that's impractical to measure over the duration of an experiment, and have some inherent prediction error associated with the model used to derive the metric values. In advanced settings for latest value metrics, you can indicate a metric as a surrogate metric with a mean squared error (MSE). Prediction accuracy is then accounted for in variance calculation, which adjusts p-values and confidence intervals accordingly. Consider the variable X to be the true north metric which is being predicted by the surrogate metric S. The surrogate metric S is assumed to be an unbiased estimator with an error term $\epsilon$. $$ \mu\_\{X} = \mu\_\{S} = \overline\{S} $$ $$ Var(X) = Var(S + \epsilon) = Var(S) + MSE $$ $$ Var(\overline\{X}) = \frac\{Var(X)}\{n} = \frac\{Var(S) + MSE}\{n} $$ ================================================================================ # Retention Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/retention Updated: 2026-07-11 ================================================================================ ## Use cases Retention metrics measure user stickiness, conversion, and growth over the duration of experiments and holdouts. For example, a retention metric can evaluate changes in "Current User Retention", "Notification Retention", or "Video Viewer Retention" over an experiment, broken down in timeseries and days-since-exposure views. Many platforms limit retention metrics to checking if a unit was active between days X and X+Y since exposure. This is useful for new-user or marketing experiments but is less useful for experiments targeted at an existing user base. [This article in Lenny's newsletter](https://www.lennysnewsletter.com/p/how-duolingo-reignited-user-growth) shows how teams use these metrics to drive user growth. Use this metric type for any change aimed at increasing user stickiness, such as changes to notifications, reactivation campaigns, or quality improvements. ## Setup and definition Retention metrics are defined with a duration and a lookback window. The period is measured backwards from the end. For example, "Lookback = 7, Duration = 14" (L7D14) measures the week ending 14 days after the start event. {% figure %} ![Retention Setup](/images/statsig-warehouse-native/metrics/retention/cce09282-0a9b-4218-b823-7416f03ef387.png) {% /figure %} This is a rolling calculation. Each day a user triggers the start event, the metric denominator increments by 1. If the user is active in the corresponding completion window, the numerator is 1 for that day. Statsig includes only days with completed windows in pulse. For example, if the duration is 7, Statsig excludes the last week of data from pulse to avoid diluting the metric, because an L3D7 metric would always have a numerator of 0 for those days. The `allow cohort metrics to mature after experiment end` setting in advanced experiment settings allows post-experiment data to complete the analysis, so Statsig includes units exposed later in the experiment. Use this setting when the treatment is one-time and doesn't need to be re-applied to affect users. {% figure %} ![Retention Explanation](/images/statsig-warehouse-native/metrics/retention/2a9d8731-1c28-4c59-a0fe-3c1e7586c129.gif) {% /figure %} ## Calculation ```sql expandable -- Denominator - 1/0 flag for activity on a day WITH denominator AS ( SELECT source_data.unit_id, source_data.date, exposure_data.group_id, MAX(1) as denominator FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp WHERE <start_filter> GROUP BY ALL; ), -- Numerator Candidates - 1/0 flag for success activity on a day -- Note by default this is equivalent to the denominator CTE numerator_candidates AS ( SELECT source_data.unit_id, source_data.date, exposure_data.group_id, MAX(1) as denominator FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp WHERE <success_filter> GROUP BY ALL ), -- Numerators, deduplicated - 1/0 flag for success per denominator -- Now we have a 1-0 numerator flag per denominator-day joined_data AS ( SELECT denominator.unit_id, denominator.date, den.group_id, 1 as denominator MAX(CASE WHEN numerator_candidates.unit_id IS NOT NULL THEN 1 ELSE 0 END) as numerator FROM denominator LEFT JOIN numerator_candidates ON denominator.unit_id = numerator_candidates.unit_id AND numerator_candidates.date BETWEEN denominator.date + INTERVAL '<END - (LENGTH - 1)>' DAY AND denominator.date + INTERVAL '<LENGTH>' DAY GROUP BY ALL ) -- Group Level SELECT group_id, SUM(denominator) as unit_days_started, SUM(numerator) as unit_days_completed, SUM(numerator)/SUM(denominator) as mean FROM joined_data GROUP BY ALL ``` ### Methodology notes Statsig treats retention metrics as [ratio metrics](/statsig-warehouse-native/metrics/ratio) for pulse calculations. The only distinction is that the metric date is attributed to the denominator date. The ratio components for retention metrics reflect the rolling metric definition: * the denominator is the average number of days per user where the "retention start" event was triggered * the numerator is the average number of days per user where a "retention start" event had a corresponding "retention end" event in its retention period. ## Options * Metric Breakdowns * You can configure Metadata Columns to group results by, getting easy access to dimensional views in pulse results * Retention Lookback Window (Days) * The length of the "Completion Event" collection window * Retention Period End (Days) * When to stop measuring retention completion events * Use a different start and completion event for retention calculations: choose a secondary event for completion windows. By default, retention measures a behavior's retention against itself. Enabling this option lets you measure a secondary event instead. For example, with user accounting flags, you can measure "IS CHURNED" -> "IS REACTIVATED" and "IS REACTIVATED" -> "IS CHURNED" to track both reactivation and falloff of a long-term marketing test. ================================================================================ # Max/Min Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/max-min Updated: 2026-07-11 ================================================================================ ### Use cases Max/min metrics track users' extremes during an experiment. Common examples are: * Measuring the impact of performance changes on users' worst experiences by analyzing the maximum latency per user. * Measuring the effect of game changes on user performance by calculating the peak high score per user. * Counting the number of users who ever left a 2-star review or lower by applying MIN(review\_score) with a threshold condition. ## Calculation for max/min At the unit level, max/min metrics take the max/min of their input column. At the group level, Statsig calculates the max/min as the MEAN of the unit-level sums, divided by the count of UNIQUE UNITS exposed to the experiment. The MAX would look like the SQL below: ```sql expandable -- Unit Level SELECT source_data.unit_id, exposure_data.group_id, MAX(source_data.value_column) as value FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp GROUP BY source_data.unit_id, exposure_data.group_id; -- Experiment SELECT group_id, COUNT(distinct unit_id) total_units FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value)/SUM(total_units) as mean FROM unit_data JOIN group_data USING (group_id) GROUP BY group_id; ``` The MIN would look like the SQL below: ```sql expandable -- Unit Level SELECT source_data.unit_id, exposure_data.group_id, MIN(source_data.value_column) as value FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp GROUP BY source_data.unit_id, exposure_data.group_id; -- Experiment SELECT group_id, COUNT(distinct unit_id) total_units FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value)/SUM(total_units) as mean FROM unit_data JOIN group_data USING (group_id) GROUP BY group_id; ``` ### Methodology notes Max/min metrics are simple to use, and many advanced options are available. ## Options * Metric Breakdowns * You can configure Metadata Columns to group results by, getting easy access to dimensional views in pulse results * Winsorization * Specify a lower and/or upper percentile bound to winsorize at. Statsig clamps all values below the lower threshold, or above the upper threshold, to that threshold to reduce the outsized impact of outliers on your analysis * CUPED * Specify if you want to calculate CUPED, and the lookback window for CUPED's pre-experiment data inputs * Thresholding * Turn this metric into a 1/0 unit count metric counting if the unit's max/min equals to or surpasses (>=) a given threshold * [Cohort Windows](/statsig-warehouse-native/features/cohort-metrics) * You can specify a window for data collection after a unit's exposure. For example, a 0-1 day cohort window would only count actions from days 0 and 1 after a unit was exposed to an experiment * Select **Only include units with a completed window** to remove units from pulse analysis for this metric until the cohort window has completed * [Baked Metrics](/statsig-warehouse-native/features/cohort-metrics#metric-bake-windows) * [Baked Metrics](/statsig-warehouse-native/features/cohort-metrics#metric-bake-windows) let you specify how long a metric needs to mature. This is common in situations like chargebacks or cancellations. Statsig delays loading the data until the window has elapsed, and only calculates pulse results for that metric after a unit's metric has matured. ================================================================================ # Composite Metrics URL: https://docs.statsig.com/statsig-warehouse-native/metrics/composite Updated: 2026-07-11 ================================================================================ ### Use cases Composite metrics are a flexible metric type designed to sum aggregated metric sources at the user level. Use them when you need to add or subtract two or more aggregated values into a single signal, such as: * Net value (e.g., revenue minus refunds) * Unit-level experiment change (latestvalue minus first value) ## Calculation At the unit level, composite metrics first compute the aggregations of each component. Statsig then adds or subtracts the aggregated results according to the formula specified (for example: `A + B` or `A - B + C`). Statsig performs this calculation at a daily, 7-day, and cumulative level during experiment analysis. {% callout type="note" %} Composite metrics don't currently support daily rollups in turbo mode. {% /callout %} At the group level, Statsig calculates the mean as the average of the unit-level composite aggregation. ### Example If you define a composite gap metric as `Max Price - Min Price`, Statsig computes `Max Price` and `Min Price` for each user, then subtracts to get each user's net value. The experiment group mean is the average of those per-user results. The SQL for the individual components would look like the following: MAX Component: ```sql expandable -- Unit Level SELECT source_data.unit_id, exposure_data.group_id, MAX(source_data.value_column) as value FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp GROUP BY source_data.unit_id, exposure_data.group_id; -- Experiment SELECT group_id, COUNT(distinct unit_id) total_units FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value)/SUM(total_units) as mean FROM unit_data JOIN group_data USING (group_id) GROUP BY group_id; ``` MIN Component: ```sql expandable -- Unit Level SELECT source_data.unit_id, exposure_data.group_id, MIN(source_data.value_column) as value FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp GROUP BY source_data.unit_id, exposure_data.group_id; -- Experiment SELECT group_id, COUNT(distinct unit_id) total_units FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value)/SUM(total_units) as mean FROM unit_data JOIN group_data USING (group_id) GROUP BY group_id; ``` Composite Aggregation: ```sql expandable -- Unit Level SELECT source_data.unit_id, exposure_data.group_id, MAX(source_data.value_column) - MIN(source_data.value_column) AS value FROM source_data JOIN exposure_data ON -- Only include users who saw the experiment source_data.unit_id = exposure_data.unit_id -- Only include data from after the user saw the experiment -- In this case exposure_data is already deduped to the "first exposure" AND source_data.timestamp >= exposure_data.timestamp GROUP BY source_data.unit_id, exposure_data.group_id; -- Experiment SELECT group_id, COUNT(distinct unit_id) total_units FROM exposure_data GROUP BY group_id; -- Group Level SELECT group_id, SUM(value)/SUM(total_units) as mean FROM unit_data JOIN group_data USING (group_id) GROUP BY group_id; ``` ## Options * [Cohort Windows](/statsig-warehouse-native/features/cohort-metrics) * You can specify a window for data collection after a unit's exposure. For example, a 0-1 day cohort window would only count actions from days 0 and 1 after a unit was exposed to an experiment * Select **Only include units with a completed window** to remove units from pulse analysis for this metric until the cohort window has completed * Winsorization * Specify a lower and/or upper percentile bound to winsorize at. Statsig applies these bounds separately to the top-level metric *and* its individual components. * [Baked Metrics](/statsig-warehouse-native/features/cohort-metrics#metric-bake-windows) * [Baked Metrics](/statsig-warehouse-native/features/cohort-metrics#metric-bake-windows) let you specify how long a metric needs to mature. This is common in situations like chargebacks or cancellations. Statsig delays loading the data until the window has elapsed, and only calculates pulse results for that metric after the unit's metric has matured. ================================================================================ # Metric Examples URL: https://docs.statsig.com/statsig-warehouse-native/configuration/metric-examples Updated: 2026-07-11 ================================================================================ For customers transitioning from other warehouse-native vendors, the metric format is similar. Customers have generally been able to use APIs to fetch remote configurations, translate them, and post them to Statsig without issue. For customers migrating from in-house systems, there may be differences between how experiment metrics are defined internally and how Statsig handles them. This page covers common use cases and how Statsig handles them. ## Average user revenue from a wide table In many cases, companies have a primary source-of-truth table about user engagement with one row per user-day and many columns representing actions taken or other values. This table type integrates directly with Statsig. First, enter the table path and optionally a partition column to use for date partitioning: {% figure %} ![Metric source setup specifying table path and date partition](/images/whn/metric_examples_configuration.png) {% /figure %} Then, configure your timestamp field and ID types. Add any custom SQL aliases, for example, dividing revenue by 100 to convert from cents to dollars. {% figure %} ![Timestamp and ID type configuration with revenue alias](/images/whn/metric_examples_initials.png) {% /figure %} Go to the metrics tab, press create, configure your name/source, and then configure a sum metric on the column with the revenue value. {% figure %} ![Create metric dialog selecting revenue source](/images/whn/metric_examples_create_revenue.png) {% /figure %} {% figure %} ![Sum metric configuration for revenue column](/images/whn/metric_examples_configure_revenue.png) {% /figure %} ### How it works in experiments First, Statsig aggregates each unit-level record across the days they are enrolled in the experiment. Then, Statsig calculates the mean unit-level revenue per experiment group, imputing 0s for all exposed users with no revenue. Statsig provides a description of this in-product for any user who wants to learn more: {% figure %} ![Inline description explaining aggregation behavior](/images/whn/metric_examples_configuration.png) {% /figure %} ## Average current account value Often, you will want to understand if your experiment has altered the state of users. For example, you may want to know whether users in test vs. control have grown their account value. To measure this, provide a table or query that tracks users' account values each day. Then, set up a metric source pointing to that table or query. Go to the metrics tab, press create, configure your metric name & source, and then configure a latest value metric on the column with the account value. {% figure %} ![Create metric form for latest account value](/images/whn/metric_examples_create_account.png) {% /figure %} {% figure %} ![Latest value metric configuration using account column](/images/whn/metric_examples_configure_account.png) {% /figure %} ### How it works in experiments First, at unit level, Statsig calculates each day's latest non-null value within any cohort bounds and takes the latest value from the latest day available. Then, Statsig calculates the mean unit-level value per experiment group on each day, imputing 0s for all exposed users with no value. Statsig provides a description of this in-product for any user who wants to learn more: {% figure %} ![Product tooltip describing latest value aggregation logic](/images/statsig-warehouse-native/configuration/metric-examples/7fe6af8f-02a9-45f4-ae3f-5c682757e571.png) {% /figure %} ## Users' D7 participation rate A common metric in experimentation is measuring whether exposed users take specific actions within a defined time window. Provide an event table that records user actions with essential columns such as user\_id, timestamp, and event type. Configure your timestamp field and ID types as described in the previous sections. {% figure %} ![Event table metric source definition for participation rate](/images/statsig-warehouse-native/configuration/metric-examples/148469b7-df7b-4f39-af87-1ee9dd5ee431.png) {% /figure %} {% figure %} ![ID type configuration for participation event data](/images/statsig-warehouse-native/configuration/metric-examples/5bd9fcc7-54e1-4a15-a8c4-8d9950631d24.png) {% /figure %} Navigate to the metric catalog and create a unit count metric using the defined metric source. Use the 'Add Filter' option to focus on specific events relevant to your metric. {% figure %} ![Unit count metric creation with filters for specific event](/images/statsig-warehouse-native/configuration/metric-examples/04712a16-8dac-4c1f-a327-1854fd15d2aa.png) {% /figure %} When defining the metric, you can choose from several rollup modes: * Daily Participation Rate: measures the days a unit was active after exposure, divided by its total days in the experiment * On-Time Event: measures whether a unit performed an action any time after exposure * Latest Value: measures whether a unit passed metric filters on their last observed record * Custom Attribution Window: includes data for each unit within a specified time window after exposure {% figure %} ![Metric source selection during creation flow](/images/statsig-warehouse-native/configuration/metric-examples/959c5dbd-eecf-4797-bc87-7970ccda4947.png) {% /figure %} To measure user participation within 7 days, select 'Custom Attribution Window' as your rollup mode and set start = 0, end = 6 to define a 7-day window. Enable 'Only include units with a completed window' to exclude users who haven't yet reached the full 7-day period from your analysis. {% figure %} ![Rollup mode options for participation metrics](/images/statsig-warehouse-native/configuration/metric-examples/0632de2f-dc8c-44c0-85c1-39e8a6a6f070.png) {% /figure %} ### How it works in experiments First, at unit level, Statsig creates a 0/1 flag if the event triggers during the specified time window. Then, at the group level, Statsig calculates the mean as the SUM of the unit-level flags, divided by the count of UNIQUE UNITS exposed to the experiment. Refer to [Unit Count (Window) Metrics](/statsig-warehouse-native/metrics/unit-count-window) for more details about how these metrics are calculated. ## User funnel metric A common analysis in experimentation is understanding how a new feature impacts dropoff rates at each step of a user funnel. To create a funnel metric in Statsig, you need an event table that records each step of the events you want to track. The setup for your metric source follows the same process described in previous sections. Navigate to the metric catalog, and select 'Funnel' as your metric type. Choose the unit level for your funnel steps: a distinct count of users or sessions, depending on what you want to measure. {% figure %} ![Average session count metric configuration](/images/statsig-warehouse-native/configuration/metric-examples/76c815a1-14a6-4d8c-853e-10460f38d4a6.png) {% /figure %} Then, define your funnel steps, specifying the sequence of events users go through. {% figure %} ![Example showing multi-event participation configuration](/images/statsig-warehouse-native/configuration/metric-examples/91d50945-ba23-47d0-b0e4-85308a0e404c.png) {% /figure %} In the Advanced Settings, you can further customize your funnel metric. Options include specifying a calculation window, measuring time to convert, and treating exposure as the initial funnel event. {% figure %} ![Metric filters applied to isolate specific event values](/images/statsig-warehouse-native/configuration/metric-examples/a6de4690-83ad-49ed-af9a-4eef9c6a9700.png) {% /figure %} ### How it works in experiments First, at unit level, Statsig constructs a 1/0 (or session-count number for session funnels) metric for each step of the funnel. This flag is 1 if the unit completed that step some time after all previous steps were completed in order. If using a session-level funnel, it's the number of sessions where that is true, e.g. all previous steps were completed in order for that session key. Then, at the group level, Statsig calculates the stepwise mean as the total of each step's metric divided by the total metric from the previous step. Statsig calculates the overall mean as the units/sessions that completed the funnel divided by the unit/sessions that started the funnel. Statsig provides a description of this in-product for any user who wants to learn more: {% figure %} ![Metric catalog entry summarizing event-based conversion metric](/images/statsig-warehouse-native/configuration/metric-examples/84c22973-af41-495d-a943-f0f7436050ee.png) {% /figure %} ## User retention rate A retention metric measures changes in user stickiness and product growth. To create a retention metric in Statsig, you need an event table that captures the key activities indicating user retention. The setup for your metric source follows the same process described in previous sections. Navigate to the metric catalog and select 'Retention' as your metric type. Configure the retention period and lookback window. For example, if you set 'Retention Period End' to 14 and the retention lookback window to 7, retention is measured as whether the user triggered the retention event between day 8 and day 14. {% figure %} ![Configuration UI for retention-style rollup](/images/statsig-warehouse-native/configuration/metric-examples/e418815e-505c-4356-9922-d706bebb053c.png) {% /figure %} You can enable "Use a different start and completion event for retention calculations" if you want to use a different starting event than exposure, or if you want to define a specific subset of events as your retention event. For example, based on the setup shown in the screenshots, this measures the week 2 retention rate of users who made a purchase in week 1. {% figure %} ![Metric details view describing rollup logic](/images/statsig-warehouse-native/configuration/metric-examples/d3e12922-3767-4a05-987b-e16df126ea41.png) {% /figure %} In the Advanced Settings, you can configure the ID type for your retention metric. {% figure %} ![Metric result example for session count](/images/statsig-warehouse-native/configuration/metric-examples/cdf184a4-cd3a-4622-8669-0d029b1e76dc.png) {% /figure %} ### How it works in experiments First, for each unit per day, Statsig checks if the retention start event triggers and assigns a 0/1 flag, which serves as the denominator of the calculation. Next, Statsig checks if the retention completion event occurs within the specified time window and assigns a 0/1 flag, which serves as the numerator of the calculation. Finally, at the group level, Statsig calculates retention as sum(numerator) / sum(denominator) to determine the overall retention rate. Statsig provides a description of this in-product for any user who wants to learn more: {% figure %} ![Example of metric insight card for participation rate](/images/statsig-warehouse-native/configuration/metric-examples/94843265-ed43-4bce-954f-3f64ec2d380f.png) {% /figure %} ================================================================================ # Cohort Metrics URL: https://docs.statsig.com/statsig-warehouse-native/features/cohort-metrics Updated: 2026-07-11 ================================================================================ Cohort metrics are useful for many reasons. Common use cases are: * By ensuring all users have equal periods for data collection, there is an "apples to apples" comparison across user enrolled early/late in the experiment (which often corresponds to power/occasional users), and across different "time periods" that may have extrinsic factors like holidays * If analyzing an unbounded period, experimental units' variance in the population can increase over time - leading to scenario where error bars don't actually converge towards 0 as the experiment is run for longer! * This allows one to skip noisy early metrics, or focus on outcomes after users might churn - e.g. capturing "week-2 engagement" if a product has a 1-week trial period * This can also be used to capture "one-shot retention". [Retention metrics](/statsig-warehouse-native/metrics/retention) are used to capture rolling, ongoing retention. A user metric with a window from day X to day Y is a good way to check if an experiment is causing more users to retain at least X days The downsides of cohort metrics are that: * They don't capture any sort of long-term impact, or how that evolves over time. This is purely a point in time analysis and may not be appropriate for measuring complex, evolving behaviors * They make topline impact estimates lossier and harder to trust Some practitioners have made compelling arguments that cohort metrics are a better "standard" metric for organizations to use in analysis. Statsig tends to believe that the use of cohorts is dependent on business context, but consider whether they should be at least a part of an experiment's measurement (for example, measuring topline revenue as an overall evaluation criteria, but also measuring 7d revenue alongside it for additional context). This page explains the available settings, what they do, and how they interact so you know what Statsig measures. ## Basic cohort windows Basic cohort windows are a filter on metric data with a time range relative to the unit's time of exposure. For example, a cohort window from day 1 to day 6 filters to events from 24 hours until 168 hours after exposure. {% callout type="note" %} This is calculated as a timestamp comparison; a unit enrolled at 12pm will have exactly 24 hours until they hit the end of a 0-1 day cohort. {% /callout %} For metrics from data sources that are marked as daily data, the cohort comparison is truncated to a date so that day-0 data behaves as expected (e.g. a user exposed on `2025-01-05T09:00` will include the date-based data from `2025-01-05` instead of truncating to times "after 9:00am"). ## Waiting for maturation By default, cohort metrics can have a mix of maturation levels in the experimental population. For a 1-week cohort, users enrolled in the final week have a mix of maturities during analysis. This yields the maximum sample but can dilute the analysis with partial cohort windows. To prevent this, mark the metric as "Wait for cohort window to complete". This drops units' metric data from analysis and removes them from the experiment analysis population. In the examples below, one metric forces cohorts to complete. It has **less units** in the analysis, since many units don't have a complete window, a **lower total** because of the small unit count, but a **higher mean** since the units it does have have completed their window and have a longer data collection period on average. {% figure %} ![Metric that did not wait to mature](/images/whn/basic_cohort_metric.png) {% /figure %} {% figure %} ![Metric that waited to Mature](/images/whn/wait_to_mature_metric.png) {% /figure %} This setting can lead to different populations between metrics and filters out the last few days of an experiment's data in the daily time series, because new cohorts' data is not yet complete. View a metric's cohort settings by hovering over the metric name in the experiment scorecard. ## Visual examples This is what data collection looks like for a standard cohort metric with a 0-6 day window. This collects data for **7 days** because it is 0-indexed. {% figure %} ![Basic cohort example](/images/whn/basic_cohort_example.png) {% /figure %} If the cohort period extends past the end of the experiment, data collection is truncated to the end of the experiment by default. {% figure %} ![Basic cohort over end example](/images/whn/basic_cohort_over_end_example.png) {% /figure %} If the metric is configured to allow only completed cohort windows, Statsig excludes the unit from the analysis. Statsig doesn't include excluded units in the denominator for the average value of a sum or count metric, and filters their metric data from the analysis. {% figure %} ![Basic cohort example](/images/whn/completed_window_example.png) {% /figure %} If mature after end is configured in the experiment, data collection continues after the experiment ends, regardless of whether "wait for mature" is enabled. {% figure %} ![Basic cohort example](/images/whn/mature_after_end_example.png) {% /figure %} ## Experiment-based cohort settings Cohort controls are also available at the experiment level. The relevance of these settings depends on the kind of experiment being run, as described below. ### Allow post-experiment cohort data Checking **Allow Cohort Metrics to Mature After Experiment End** allows metrics to be collected after the experiment ends. This is recommended for one-time interventions, such as a new signup page, because post-experiment signal from units that received the intervention provides additional statistical power. {% figure %} ![Allow Cohorts to Mature](/images/whn/allow_cohorts_to_mature.png) {% /figure %} This is **not** recommended for continuous interventions, such as a ranking change, because post-experiment data can be diluted. For example, test users may receive the control experience during their post-experiment period, which dilutes results. The analysis is extended past the experiment end by the length of the longest cohort window across all metrics. Non-cohorted metrics are constrained to the analysis period; cohort metrics are filtered to the experiment end plus their cohort window. ### Fixed duration On the **Experiment Population** section of the experiment setup page, **Configure Analysis Period** with Analysis Type **Fixed Duration** counts metrics only for a specified period after **experiment start**. This is useful for experiments with a fixed enrollment point, such as email campaigns. {% figure %} ![Analysis Period Settings](/images/whn/configure_analysis_period.png) {% /figure %} This setting is only available for assign and analyze experiments. ### Cohorted duration On the **Experiment Population** section of the experiment setup page, **Configure Analysis Period** with Analysis Type **Cohorted Duration** applies allocation-based cohorting globally to all metrics in the experiment analysis. This is the same as the metric-based cohort but applied experiment-wide. This is useful for new user experiments. {% callout type="note" %} When this is used in conjunction with metric cohorts, Statsig uses the minimal end of the cohort window. For example, if a metric cohort is set to end at 7 days and the experiment at 10 days, Statsig uses 7. If the metric cohort is set to end at 7 days and the experiment at 5 days, Statsig uses 5. {% /callout %} The **only include units with a completed cohort window** setting can also be specified at the experiment level and applies to all metrics in the experiment when set. If not checked, the setting is applied on a metric-by-metric basis. ## Metric bake windows In some cases, a metric in your warehouse may not be mature until a certain time has passed, after which you care about the daily value. Statsig provides the option to specify a bake window for your metrics. Statsig excludes metrics that haven't reached the end of their bake window from the numerator and denominator of the metric in the analysis. Daily revenue is an example of a metric that may not be immediately mature. If a user makes a purchase and refunds it a few days later, their daily revenue retroactively changes to reflect the refund. A 28-day refund period would align naturally with a 28-day bake window for revenue metrics. {% figure %} ![Metric Bake Example](/images/statsig-warehouse-native/features/cohort-metrics/7c0ca9e7-ae49-4213-96aa-e0815a46940e.png) {% /figure %} In the example above, the revenue metric covers the last 28 days. Rather than calculating a partial result, users are excluded from the analysis until their data has had 28 days to mature. Partial results can occur because, when a metric bakes over a long period, part of the metric values come from before the experimental intervention and dilute the results. For a user exposed 1 day ago, 27 of 28 days of their revenue metric would be from the pre-intervention period, diluting the experiment results. ================================================================================ # Dimension Analysis URL: https://docs.statsig.com/statsig-warehouse-native/configuration/dimensional-analysis Updated: 2026-07-11 ================================================================================ {% figure %} ![dimension example](/images/statsig-warehouse-native/configuration/dimensional-analysis/550ac5d6-5e83-42aa-9db7-8618c200beef.png) {% /figure %} You can also configure [Differential Impact Detection](/experiments/exploring-results/differential-impact-detection) to help Statsig automatically flag when different classes of users are responding differently to your experiments. To view the Pulse result breakdowns for all categories within a metric, select the (+) sign next to the metric. ## Unit dimensions Unit dimensions refer to unit-level attributes that are either part of the user object you log to Statsig, provide as part of your [assignment data](/statsig-warehouse-native/configuration/assignment-sources), or provide through an [Entity Property](/statsig-warehouse-native/configuration/entity-properties). Examples of these attributes are operating system, country, region, or user segments. Using [explore queries](/experiments/interpreting-results/custom-queries), you can filter to specific unit dimensions or group results by a dimension. For example, you can view results for users in the US, or view results for users on iOS grouped by their country. Statsig chooses the dimension based on the last available record at or before exposure. Statsig doesn't use information from after the unit is exposed to a given experiment in the experiment analysis, because using post-exposure data can lead to data leakage and imbalanced comparisons. ## Metric dimensions Metric dimensions break down a metric's results based on the values in columns from your [metric source](/statsig-warehouse-native/configuration/metric-sources) for a given metric. You configure these breakdowns per-metric, after which Statsig calculates them for that metric across all pulse results. Unlike Unit Dimensions, Metric Dimensions are not mutually exclusive. For example, with a unit dimension a user can only be from one country for the purpose of pulse analysis, but a user on an e-commerce website could buy "clothes", "books", and "snacks" within a "total purchases" metric and contribute to each of those dimensions as well as the overall value. ## Default behavior By default, dimensional analysis: * only considers dimensions with at least 100 units in the experiment that participated (had a non-zero value) * chooses the top 10 dimensions by total value, and puts all others into an "OTHER" bucket. These defaults prevent extreme results, avoid cases where centrality assumptions fail due to low sample sizes on a specific dimension, and limit excessive multiple comparisons on the tail end of dimensional breakdowns. This behavior is configurable. Contact Statsig support to evaluate whether your use case warrants a change. ## Precomputed dimension loading timing For precomputed user dimensions that are configured and run on a schedule, Statsig processes dimension data asynchronously and it may take a few minutes to become available after the main experiment results load. You may temporarily see "No dimensions available for this time range" messages while Statsig processes the data, especially after the first reload of the day. This is expected behavior. Wait a few minutes and refresh the page to see the dimensional breakdowns. {% callout type="note" %} This timing behavior only affects precomputed user dimensions. User-triggered dimensional analysis doesn't experience this delay. {% /callout %} ================================================================================ # Assignment Sources URL: https://docs.statsig.com/statsig-warehouse-native/configuration/assignment-sources Updated: 2026-07-11 ================================================================================ Assignment Sources define the schema for your assignment data in Statsig. They serve as the input for determining which users are in an experiment and which treatment each user received. ## Creating an assignment source To create an assignment source, navigate to the data tab in Statsig and open the Assignment Sources pane. An Assignment Source is defined as a SQL query and a mapping of the output columns to specific fields Statsig requires (user identifiers, a `timestamp`, an experiment identifier, and a group identifier). {% figure %} ![Assignment Source](/images/statsig-warehouse-native/configuration/assignment-sources/264100295-05d71c64-9b31-4531-b371-03b6cb692446.png) {% /figure %} ## Scanning assignment sources Statsig scans assignment sources on-demand or on a schedule to find experiment data. These jobs run quickly and identify unique groups, the ID types present in the experiment, and the estimated number of users per group. After the scan is complete, you can view and create experiments from the assignment source. The assignment data also populates the Experiment creation flow. {% figure %} ![Assignment source scan results showing detected experiments](/images/statsig-warehouse-native/configuration/assignment-sources/87fac269-75bc-4a65-a660-339486605e24.png) {% /figure %} ## Manage assignment sources In the Assignment Source tab, you can see your Assignment sources and the experiments they're being used in. {% figure %} ![Assignment Source Tab](/images/statsig-warehouse-native/configuration/assignment-sources/264100297-c41cd747-089c-4ccf-8b45-b70a1b4e264a.png) {% /figure %} ## Example data For experiment assignment sources, Statsig requires the following information: which users received an exposure, when, and to which experiment: | Column Type | Description | Format/Rules | | ---------------------- | ----------------------------------------------------------------------------------------- | ------------------------------ | | timestamp | **Required** an identifier of when the experiment exposure occurred | Castable to Timestamp/Date | | unit identifier | **Required** at least one entity to which this metric belongs | Generally a user ID or similar | | experiment identifier | **Required** the experiment the exposure was for | Usually an experiment name | | group identifier | **Required** the experimental variant the user was assigned to | Usually a group name | | additional identifiers | *Optional* Entity identifiers for reuse across identifier types | | | context columns | *Optional* Fields which can be used to group by and filter results in exploratory queries | | For example, you could pull from exposure event logging directly: | timestamp | user\_id | company\_id | experiment\_name | group\_name | country | | ------------------- | ------------- | ---------- | ---------------- | ---------- | ------- | | 2023-10-10 00:01:01 | my\_user\_17503 | c\_22235455 | ranking\_v1\_vs\_v2 | v1 | US | | 2023-10-10 00:02:15 | my\_user\_18821 | c\_22235455 | ranking\_v1\_vs\_v2 | v2 | CA | | 2023-10-10 00:02:22 | my\_user\_18821 | c\_22235455 | search UI revamp | control | CA | ================================================================================ # Entity Properties URL: https://docs.statsig.com/statsig-warehouse-native/configuration/entity-properties Updated: 2026-07-11 ================================================================================ You can provide additional detail about an entity that doesn't typically change (for example, a user's home country), or a property that may change as part of an experiment (for example, Subscriber Status: True/False). For dynamic properties, you provide a timestamp field that Statsig uses to identify the most recent value before the user's exposure. This prevents imbalanced groups and biased results in cases where an experimental treatment impacts the property, for example if the treatment increased the subscription rate. {% figure %} ![Entity Properties configuration interface](/images/statsig-warehouse-native/configuration/entity-properties/7fcac725-54b4-46be-bb68-52fcc308fe5f.png) {% /figure %} {% figure %} ![Entity Properties setup screen with timestamp configuration](/images/statsig-warehouse-native/configuration/entity-properties/6c151cf4-d343-4750-8bfd-a6d48afd6e10.png) {% /figure %} ## Example data For property sources, Statsig only needs a user\_id and property fields. Property sources can define **fixed** properties (for example, a user's country of origin), or **dynamic** properties, in which case you need to provide a timestamp for Statsig to identify the most recent pre-exposure record. | Column Type | Description | Format/Rules | | ---------------- | ------------------------------------------------------------------------------------------- | ------------------------------ | | timestamp | *Optional* an identifier of when the property was defined. Required for dynamic properties | Castable to Timestamp/Date | | unit identifier | **Required** At least one entity to which this metric belongs | Generally a user ID or similar | | property columns | **Required** Fields which can be used to group by and filter results in exploratory queries | | For example, a static property source could just be: | user\_id | company\_id | country | | ------------- | ---------- | ------- | | my\_user\_17503 | c\_22235455 | US | | my\_user\_18821 | c\_22235455 | CA | You can use this source to filter and group results in any experiment that exposed either user\_id or company\_id. For a dynamic property, it might look like this: | user\_id | timestamp | company\_id | intent\_segment | spend\_segment | | ------------- | ---------- | ---------- | -------------- | ------------- | | my\_user\_17503 | 2023-10-10 | c\_22235455 | high\_intent | high | | my\_user\_17503 | 2023-10-11 | c\_22235455 | high\_intent | high | | my\_user\_17503 | 2023-10-12 | c\_22235455 | mid\_intent | high | | my\_user\_18821 | 2023-10-10 | c\_22235455 | low\_intent | low | | my\_user\_18821 | 2023-10-11 | c\_22235455 | low\_intent | mid | | my\_user\_18821 | 2023-10-12 | c\_22235455 | low\_intent | mid | The first user in this example has their intent\_segment property change on `2023-10-12`. Because Statsig uses the intent\_segment value before the user's exposure, this user may have different intent\_segment values across different experiments. ================================================================================ # Qualifying Events URL: https://docs.statsig.com/statsig-warehouse-native/configuration/qualifying-events Updated: 2026-07-11 ================================================================================ ## Using qualifying events Statsig uses qualifying events to simulate exposures for power analysis, and to filter exposures to users who triggered a specific event. Setting up a Qualifying Event is identical to setting up an Assignment Source, except they don't require experimental information. {% callout type="info" %} The qualifying event must occur after the exposure, as indicated by their respective timestamps. {% /callout %} You can use context columns to filter the qualifying event for power analysis. For example, you might have a Qualifying Event for page load and filter to different page identifiers for power analyses of experiments on different surfaces. ## Filter by qualifying events Statsig provides controls for using qualifying events to filter exposures in an experiment in cases of over-exposure. {% figure %} ![qualifying event filter](/images/whn/qe_filter.png) {% /figure %} In the image above: * Forwarded SDK events from Statsig filter the exposures. This could also be a single-event source or any data source. * The configuration replaces the actual exposure timestamp with the first observed qualifying event, which makes metric timelines and cohort timelines more accurate. * The configuration counts only qualifying events from within an hour of an exposure event, which helps avoid issues with late-landing attribution. * A filter limits events to the `www.statsig.com` domain. Some customers prefer this flexibility, and many set up qualifying events as individual events to use in their analyses. You can often identify over-exposed experiments by observing uniformly low participation rate on all metrics. ## Power analysis Use qualifying events in power analysis to estimate your experimentation population based on how many users historically triggered your proposed trigger event. {% figure %} ![power analysis with qualifying event](/images/whn/pac_qe.png) {% /figure %} You can filter these by event attributes, or by entity properties (e.g. users who triggered this event and are also in a segment or country). ## Example data | Column Type | Description | Format/Rules | | ---------------------- | ----------------------------------------------------------------------------------------- | ------------------------------ | | timestamp | **Required** an identifier of when the qualifying event occurred | Castable to Timestamp/Date | | unit identifier | **Required** At least one entity to which this metric belongs | Generally a user ID or similar | | additional identifiers | *Optional* Entity identifiers for reuse across identifier types | | | context columns | *Optional* Fields which can be used to group by and filter results in exploratory queries | | For example, you could pull from page load event logging directly and save it as a qualifying event called `Page Load`: | timestamp | user\_id | company\_id | page\_route | | ------------------- | ------------- | ---------- | ---------- | | 2023-10-10 00:01:01 | my\_user\_17503 | c\_22235455 | / | | 2023-10-10 00:02:15 | my\_user\_18821 | c\_22235455 | /search | | 2023-10-10 00:03:12 | my\_user\_22251 | c\_9928 | /profile | ================================================================================ # Tags & Teams URL: https://docs.statsig.com/statsig-warehouse-native/configuration/tags-and-teams Updated: 2026-07-11 ================================================================================ Statsig offers tools to enrich data sources, metrics, and entities with team ownership and tags. These provide a useful way to organize your data configuration, and also feed into advanced analytics tools like [meta-analysis](/statsig-warehouse-native/features/meta-analysis). ## Tags Experiments, Metrics, and Sources can all be tagged for easy discovery, search, and context. You can bulk-add metrics that share a tag to scorecard or guardrail metric sets. Statsig also provides a Core tag, which identifies company key metrics to use as guardrail metrics across experiments. Go to the [tags page](/metrics/create-metric-tags) for more information. ## Teams For larger organizations, the Teams feature adds an organizational and permissions layer on top of a Project. You configure teams at the Project (not Organization) level, and all Project Admins can edit them by default. After you configure teams and assign a user to a team, any configuration (gates, experiments, metrics, and so on) they create associates with their team and inherits that team's settings. Users who belong to multiple teams can choose which team to associate their configuration with at creation time. This is useful for managing ownership and review practices. Go to [Teams](/access-management/teams) to learn more. ================================================================================ # Roles URL: https://docs.statsig.com/statsig-warehouse-native/features/roles-and-access Updated: 2026-07-11 ================================================================================ Statsig has custom, configurable RBAC (role-based access control) across the console. This is especially useful for Warehouse Native customers, because you can control who can update which metrics as well as visibility into raw data. ================================================================================ # Verified Metrics URL: https://docs.statsig.com/metrics/verified Updated: 2026-07-11 ================================================================================ {% callout type="info" %} This is an Enterprise feature. Reach out to the support team, your sales contact, or the [Slack community](https://statsig.com/slack) to enable it. {% /callout %} Verified metrics identify metrics that are curated by your company and known to be trustworthy. When users set up experiments and search for metrics, a verified icon appears next to verified metrics. {% figure %} ![Verified metrics interface showing verified icon](/images/metrics/verified-metrics-interface.png) {% /figure %} To verify a metric, use the overflow menu on the metric. {% figure %} ![Metric overflow menu with verify option](/images/metrics/metric-overflow-verify-option.png) {% /figure %} Admins can control which roles can verify metrics. {% figure %} ![Admin role permissions for metric verification](/images/metrics/verified/437645df-dd63-4fd4-841d-1517126e1531.png) {% /figure %} ================================================================================ # Macros URL: https://docs.statsig.com/statsig-warehouse-native/configuration/query-tools Updated: 2026-07-11 ================================================================================ In **Metric** and **Assignment sources**, you can use Statsig Macros to directly inject a DATE() type that is relative to the experiment period being loaded. * `{statsig_start_date}` * `{statsig_end_date}` For example, in an incremental reload from `2023-09-01` to `2023-09-03`, this query: ```sql SELECT user_id, event, ts, dt FROM log_table WHERE dt BETWEEN `{statsig_start_date}` AND `{statsig_end_date}` ``` resolves to ```sql SELECT user_id, event, ts, dt FROM log_table WHERE dt BETWEEN DATE('2023-09-01') AND DATE('2023-09-03') ``` This is useful because you can inject filters into queries with joins or CTEs and confirm that the initial scan is pruned. Statsig adjusts the range as necessary in some cases. For example, in entity properties, Statsig adds a pre-experiment buffer to allow for late-landing property data, then selects the most recent value as of each unit's exposure. For CUPED, Statsig adjusts the range to include the pre-experiment window for CUPED calculations. ## Timezone note All timestamps in Warehouse Native from Statsig are in UTC, and Statsig assumes timestamps in the warehouse are timezone-less/UTC as well. ## Advanced macros * `{statsig_start_date_int}` * `{statsig_end_date_int}` Int versions of the above for use with number based partitioning - e.g. '2025-03-01' => 20250301 * `{statsig_experiment_start_timestamp}` Only available for metric sources and entity property sources; this resolves to the start timestamp of the experiment. In non-experiment contexts, it resolves to `TIMESTAMP('1970-01-01')`. This is useful for generating entity properties such as "30d revenue before the experiment started". ================================================================================ # Metric Family URL: https://docs.statsig.com/metrics/metric-family Updated: 2026-07-11 ================================================================================ Managing a large catalog of metrics that are slight variants of one another can be difficult. A common challenge is ensuring that each metric variant inherits the same changes when the metrics need to be updated. Metric Families let you create metric variants as child metrics of a parent metric. When you update a parent metric, the changes automatically cascade to its child metrics, keeping all related metric definitions consistent. {% callout type="info" %} Child metrics of **SUM** and **COUNT** parent metrics support these modifiable configs: - Add metric source filter - Outlier Management - Variance Reduction - Cohorts and Delayed Data {% /callout %} ## Create a child metric To create a child metric, select the **Create Child Metric** option in the dropdown menu in the top right corner of a metric. {% figure %} ![Creating a child metric](/images/metrics/metric-family/create-child.png) {% /figure %} You can track the child metrics for a given parent metric using the metric family icon in the top right. {% figure %} ![Tracking tree of child metrics](/images/metrics/metric-family/tree-child.png) {% /figure %} ## Deleting and archiving When a parent metric is deleted or archived, all of its child metrics are deleted or archived as well. Re-enabling the parent automatically restores those child metrics. You can't restore child metrics disabled through the parent individually; you can only re-enable them by restoring the parent metric. {% figure %} ![Metric deletion confirmation message](/images/metrics/metric-family/delete.png) {% /figure %} If a child metric is deleted or archived independently of its parent, it can be restored on its own, but it returns as a standalone metric without its original parent-child relationship. ## In experiments Selecting a Parent Metric in Experiment setup adds all of its child metrics as well. You can remove any metrics you don’t need in the experiment. {% figure %} ![Adding parent metric to an experiment will add all child metrics with it](/images/metrics/metric-family/in-experiment.png) {% /figure %} ================================================================================ # WHN Console API URL: https://docs.statsig.com/statsig-warehouse-native/configuration/console-api Updated: 2026-07-11 ================================================================================ Statsig's console API allows you to programmatically perform CRUD operations on all configuration objects. Use the API for bulk edits, or to sync your definitions into Statsig as a triggered or scheduled job. You can also use this API to trigger pulse results and manage workflows in Statsig. [Go to the full Console API Docs.](/console-api/introduction) ================================================================================ # Semantic Layer Sync URL: https://docs.statsig.com/statsig-warehouse-native/configuration/semantic-layer-sync Updated: 2026-07-11 ================================================================================ If you have centrally defined metrics, Statsig can sync its data sources and metrics as part of your data version management workflow. Using Statsig's [Console API](/console-api/introduction), you can automatically sync changes to matching definitions in Statsig, and optionally make the metrics read-only in the Statsig console. A demonstration [GitHub repository](https://github.com/statsig-io/semantic_layer) uses [a script](https://github.com/statsig-io/semantic_layer/blob/main/.github/scripts/statsig_sync.py) executed by [a GitHub Action](https://github.com/statsig-io/semantic_layer/blob/main/.github/workflows/statsig_sync.yml). This setup automatically synchronizes changes to .yml files in the /metrics or /metric\_sources directories in the repo. After you create or update these files, the script updates existing metrics or metric sources in Statsig, or creates new ones accordingly. To use this example template, follow these steps: 1. Fork [this repository](https://github.com/statsig-io/semantic_layer) to get started. 2. In your forked repository, add your Statsig Console API Key to GitHub Secrets. 3. Update the metric definitions to match your data needs. 4. Validate the automation by modifying relevant files and observing the triggered GitHub Action. ## Detailed guide ### Forking the repository 1. **Fork this repository** to create a copy in your GitHub account. {% figure %} <img src="/images/statsig-warehouse-native/configuration/semantic-layer-sync/79652104-6e13-467a-b4ef-dbbac83e15c9.png" alt="Untitled" width="1056" /> {% /figure %} ### Adding the Statsig Console API Key 2. Navigate to `Settings > Secrets and variables > Actions` in your repository settings. Create a new secret named `STATSIG_API_KEY` with your Statsig Console API key as its value. This key authenticates the synchronization process with the Statsig Console API. {% figure %} <img src="/images/statsig-warehouse-native/configuration/semantic-layer-sync/0a627a98-3772-4c36-89d1-7587e3950e84.png" alt="Untitled" width="1168" /> {% /figure %} ### Customizing metric definitions 3. Metric definitions are in the `./metrics` directory, and metric source definitions are in the `./metric_sources/` directory. To customize: * Use the Statsig Console API to fetch an existing **metric\_source** or **metric** using GET requests for [metric sources](/console-api/introduction#post-/metrics/metric_source/-name-) and [metrics](/console-api/introduction#get-/metrics/-metric_id-). * Remove the provided example metrics and metric sources, and replace them with your definitions in `./metric_sources/*.yml` and `./metrics/*.yml`. For readability, the examples use `metric.metricDefinition[]` instead of `metric.warehouseNative[]`. You can view this change [in the sync script](https://github.com/statsig-io/semantic_layer/blob/1611a68703caf18d7fa32088ff06d568d8b3b03a/.github/scripts/statsig_sync.py#L38). Adjust the mapping or revert to `metric.warehouseNative[]` in your definitions as needed. ### Verifying automation 4. To test, edit a metric or metric source description in your repository. This triggers the GitHub Action, visible under the `Actions` tab. The process then creates or updates your metrics and metric sources in Statsig based on the repository's semantic definitions. {% figure %} <img src="/images/statsig-warehouse-native/configuration/semantic-layer-sync/2dcf8961-3591-4021-88fe-984994177f35.png" alt="Untitled" width="1043" /> {% /figure %} {% figure %} <img src="/images/statsig-warehouse-native/configuration/semantic-layer-sync/04a084d6-ef6c-4b3f-aee9-41663ed47a60.png" alt="Untitled" width="1041" /> {% /figure %} {% figure %} <img src="/images/statsig-warehouse-native/configuration/semantic-layer-sync/4442dd9d-313f-4b16-a978-59513e4e8469.png" alt="Untitled" width="1031" /> {% /figure %} <br /> This example is a basic template. Test and extend it to meet production standards. Share feedback or improvements with the Statsig support team, your sales contact, or in the [Slack community](https://statsig.com/slack). ================================================================================ # Understanding Experiments URL: https://docs.statsig.com/statsig-warehouse-native/features/understanding-experiments Updated: 2026-07-11 ================================================================================ Running statistical analysis for an experiment can be complex. Statsig abstracts much of this complexity, which commonly leads to questions like the following: * What do different date ranges mean? * What metric data is included in the calculation? This page gives an overview of the basic settings in Statsig and what they mean for the analysis that runs. For more advanced settings, refer to [experiment configuration](./experiment-options) ## Date ranges The date range of an experiment controls the filters used when querying both exposure and metric data. For example, an experiment with a start date of `2025-02-01` and an end date of `2025-02-14` queries assignment data between those two dates, and also queries metric data between those two dates. There are a few exceptions: * Metric Bake Windows or Cohort Windows with `wait for...` enabled: cohorts that haven't been in the experiment for the duration of their cohort/bake window are excluded from both the exposure count and metric data for the metric in question. * If `allow cohort metrics to bake after experiment end` is enabled: the end date of analysis is extended artificially (only for cohort metrics) to maximize data collection for experiments with a one-time intervention where the experiment effect is expected to persist after the treatment is turned off. ### Exposure timing Statsig includes a given experimental unit’s (for example, a user’s) data only after that unit triggers the experiment. For example, if you are testing a new notification, metric data from before a given user sees the notification shouldn't be included in the experimental analysis. There is an exception: if a metric source uses a date column as its timestamp, Statsig doesn't include metric data from the day of exposure in the calculation. This happens because a timestamp cast from a date defaults to the first second of that date (00:00:00), which falls before the exposure time. To address this, the metric source has a setting called `Treat timestamp as date`: * If checked: Statsig includes day-0 data by converting the join condition to use the cast date (ignoring time). * If unchecked: Statsig excludes day-0 data from the join between exposure and metric for the reason described above. Statsig recommends including day-0 data (checking `measure timestamp as day`) if data has been preprocessed at a daily grain. ### Explore query dates Explore queries can filter metric data or assignment data independently. The default filtering option is metric data. This allows you to exclude certain dates with buggy or non-representative data, or to scope an analysis to recent periods (for example, if your hypothesis is that the treatment effect will take a long time to develop). Under the advanced tab, it is also possible to: * filter to exposures in a date range (or outside of) * filter to exposures within a certain cohort (e.g. X-Y days since exposure) ### Selecting dates Regardless of [turbo mode](/statsig-warehouse-native/features/turbo) when using incremental reloads, the date picker next to pulse can be used to view pulse "as of" a certain date. This is useful for reviewing historical discussion of an experiment or understanding how results have evolved over time. This data can also be viewed through the time series views in Statsig. {% callout type="note" %} Do not use this feature to cherry-pick dates with favorable results. {% /callout %} ## How experiment calculations work For every experiment analysis, the basic flow is: * Identifying when units first saw or were enrolled into the experiment. * Identifying what those units did (metric data: events or other rollups) after seeing the experimental variant they were assigned to, up until the end of the experiment. This results in a tagged dataset where metric data is associated with a group. * Aggregating that metric data over the experiment duration. Go to the [metrics documentation](/statsig-warehouse-native/configuration/metrics) for descriptions and SQL snippets describing this step. * Calculating group-level statistics to be used in the final scorecard analysis. At a high level, the analysis requires the observed totals/means per group, and the variance, which is used to understand how meaningful that difference is (typically statistical significance in frequentist analysis, or probability of best in Bayesian analysis). * Means are normalized per unit. For basic aggregations this imputes 0s: the calculation is the total value over the count of units exposed. For ratios and means, Statsig computes the means as the sum of the numerator over the sum of the denominator. ================================================================================ # Configuring Experiments URL: https://docs.statsig.com/statsig-warehouse-native/features/experiment-options Updated: 2026-07-11 ================================================================================ ## Configuration Statsig Warehouse Native offers extensive configurability in experiment setup and analysis. You can run an experiment with default options and get powerful, trustworthy statistical analysis of your results, but in many cases an advanced configuration helps maximize statistical power and measure exactly what you intend to measure. This page is a glossary for advanced experimentation settings: what they do, how to use them, and tradeoffs to consider. ## Experiment settings ## Basic settings ### Hypothesis Hypotheses are required to run experiments in Statsig. Hypotheses should specify what an experiment aims to accomplish and how that is measured. ### Primary and secondary metrics These are the metrics used as the evaluation criteria for your experiment. Generally, Statsig recommends a small number of primary metrics as your overall evaluation criteria, and putting guardrails and exploratory metrics into Secondary. Statsig provides project-level configuration of the maximum number of primary/secondary metrics; this behavior varies by company and industry, as well as the complexity of the space being measured. ### Experiment duration In experiment setup there are fields for *Experiment Measured In* and *Target*. These configure how long your experiment runs, influencing sequential testing as well as notifications/timeline alerts. {% callout type="note" %} Statsig's [power analysis tools](/statsig-warehouse-native/features/power-analysis) are the best way to determine what the target duration should be. You can attach a power analysis to an experiment to add context on the duration. {% /callout %} ### Experiment configuration ### Assignment source and groups For analysis-only experiments, Statsig pre-fills this section from the observed data in the data warehouse. You can configure images and descriptions. Statsig infers group sizes, but you should review and correct them if they don't match the intended traffic split. To update this, reset the experiment from the decision menu after it has started. ### Groups and parameters For end-to-end experiments (experiments using Statsig for both assignment and analysis), this section is where you configure targeting, layers, and the groups and associated parameters. Statsig automatically associates exposures generated from the setup with this experiment for analysis. ### Advanced settings A large number of advanced settings are available for customizing analysis. These can have complex interactions with data. ### Pre-computed user dimensions Configure dimensions as default breakdowns in pulse. For example, specify a user dimension such as country here to make it available in the scorecard results with daily loads. This lets you skip scheduled explore queries for this dimension, and the results appear inline in the scorecard. ### Stratified sampling Stratified Sampling lets you balance experiments across behavior or segments. Statsig tests random salts and picks the one that best balances user attributions. You can partially achieve this during analysis using [CURE](/statsig-warehouse-native/cure/introduction). Refer to [Stratified Sampling](/experiments/advanced-setup/stratified-sampling) for more details. ### ID type and secondary ID type You configure the ID type, which is the unit of randomization for an experiment, when setting up the experiment. This is a critical field that represents the kind of entity being experimented on. For example, if splitting traffic randomly per user, this should be `User ID`. If splitting traffic by company, this should be `Company ID`. Secondary ID types associate metrics from a different ID with the unit of randomization. For example, when running an experiment on a logged-out cookie ID, specifying `UserID` as a secondary ID allows analysis of `UserID` metrics like revenue while keeping `Cookie ID`s as the unit of analysis (for example, the denominator in means). This lets you understand the downstream impact of experiments without introducing survivorship bias or other bias to the analysis. This mapping can come directly from the assignment source. If multiple exposures are logged for a given unit, and at least one has both ID types, Statsig identifies that these two IDs are mapped to each other. Alternatively, you can specify mappings in an Entity Property Source: provide a mapping table of `ID1` to `ID2` and Statsig connects the data during analysis. You can configure secondary ID mapping as an enforced 1:1 mapping or as first-touch attribution. Refer to the [ID Resolution documentation](/statsig-warehouse-native/features/id-resolution) for more details. ### Allocation and cohorting **Configure Allocation Duration** If using a persistent assignment SDK ([docs](/client/concepts/persistent_assignment)), this setting controls when to stop enrolling users into the experiment. This setting can also be used without a persistent assignment SDK to filter out users exposed after the duration period. This is useful for enforcing even cohorts across a user base. For example, if a metric takes 7 days to mature and stopping the experiment halts its effect, set the allocation duration to 14 days and run the experiment for 21 days. This analyzes the first 14 days of exposed units while capturing all 7 days of the last cohort's metric behavior. **Configure Analysis Period** This setting controls the dates from which Statsig collects metric data. It is useful for truncating the analysis window while continuing to run an experiment. **Allow cohort metrics to mature after experiment end** This setting allows cohort or baked metrics that take time to mature to continue collecting data after the end of an experiment. This is only recommended if an experiment is a one-time intervention. For example, consider a 14-day experiment on new users that modifies a signup page. Removing the changes to the signup page doesn't impact users who already saw it. To maximize data for the "first-week revenue" metric, enable this setting so data continues to collect for users enrolled on day 14 until day 21, and for users enrolled on day 10 until day 17, even after the experiment ends on day 14. ### CURE covariates This section lets you configure covariates for CURE. Configure strong defaults in the project settings, and use this section to add relevant/domain-specific covariates. Refer to the [CURE](/statsig-warehouse-native/cure/introduction) documentation for more details. ### Analysis settings **Analytics type** Whether to use Frequentist or Bayesian analysis. You can't change this once an experiment starts, to avoid cherry-picking methodology. **Apply Sequential Testing \[Frequentist Only]** Controls whether [sequential testing](/experiments/advanced-setup/sequential-testing) is applied. Statsig recommends this setting to avoid false positives from peeking. **Bonferroni/Benjamini-Hochberg \[Frequentist Only]** Configures multiple-comparisons corrections, either controlling the false positive rate or false discovery rate. Refer to the more detailed documentation for [Bonferroni](/statsig-warehouse-native/features/statistics/methodologies/bonferroni-correction) and [Benjamini-Hochberg](/statsig-warehouse-native/features/statistics/methodologies/benjamini-hochberg-procedure). **Default Confidence Interval/Chance to Beat Threshold** The confidence level used for this experiment. The default is 95%, which is the recommended industry standard. Depending on the risk profile of the experiment, a stricter or less strict setting may be appropriate. **Use Informative Priors \[Bayesian Only]** For bayesian experiments, whether to use informed priors in analysis, and the configuration for them. **Turbo Mode** Whether to use [Turbo Mode](/statsig-warehouse-native/features/turbo) to run experiment reloads more quickly. **Filter Exposures by Qualifying Event** This setting allows filtering exposures to experimental units that did (or did not) trigger a secondary event beyond exposure. This is useful for analysis-only experiments on web experimentation platforms that over-expose heavily. For end-to-end experiments, use Statsig to expose only at the point of intervention. This setting has a few inputs: * A qualifying event, which Statsig joins to exposures to determine if users triggered an event * *Exclude Matching Units*: if toggled, Statsig drops units that triggered this event from the analysis. If unchecked, Statsig filters the analysis to units that triggered the event. * *Use qualifying event timestamp for first exposures*: if checked, Statsig replaces units' exposure timestamp with the qualifying event's timestamp. This is useful for small cohort windows, for example, measuring whether something happened within 10 minutes of the intervention. If the intervention occurred several minutes after the original exposure event, using the actual time the user saw the intervention can be helpful. * *Filter events by time window*: only consider qualifying events for the inclusion/exclusion filters that occurred within a certain time from the exposure. This is useful if a user may return and re-trigger the qualifying event when it is no longer relevant to the exposure. **Filter Assignment Source** This setting controls additional filters applied to the exposure data for this experiment. This can be useful to filter out bad dates with data known to be biased or non-representative, or to filter to a specific subset of interest for the scorecard results. **Default Date Filter** Statsig calculates results across multiple rollups, including Cumulative, 7 days, and 1 day. Cumulative is almost always the correct choice for this setting because it maximizes statistical power. You can select other views in the scorecard. ## Explore (custom) query settings Explore queries enable drilldown, filtering, grouping, and more advanced ways to cut metric data by user and metric properties. They provide a way to analyze experiment results in depth. Explore queries run with the same statistics as the scorecard; with no filters or other settings applied, results match the scorecard results. ## Metrics Pick the metrics to run the explore or drilldown analysis on. This can include tags and local metrics. ## Group By This setting is the primary tool for explore and custom queries. It allows drilldowns into user segments to understand differentiated performance. You can combine it with [differential impact detection](/experiments/exploring-results/differential-impact-detection) to analyze experiment results beyond topline averages. By default, Statsig shows only the top 10 dimension levels with over 100 units per segment in results. The rest are grouped into an OTHER category. This prevents inflated false positive rates and keeps statistical analysis rigorous. Contact the Statsig team in Slack if you need this limit adjusted. ## Filter Use filters to include or exclude certain cohorts of units in an explore analysis by specifying the property and a filter set. ## Time range for metric data This setting filters metric data to a specific date period, for example the three days from 2024-04-10 to 2024-04-12. This is useful for investigating data anomalies, analyzing a recent cohort when results have changed significantly, or running drill-down analyses. ## Filter by exposure date In some cases it is useful to filter to units that entered the experiment on a specific date, or to exclude them (for example, units exposed on a holiday may behave atypically). You can specify filters based on user exposure date. By switching the mode to Days Since First Exposure, you can also drill down into each cohort's behavior during a specific window after exposure. This is a useful way to understand behaviors observed in the days-since-first-exposure timeline view. ## Scheduling After running a query, you can schedule it. Scheduled queries run daily after any scheduled pulse load to update results. Scheduled queries can also be added to the experiment Summary page as part of the experiment writeup and downloadable summary. ## Interaction effect detection Explore queries can trigger [interaction effect detection](/experiments/exploring-results/interaction-detection) analyses to determine whether two experiments are interacting in a synergistic or harmful way. ================================================================================ # Configuring Experiments URL: https://docs.statsig.com/statsig-warehouse-native/features/configure-an-experiment Updated: 2026-07-11 ================================================================================ Setting up an experiment is a core flow in Statsig. In Warehouse Native, there are two modes. In one, you connect to existing assignment data from your warehouse. In the other, you configure the experiment in Statsig, use the Statsig SDK, and then analyze the resulting exposures in your warehouse. This page covers creating and configuring an experiment for analysis in Statsig. For most A/B/n experiments, the default settings produce a standard, powerful analysis. ## Creating an experiment To create an experiment, go to the experiments tab in your console and click the create button. There are two types of experiments in Statsig Warehouse Native: * Analyze: these are for 3rd-party or in-house exposure sources * Assign & Analyze: these are Statsig-configured experiments. You can set up all of the configuration here, implement it through Statsig SDKs, and track results. {% figure %} ![Experiment type selection interface](/images/statsig-warehouse-native/features/configure-an-experiment/30954be6-7c2e-48f4-8072-f196349adbc3.png) {% /figure %} Next, give the experiment a name, specify your hypothesis, and select the experiment from your exposure sources. Sync sources if your exposure isn't available in the dropdown. Specify the control group and the ID type the experiment uses. {% figure %} ![Configure Experiment](/images/statsig-warehouse-native/features/configure-an-experiment/7984bdd3-e2bf-4322-8230-18e3222077e9.png) {% /figure %} ## Choose metrics On the experiment setup page, refine your hypothesis and add the Primary and Secondary metrics for your experiment. * Primary metrics: a short list (1-3) of metrics that define your overall evaluation criteria. Typically one target metric and one first-mile metric, such as revenue and checkout clicks. * Secondary metrics: guardrail and explanatory metrics. These are observational in nature and are less conclusive. Applying Bonferroni corrections is one way to formalize this approach, though it can be overly conservative. {% figure %} ![Choose Metrics](/images/statsig-warehouse-native/features/configure-an-experiment/264101219-396f00f6-fbdc-4944-94c0-62354eaa2980.png) {% /figure %} ## Choose groups Statsig infers the groups and group splits in your experiment. Check and correct the splits to ensure SRM checks work as intended. You can also rename groups or delete irrelevant groups. {% figure %} ![Choose Groups](/images/statsig-warehouse-native/features/configure-an-experiment/33de7e4f-a0f8-4fcd-bde0-850a57055b71.png) {% /figure %} ## Advanced settings Statsig has many configurable settings for experiments. You can set defaults at the org level. Available settings include: * Frequentist vs. Bayesian analysis * Target duration of the experiment * Whether to apply [Sequential Testing](/experiments/advanced-setup/sequential-testing) adjustments * Allocation Duration * Stop enrolling new users into the experiment after X days {% callout type="note" %} You need to set up [Persistent Assignment](/client/concepts/persistent_assignment) to retain already-enrolled users to stay in the same group {% /callout %} * Cohort Duration * Specify a timeline for collecting metric data * Include only units with a completed duration, so Statsig doesn't include units with incomplete data in Pulse * Whether to allow cohort metrics to mature after experiment end {% callout type="note" %} After you configure this value, it overwrites the `only include units with a completed duration` setting above. {% /callout %} * [ID stitching](/statsig-warehouse-native/features/id-resolution) * Whether and how to apply [Bonferroni Correction](/experiments/statistical-methods/methodologies/bonferroni-correction) * Default Confidence Intervals * Default rollup windows for result readouts (cumulative, 1, 7, 14, or 28 days from the analysis date) * Turbo Mode * Skip time series calculations to reduce cost and runtime when you only need the overall effect * Filter exposures by qualifying event * Only applicable in Analyze-only experiments * Filter assignment source * Add optional filters on the experiment's assignment source. By default, the assignment source is filtered to the experiment and groups being analyzed. Use this setting to exclude known bad actors from the primary scorecard or to analyze a sub-population in the main scorecard. ## Testing in a lower environment After experiments are launched, you can't edit the groups without restarting the experiment, because Statsig has already allocated users to each group. Test each experiment in lower environments before starting. Click the **Test** button on the experiment setup page, then select **Enable for Environments**. These environments should match your [SDK environment setup](/guides/using-environments/#configuring-environments). Testing in a lower environment and using [overrides](/experiments/setup/overrides) lets you manually set your experiment group to test each variant. {% figure %} ![Experiment test button interface](/images/experiments/experiment_test_button.png) {% /figure %} When an experiment is enabled for lower environments, Warehouse Native customers can check exposure logs to troubleshoot exposures. Experiment results and cumulative exposures aren't available in lower environments. ## Start the experiment After completing setup, click **Save and Analyze** to start the experiment. You are prompted to finalize the dates, experiment status, and optionally configure a schedule to reload experiment results. {% figure %} ![Schedule](/images/statsig-warehouse-native/features/configure-an-experiment/264101192-1626481b-3c45-4622-b7c8-1e9638436a8d.png) {% /figure %} When you click **Load Data**, a Pulse analysis starts and you are taken to the results page. ## Data freshness For a Statsig-configured experiment using the Statsig SDKs to generate exposures, the default is that exposures are batched, deduplicated, and written to your warehouse once a day. When you launch an experiment, early metric data can help detect crashes or significant bugs. When Pulse is loaded shortly after the experiment starts, Statsig updates exposures in your warehouse before computing Pulse results. This allows you to see Pulse results as fresh as approximately 15 minutes, assuming events and metrics arrive at the same speed. If the number of exposures on an experiment is fewer than 1 million, Statsig performs a just-in-time update of exposures in your warehouse when Pulse is loaded. {% callout type="note" %} Statsig only writes exposure information to your warehouse that it uses for experiment analysis. Statsig doesn't write exposures from pre-production environments or overrides, because it doesn't use them in analysis. {% /callout %} ================================================================================ # Types of Experiments URL: https://docs.statsig.com/statsig-warehouse-native/features/types-of-experiments Updated: 2026-07-11 ================================================================================ ## Analysis only ### A/B/n Analysis-only A/B/n tests run analysis on top of assignment and metric data from your warehouse. In these experiments, Statsig operates as a statistics engine to help make analyses more reproducible. ## Assign and analyze ### A/B/n The most common Assign-and-Analyze experiments are A/B/n tests that integrate warehouse data with Statsig's live SDK. In these experiments, Statsig operates as an assignment tool, a real-time diagnostics tool, and as a statistics engine. Assignment related features supported include: > #### [Stratified Sampling](/experiments/advanced-setup/stratified-sampling) > > Dividing your population into homogeneous groups (based on a metric or classification). > #### Configurable Allocation Duration > > You can enroll users for a subset of an experiment's duration. If you are experimenting on a one-time experience (such as signup flows), no additional configuration is needed. If the experience isn't one-time (enrolled users need to keep being assigned), configure your SDKs to use Persistent Assignment (you provide a store to save user enrollment states; the SDK manages this state). Go to Persistent Assignment for [Client](/client/concepts/persistent_assignment) and [Server](/server/concepts/persistent_assignment) SDKs. ### Switchback experiments Statsig offers [Switchback](/experiments/types/switchback-tests) tests for experimenting in the presence of meaningful network effects or in ecosystems where changing an experience for one group affects other groups (for example, a ride-service app changing prices for some users changes driver demand for all users). You can configure Switchback tests in Statsig's console. They use time periods (and optional buckets such as city or country) to alternate experiment conditions and run a bootstrapping analysis to estimate test statistics. Advanced options include burn-in and burn-out periods, allocation windows, and configurable window lengths. ### Geo testing Statsig offers [Geotesting](/statsig-warehouse-native/geotests/introduction) to support marketing and product causal inference techniques where you can't run a traditional A/B test. Geotesting treats geographic units (such as postal codes and DMAs) as the unit of analysis, enabling new experimental methodologies like rigorous testing of paid marketing or search on platforms like Google and Facebook Ads. Geotesting uses Synthetic Control methodologies built on GeoLift, an open-source package from Meta. All existing metrics and metric sources are available, with the addition of geographic labels. ### MABs * [Autotune](/statsig-warehouse-native/features/autotune) is Statsig's multi-armed bandit solution. It balances explore and exploit to deliver the optimal global treatment to users, making it useful for evaluating many options and dynamically adjusting traffic to avoid over-delivering underperforming variants. * Statsig also offers a [Contextual Multi-Armed Bandit](/autotune/contextual/introduction), which extends the multi-armed bandit by personalizing the experience served to users based on "context" (user or event attributes provided to the Statsig SDK). This balances explore and exploit by optimizing for potential upside in its predictions. Both MAB types are invoked through Statsig's `getExperiment` call. For the CMAB approach, provide relevant attributes to the user object. Contact the Statsig support team, your sales contact, or the [Slack community](https://statsig.com/slack) for assistance with Contextual Multi-Armed Bandits. ================================================================================ # Power Analysis URL: https://docs.statsig.com/statsig-warehouse-native/features/power-analysis Updated: 2026-07-11 ================================================================================ Establishing a run time for an experiment is good practice for two reasons: * It aligns teams around timelines. * For fixed-horizon analysis, it establishes when it is valid to draw a conclusion from the results. To determine run time, establish the minimum size of impact you want to reliably detect (known as the MDE, or minimum detectable effect), then run an analysis to determine how many samples and how much time you need to achieve that MDE. ### Running a power analysis To run a power analysis in Statsig, provide two inputs: * A population * This step is important because most experiments reach only a subset of users, and those users may behave differently from the overall population. * You can base the population on an experiment you already ran, or on a Qualifying Event. * A qualifying event is an arbitrary set of historical user-timestamp pairs. For example, if you plan to expose users on a button click, provide the users who clicked that button in the preceding week. * Metrics * Enter the metrics you plan to use as your evaluation criteria. You can add multiple metrics, which is useful for analyzing which metrics will be more or less sensitive in your target population. {% figure %} ![Power Analysis UI](/images/statsig-warehouse-native/features/power-analysis/563ee87d-e16a-4649-b50a-628e32acca34.png) {% /figure %} ### Readout Statsig simulates an experiment based on your input, calculating population sizes and relative variance from historical behavior. The power analysis readout shows a week-by-week view of the experiment statistics you can expect. In the settings section, you can specify: * **Number of Experiment Groups**: The total number of groups in the experiment, including control. * **Control Group %**: The percentage of users in the control group, for example 50% if half of all users are control. * **Fixed Allocation or Fixed MDE Analysis**: The type of analysis to run. Go to [Analysis Types](/experiments/power-analysis/#analysis-types) for details. * **One-sided or Two-sided test**: The type of z-test to use for the analysis. * **Significance Level (α)** * **Power (1-β)** * **Bonferroni Correction Per Variant**: Whether to include an α correction for multiple tests in the power analysis. Updating these settings recalculates results based on the analysis that already ran. ================================================================================ # Stratified Sampling URL: https://docs.statsig.com/statsig-warehouse-native/features/stratified-sampling Updated: 2026-07-11 ================================================================================ ## What is stratified sampling Stratified sampling divides the entire population into homogeneous groups called strata (singular: stratum), then selects random samples from each stratum. For example, if you have XS and XL customers and randomize them into Control and Test groups, both groups should be balanced across XS and XL customers. You can also stratify based on a metric like Revenue/User. With large populations, randomization typically achieves this balance. In B2B scenarios and other low-volume or high-variance situations, stratified sampling ensures the balance explicitly. Statsig supports both automated and manual stratified sampling. When a small number of power users drive a large portion of an overall metric value, stratified sampling meaningfully reduces false positive rates and produces more consistent, reliable results. Statsig simulations showed approximately a 50% decrease in the variance of reported results. ## Automated stratified sampling ### How it works The Statsig SDKs use a *salt* to randomize or bucket experiment subjects ([learn more](/faq#how-does-bucketing-within-the-statsig-sdks-work)). When you enable stratified sampling, Statsig tries 100 different salts and evaluates how balanced the resulting groups are. Balance is evaluated using either a metric or an attribute you provide that describes your experiment subjects. The best salt from this set is selected and saved. [Learn more](https://statsig.com/blog/introducing-stratified-sampling). {% figure %} ![Stratified sampling algorithm diagram](/images/snippets/stats-methods/stratifiedSampling/99f72b83-9f14-45a3-aa6e-ffcbd6211ec7.png) {% /figure %} The selection space for salts is large enough that stratifying multiple experiments on the same metric doesn't result in overlap. In Statsig simulations, the groups were as independent as the literature predicts. ### Enabling stratified sampling You can enable stratified sampling under Advanced Settings on the experiment setup page. There are two ways to stratify on Statsig. If you choose a metric to stratify on, Statsig uses that metric to balance the groups. {% figure %} ![Stratified sampling metric selection interface](/images/snippets/stats-methods/stratifiedSampling/0cfc499d-4fdf-44a8-ba2a-3537ba5bb904.png) {% /figure %} If you instead choose an attribute or classification (for example, S, M, L, XL), Statsig uses that to balance the groups. * On Statsig Cloud, you'll upload a CSV (in Early Access) * On Statsig Warehouse Native, you'll use Entity Properties {% figure %} ![Entity properties configuration for stratified sampling](/images/snippets/stats-methods/stratifiedSampling/102a839f-37fd-4443-807a-4b269f137490.png) {% /figure %} After you select the Stratify button, Statsig analyzes a set of salts and picks the best one. {% figure %} ![Stratification analysis results interface](/images/snippets/stats-methods/stratifiedSampling/412f5c78-8c4f-4f16-88d3-60d3d3555ffd.png) {% /figure %} ## FAQ and best practices * **What population is used when balancing?** * When evaluating salts, Statsig computes balance using pre-experiment data for the entire targeted population of the experiment’s unit type (e.g., all `userID`s or all `customerID`s) over the selected lookback window. There is no filtering on exposure because the experiment has not started yet. * **How are new units handled after stratification?** * Units that weren't present in the pre-experiment data are still assigned deterministically by the chosen salt, i.e., effectively at random with respect to the balancing metric. They don't influence the salt selection and may introduce some drift from the initial balance. * **Should I use stratified sampling for every experiment?** * Not necessarily. It’s most useful when you expect imbalance due to heterogeneous units (e.g., “whales”) or skewed metrics. The tradeoff is time/compute cost that scales with the number of units and adds steps before starting an experiment. If you don’t expect meaningful imbalance, a standard random split is generally recommended. * **Does salt evaluation assume 100% allocation? What about running at less than 100%?** * Yes. All candidate salts are evaluated assuming 100% of the targeted population is allocated. If you then run the experiment at an allocation below 100%, random sampling of that subset can reintroduce imbalance (e.g., by chance, some high-impact units may fall disproportionately into one arm). For the period you care most about inference, prefer 100% allocation to preserve the intended balance. Lower allocations are best used briefly for safe rollouts rather than for the full experiment duration. * **Across candidate salts, is it the same set of users being evaluated?** * Yes. Candidate salts are assessed over the same targeted population; only the randomization induced by the salt changes. * **How long does stratification take?** * Duration depends on the number of units and the metric/source being queried. There is no fixed SLA; larger populations take longer. ## Manual assignment for stratified sampling When setting up an experiment, you can configure overrides (for example, force user X or Segment A into Control, force user Y or Segment B into Test). Overrides are intended for testing; overridden users are excluded from experimental analysis in Pulse results. To include manually assigned users in stratified sampling analysis, select the *Include Overrides in Pulse* checkbox. This includes overridden users in all metric lift analyses. You can assign 100% of experiment participants manually, or assign a subset manually and randomly assign the rest. {% callout type="note" %} You can add overrides for an ID type that differs from the experiment's ID type, but Statsig won't resolve those ID evaluations to the experiment's ID type and they won't contribute to Pulse results. {% /callout %} When you use the Statsig SDK for assignment, the SDK handles randomization. When you control assignment of users, you are responsible for ensuring users are balanced across experiment groups. {% figure %} ![Manual assignment override configuration](/images/snippets/stats-methods/stratifiedSampling/230964234-8cc81f66-f4f8-4f37-b6df-6d36d0d7ab98.png) {% /figure %} ## Additional reading [Morgan and Rubin 2012](https://projecteuclid.org/journals/annals-of-statistics/volume-40/issue-2/Rerandomization-to-improve-covariate-balance-in-experiments/10.1214/12-AOS1008.full) covers the history, philosophy, and proofs of re-randomization, including how re-randomization reduces the randomization variance of the difference in means. The paper notes that "standard asymptotic-based analysis procedures that do not take the re-randomization into account will be statistically conservative." Statsig remains conservative with the t-test to maintain consistent and comparable results across methods. [Lin & Ding 2019](https://arxiv.org/abs/1906.11291) is additional reference reading on this topic. ================================================================================ # Differential Impact Detection URL: https://docs.statsig.com/statsig-warehouse-native/features/differential-impact Updated: 2026-07-11 ================================================================================ {% partial file="whn/differentialImpact.mdx" /%} ================================================================================ # Targeting URL: https://docs.statsig.com/statsig-warehouse-native/features/targeting Updated: 2026-07-11 ================================================================================ ## How to target experiments Experiments integrate natively with Statsig's Feature Gates product to target interventions. Feature gates provide a rich language for targeting users by properties or segments. You can give an experiment a targeting gate to restrict an intervention to units that pass that gate. Go to [Feature Gates](/feature-flags/overview) for more information. ## When to target an experiment Targeting an experiment makes sense when * You want to gradually release the experiment to tiers of users * Part of your hypothesis is that the experiment intervention will only work for a target subset of users, e.g. mobile users ================================================================================ # ID Resolution (ID Stitching) URL: https://docs.statsig.com/statsig-warehouse-native/features/id-resolution Updated: 2026-07-11 ================================================================================ Statsig Warehouse Native supports resolving multiple IDs to one identified user, allowing you to expose an experiment on one identifier and analyze data from one or more mapped identities associated with that experimental unit. Common scenarios include: - Exposing logged-out users and analyzing logged-in metrics like revenue, or a funnel from a logged-out marketing page landing to a logged-in subscription purchase. - Using one-to-many relationships, for example when a single user owns multiple accounts. ID resolution lets you aggregate metrics from the user's mapped accounts. This approach reduces statistical power but is statistically sound. ID resolution is a common need in experimentation. Without it, the responsibility for identity mapping typically falls on data users or PMs, which leads to inconsistent results and expensive query logic. Advanced ID Resolution centralizes this process, making it consistent and performant, and allowing all users to reference trusted identity tables. ## The Challenge: Connecting User Identifiers A common challenge in experimentation is linking user identifiers before and after an event boundary, most often signups. Experimenters typically have a logged-out ID (for example, a cookie or Statsig stableID) and, for users who sign up, a userID created afterward. Because business metrics are typically computed at the userID level, teams often want to randomize on logged-out identifiers but measure outcomes on logged-in metrics like revenue or LTV. Most platforms require manual joins or preprocessing to connect these identifiers, leading to complex, error-prone queries that must reconcile exposures across time and mapping tables. Statsig Warehouse Native provides an automatic, no-code way to connect identifiers across these boundaries. The approach is centralized, consistent, and reproducible. {% figure %} ![Docs_IDresolution_TheProblem_111224b](/images/statsig-warehouse-native/features/id-resolution/05cb4f49-14a2-4b51-a0e0-63f127838ce4.png) {% /figure %} ## Mapping Modes When using ID resolution, you can choose from one of three modes: - Strict 1:1 mapping enforces that identities have a singular mapping. If you have a mapping between two IDs that are always 1:1, this mode enforces that the mapping is singular and warns you if there is data where that isn't the case. Users with a single identity can use downstream metrics from the secondary identity. Statsig considers multi-mapped users corrupted and discards them from the analysis. - First-touch mapping attributes activities of secondary IDs to one primary ID based on the first time the user is exposed to the experiment. - Last-touch mapping attributes activities of secondary IDs to one primary ID based on the most recent time the user is exposed to the experiment. ### Strict 1:1 Mapping {% figure %} <img src="/images/whn/Docs_IDresolution_Enforced1to1Mapping_111324.gif" alt="Enforced1to1Mapping" width="50%" /> {% /figure %} Statsig collects all potential mappings between identifiers within the experiment date range, on the exposed population. If the primary ID has multiple secondary IDs, or vice versa, Statsig considers the record polluted and drops it from the analysis. Choosing this mode changes the exposures on the primary ID because it disqualifies any records outside of a 1:1 mapping. ### First Touch Mapping (Mixed Population) {% figure %} <img src="/images/whn/Docs_IDresolution_FirstTouchAttribution_111324.gif" alt="Enforced1to1Mapping" width="50%" /> {% /figure %} The direction of first-touch mapping is based on the experiment; all secondary IDs resolve to 1 primary ID, and a single primary ID can have multiple mapped secondary IDs. Statsig attributes data to the group of the first associated primary ID seen in the exposure. If a secondary ID has multiple associated primary IDs, Statsig uses the group of the first primary ID. Users that cross groups aren't discarded from analysis; instead, Statsig assigns them based on their first experience. Statsig drops primary ID records that are associated with another primary ID but are not the first observed records from the analysis. If a user is exposed twice on different primary IDs that resolve to the same secondary IDs, Statsig keeps only the primary ID metrics from the first-exposed user in the analysis. ### Last Touch Mapping (Mixed Population) {% figure %} <img src="/images/whn/Docs_IDresolution_LastTouchAttribution.gif" alt="LastTouchAttribution" width="50%" /> {% /figure %} Same as first touch but Statsig attributes data to the most recent primary ID. ### What mixed population means {% figure %} <img src="/images/whn/Docs_IDresolution_Primary_Population.gif" alt="MixedPopulation" width="50%" /> {% /figure %} Both first-touch and last-touch mapping show pulse results based on a mixed population. Each metric is based on the corresponding population for its unit type. For example, if an experiment randomizes on Stable ID and the scorecard metrics include both Stable ID and User ID metrics, Pulse uses the raw exposure population for Stable ID metrics (to stay true to the randomization process) and the resolved population for User ID metrics, depending on the mapping mode. ### Explanation of Methodology 1. Statsig prefers primary IDs over secondary IDs if present in the data. The unit type of analysis should match that of randomization. Statsig always prefers the primary ID when it is present in the data. If the primary ID is not present, Statsig uses the most recent secondary ID. 2. Secondary IDs are only used to join metrics to exposures, but the unit of analysis is still the primary ID. Statsig calculates the unit counts of each metric's results using the primary ID. 3. Statsig handles many (primary) to one (secondary) mapping by attributing the secondary ID to **ONE** primary ID. 4. One (primary) to many (secondary) mapping is implicitly handled by treating all secondary IDs as the same unit. e.g. The metric value is added in a sum metric or counted in a count metric. 5. Statsig supports a mixture of primary and secondary IDs in the same experiment. You can use both primary and secondary IDs in the same experiment. For example, when you run a signup experiment, you can measure session-level metrics for the primary ID and user-level metrics for the secondary ID. To do this, Statsig maintains two populations: one for the primary ID and one for the secondary ID. The primary ID population is the same as if you had only used the primary ID. ## How to Enable ID Resolution in a Statsig Experiment To set up identity resolution in Statsig, either log or join data to provide both IDs on your assignment source, or provide one ID in the assignment source along with a mapping table in the form of an Entity Property Source. ### Using Property Source To use Identity Resolution across experiments in your project, you need a lookup table that has both the ID you are exposing on and the selected target ID. Configure this table by setting up an Entity Property Source with both IDs present. After that is done, select this source when configuring your secondary ID type, and Statsig handles the join for you. {% figure %} ![ID resolution source configuration interface](/images/statsig-warehouse-native/features/id-resolution/3fc0422d-ed96-4fe6-9e52-05e24a6cc2a2.png) {% /figure %} If you want to use a Statsig SDK to populate this table, you can log an event (for example, a "Signup" event) that has both the logged-out identifier and the user ID on the same event. Statsig writes events sent through the SDK into your warehouse, and you can configure an Identity Resolution source on top of that: {% figure %} ![Identity resolution configuration interface](/images/statsig-warehouse-native/features/id-resolution/6b2a3d0e-a1ad-446b-a604-43dd050f05fa.png) {% /figure %} ### Using Assignment Source When creating an assignment source, provide a column for both ID types. The Primary ID is expected to be non-null for exposure records. The secondary ID can be null. If the secondary ID is sparse (some records are null due to logging), Statsig back-attributes any identified secondary ID to other records from the same primary ID. {% figure %} ![ID Resolution Assignment Source](/images/statsig-warehouse-native/features/id-resolution/8cbdd8cc-2ea6-4bf8-a620-0428051989d1.png) {% /figure %} When you create an analysis-only experiment or power analysis with this ID type, you can optionally select a Secondary ID. If you do so, you can now use metrics from either ID type in your analysis. For E2E experiments that use the Statsig SDK, this is configurable on the experiment setup page, under Advanced settings. Internally: - For metric sources with the primary ID, Statsig joins metrics to exposures based on that primary ID. - For metric sources with only the secondary ID, Statsig joins metrics to exposures based on that secondary ID. - In strict mode, Statsig drops users with a duplicate mapping from analysis. In first-touch mode, units use their first exposure record and merge data from all mapped secondary IDs. This works natively across Metric Sources, so you can set up funnel or ratio metrics across the two ID types. Analysis uses the primary ID. This process associates metric values from the secondary ID with the corresponding primary ID records. ### Mapping Changes If you change the entity property source or assignment source's definition or underlying data, Statsig reflects those changes on the next reload. This is **why** a full reload is required, since otherwise historical changes to the mapping can lead to inconsistent data on incremental reloads or explore queries. ### Best Practices Statsig recommends using an [Entity Property Source](/statsig-warehouse-native/configuration/entity-properties) to provide a cleaned unit mapping from your warehouse. You can also provide mappings on your exposure source by logging multiple identifiers in the exposure data. Statsig uses all available identifiers to match across records. For both modes, an experiment can only have one mapped ID type, for example secondary_id->user_id or secondary_id->account_id, but not both. All modes require a full reload to prevent data inconsistency when historical mappings are changed or new mappings are introduced. Statsig filters the property source or assignment source used to provide mappings to records within the experiment's date range. If a mapping is "evergreen", or not scoped to a specific time period, you can omit the timestamp on the entity property source. #### Example of a supported schema if your assignment source data contains:<br />`{stableID: 'unknown_123', exp_id: 'PDP Test', test_group: 'Control'}` and your metric sources contain data that represents a metric as:<br />`{userID: 'known_abc', event: 'page_load'}` Your Entity Source or Assignment source must contain the secondary identity (in this case, `userID`) that enables Statsig to join your assignment data with your metric data:<br /> `{stableID: 'unknown_123', userID: 'known_abc', country: 'USA'}` ## Considerations Deduplicating records can lead to biased results, so Statsig performs two extra health checks on this kind of experiment. - Statsig checks your deduplication rate and warns you if it is unusually high. Some secondary IDs are expected to have multiple logged-out IDs due to users using different devices or clearing browser history. - Statsig performs a chi-squared test to evaluate whether the deduplication rate is identical across arms of the experiment. In some cases, an experiment may cause more users to return (for example, an email re-engagement campaign), in which case duplicates are expected to be more frequent in that arm and can be a positive outcome. In this case, you can use first-touch attribution to maintain a common identifier. Statsig supports breakdowns of metric dimensions for experiment results only on properties associated with primary ID types. Statsig doesn't support secondary ID type dimension breakdown for experiment results and custom queries due to high risk of post-exposure data leaking into the group-by's or filters. ================================================================================ # ID Resolution (ID Stitching) URL: https://docs.statsig.com/statsig-warehouse-native/features/id-resolution-legacy Updated: 2026-07-11 ================================================================================ At the earliest, Statsig will update its ID resolution methodology to reflect mixed population on November 15th. ## Mapping Modes When using Advanced ID resolution, you can choose between modes: * Strict 1:1 mapping enforces that identities have a singular mapping. If you have a mapping between two IDs that are always 1:1, this mode enforces that the mapping is singular and warns you if there is data where that isn't the case. Users with a single identity can use downstream metrics from the secondary identity. Statsig considers multi-mapped users corrupted and discards them from the analysis. * First-touch mapping applies when units might have multiple mappings in either direction. For example, a single user may have multiple "profiles", or someone may have logged into the same account from several devices or web sessions. In this case, units use the experiment group of their first exposure for analysis and aggregate metrics from all of their associated secondary IDs. | Strict 1:1 Mapping | First Touch Mapping | | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- | | ![Enforced1to1Mapping](/images/whn/Docs_IDresolution_Enforced1to1Mapping_111324.gif) | ![FirstTouchAttribution](/images/whn/Docs_IDresolution_FirstTouchAttribution_111324.gif) | ### Strict 1:1 Mapping Statsig collects all potential mappings between identifiers within the experiment date range, on the exposed population. If the primary ID has multiple secondary IDs, or vice versa, Statsig considers it polluted and drops it from the analysis. ### First Touch Mapping The direction of first-touch mapping is based on the experiment; all secondary IDs resolve to 1 primary ID, and a single primary ID can have multiple mapped secondary IDs. If your aim is to only have one secondary ID, you can manage that logic inside the entity property source today, but feel free to reach out to support if there's specific logic you would like to request. Statsig attributes data to the group of the first associated primary ID seen in the exposure. If a secondary ID has multiple associated primary IDs, Statsig uses the group of the first primary ID. Users that cross groups aren't discarded from analysis; instead, Statsig assigns them based on their first experience. Statsig drops primary ID records that are associated with another Primary ID but are not the first observed records from the analysis. If a user is exposed twice on different primary IDs that resolve to the same secondary IDs, Statsig keeps only the primary ID metrics from the first-exposed user in the analysis. ### Last Touch Mapping Same as first touch but Statsig attributes data to the most recent primary ID. ### Note on ID stitching Multiple secondary IDs attached to one primary ID still count as "one" experimental primary ID; Statsig merges the metric values across records from the different secondary IDs, e.g. added in a sum metric or counted in a count metric. {% callout type="info" %} Statsig is interested in supporting more complex 1-to-many relationships of identities and is eager to partner with customers to develop these capabilities if a more advanced use-case is required. {% /callout %} ## How it Works To set up identity resolution in Statsig, either log or join data to provide both IDs on your assignment source, or provide one ID in the assignment source along with a mapping table in the form of an Entity Property Source. ### Using Property Source To use Identity Resolution across experiments in your project, you need a lookup table that has both the ID you are exposing on and the selected target ID. Configure this table by setting up an Entity Property Source with both IDs present. After that is done, select this source when configuring your secondary ID type, and Statsig handles the join for you. {% figure %} ![ID resolution source configuration interface](/images/statsig-warehouse-native/features/id-resolution/3fc0422d-ed96-4fe6-9e52-05e24a6cc2a2.png) {% /figure %} If you want to use a Statsig SDK to populate this table, you can log an event (for example, a "Signup" event) that has both the logged-out identifier and the user ID on the same event. Statsig writes events sent through the SDK into your warehouse, and you can configure an Identity Resolution source on top of that: {% figure %} ![Identity resolution configuration interface](/images/statsig-warehouse-native/features/id-resolution/6b2a3d0e-a1ad-446b-a604-43dd050f05fa.png) {% /figure %} ### Using Assignment Source When creating an assignment source, provide a column for both ID types. The Primary ID is expected to be non-null for exposure records. Your secondary ID can be null. If your secondary ID is sparse (some records are null, and some are not due to logging), Statsig back-attributes any identified secondary ID to other records from the same Primary ID. {% figure %} ![ID Resolution Assignment Source](/images/statsig-warehouse-native/features/id-resolution/8cbdd8cc-2ea6-4bf8-a620-0428051989d1.png) {% /figure %} When you create an analysis-only experiment or power analysis with this ID type, you can optionally select a Secondary ID. If you do so, you can now use metrics from either ID type in your analysis. For E2E experiments that use the Statsig SDK, this is configurable on the experiment setup page, under Advanced settings. Internally: * For metric sources with the primary ID, Statsig joins metrics to exposures based on that primary ID. * For metric sources with only the secondary ID, Statsig joins metrics to exposures based on that secondary ID. * In strict mode, Statsig drops users with a duplicate mapping from analysis. In first-touch mode, units use their first exposure record and merge data from all mapped secondary IDs. This works natively across Metric Sources, so you can set up funnel or ratio metrics across the two ID types. Analysis uses the primary ID. This process associates metric values from the secondary ID with the corresponding primary ID records. ### Mapping Changes If you change the entity property source or assignment source's definition or underlying data, Statsig reflects those changes on the next reload. This is **why** a full reload is required, since otherwise historical changes to the mapping can lead to inconsistent data on incremental reloads or explore queries. ### Best Practices Statsig recommends using an [Entity Property Source](/statsig-warehouse-native/configuration/entity-properties) to provide a cleaned unit mapping from your warehouse. You can also provide mappings on your exposure source by logging multiple identifiers in the exposure data. Statsig uses all available identifiers to match across records. For both modes, an experiment can only have one mapped ID type, for example secondary\_id->user\_id or secondary\_id->account\_id, but not both. All modes require a full reload to prevent data inconsistency when historical mappings are changed or new mappings are introduced. Statsig filters the property source or assignment source used to provide mappings to records within the experiment's date range. If a mapping is "evergreen", or not scoped to a specific time period, you can omit the timestamp on the entity property source. #### Example of a supported schema if your assignment source data contains:<br />`{stableID: 'unknown_123', exp_id: 'PDP Test', test_group: 'Control'}` and your metric sources contain data that represents a metric as:<br />`{userID: 'known_abc', event: 'page_load'}` Your Entity Source or Assignment source must contain the secondary identity (in this case, `userID`) that enables Statsig to join your assignment data with your metric data:<br /> `{stableID: 'unknown_123', userID: 'known_abc', country: 'USA'}` ## Considerations Deduplicating records can lead to biased results, so Statsig performs two extra health checks on this kind of experiment. * Statsig checks your deduplication rate and warns you if it is unusually high. Some secondary IDs are expected to have multiple logged-out IDs due to users using different devices or clearing browser history. * Statsig performs a chi-squared test to evaluate whether the deduplication rate is identical across arms of the experiment. In some cases, an experiment may cause more users to return (for example, an email re-engagement campaign), in which case duplicates are expected to be more frequent in that arm and can be a positive outcome. In this case, you can use first-touch attribution to maintain a common identifier. ================================================================================ # Filter Exposures URL: https://docs.statsig.com/statsig-warehouse-native/features/filtering-exposures Updated: 2026-07-11 ================================================================================ ## Filter Exposures by Qualifying Event You can use Qualifying Events to filter exposures to units that did or did not trigger an event after being exposed. This is useful for analysis-only experiments where the assignment tool may have over-exposed units (for example, assigning units on page load when the intervention was only triggered when a button was clicked). {% figure %} ![Qualifying event filter configuration interface](/images/statsig-warehouse-native/features/filtering-exposures/f7a5ee06-b67a-4cba-9680-fbe99c64d0fc.png) {% /figure %} Use this tool with caution. It is possible to introduce post-assignment data into your assignment data, biasing results. For this reason, Statsig disables qualifying event filters by default on Assign and Analyze experiments, because the Statsig SDK typically doesn't over-expose units. Contact the Statsig team to enable this feature if you have a use case for it. The available settings are: - Qualifying Event: the event source to qualify exposures with. You can filter this to pick specific target events within a qualifying event source. - Exclude matching units: whether to include or exclude units that triggered the event. - Use qualifying event timestamp for first exposures: if the actual intervention occurred when the unit triggered the qualifying event, enable this setting so that Statsig overrides the exposure timestamp with the qualifying event timestamp. - Filter events by time window: restrict qualifying event matching to events that occurred within X days or minutes of the unit's exposure event. ## Filter Assignment Source You can also filter exposures to units based on columns in the assignment source. You can use certain columns directly and apply additional filters on top. As with filtering by qualifying event, use this tool carefully because it can artificially introduce bias to experiment results. {% figure %} <img src="/images/statsig-warehouse-native/features/filtering-exposures/36ddb74f-d9e9-4e25-8349-61077f77b863.png" alt="Assignment source filter selection UI" width="1169" /> {% /figure %} ================================================================================ # Normalized Metrics (aka Clustered Experiments) URL: https://docs.statsig.com/statsig-warehouse-native/metrics/normalized-metrics Updated: 2026-07-11 ================================================================================ ## When to use normalized metrics In standard A/B tests, the unit of randomization (for example, UserID) matches the unit of analysis. In some cases it is useful for these to be different. The most common example is B2B experiments where you want to randomize by BusinessID but measure by average metric per UserID, not average metric per BusinessID. For example, if you add image support to a collaborative commenting feature and want to A/B test it before rollout, you randomize using BusinessID. You can't randomize by UserID, because everyone within a single business must have the same treatment. If you compared comments per BusinessID, the data would be skewed by large companies. A business with 1000 employees and 10 comments would contribute more weight than a business with 5 employees and 5 comments. Normalizing the metric by users exposed to the experiment corrects for this: if 1000 and 5 users were exposed from each business, the first business would have a comments/user rate of 0.01 and the second would have a rate of 1, making the comparison valid across companies of different sizes. ## What normalized metrics do Internally, normalizing a metric creates a ratio metric. The numerator is the metric being normalized. The denominator is a COUNT DISTINCT of the UnitID being normalized to. To create this ratio metric yourself and use it in experiments, refer to the [Cluster Experiments](/metrics/different-id) guide. ## How to create normalized metrics Create normalized metrics when adding metrics to an experiment. ![Experiment metrics configuration interface](/images/statsig-warehouse-native/metrics/normalized-metrics/a440ccfa-0271-4fbd-ba9d-7bb858f3e180.png) Select the unitID to normalize by on the next screen. ## Where to use normalized metrics Normalized metrics are available anywhere you can run a ratio metric and a count-distinct metric. ================================================================================ # Experiment Quality Score URL: https://docs.statsig.com/statsig-warehouse-native/features/quality-score Updated: 2026-07-11 ================================================================================ ## How Experiment Quality Score works The Experiment Quality Score is a metric that gives a quick view of the quality and trustworthiness of an experiment configured in Statsig. The score helps experimenters and their peers across an organization identify potential issues in experiment setup, execution, and data collection, supporting more confident decision-making. Measuring this score across all experiments can help teams discover systematic issues in their program and identify opportunities to improve their experimentation program over time. ## Configure Experiment Quality Score Enable Experiment Quality Score in the project settings under *Settings > Experimentation > Experiment Quality Score*. The feature uses a list of pre-defined assessment criteria. You can customize the weight of each criterion based on your organization's needs, though Statsig provides default values. {% figure %} ![Experiment quality score configuration interface](/images/snippets/quality-score/91fea3df-0dae-4d0a-ada8-1c1c9313e60e.png) {% /figure %} ## Advanced configuration using the console API For teams that need additional checks, have different requirements across product teams, or need different thresholds, Statsig supports advanced configuration through the console API. For example, you can require hypotheses to be at least 200 characters and contain a link to an external planning document. To configure this, run a POST or PATCH on the `console/v1/experiments` endpoint to update individual scores on any given experiment. Targeting the existing set of scores lets you override weights (usually to 0), so the list contains only the custom set needed. For example, running patch on an experiment with this payload: ``` { "manualQualityScores": [ { "criteriaName": "HYPOTHESIS_LENGTH", "criteriaDescription": "Check passed", "status": "PASSED", "score": 0, "weight": 0 }, { "criteriaName": "MyCompany\'s Hypothesis Check", "criteriaDescription": "Has Internal URL and > 200 Chars", "status": "PASSED", "score": 100, "weight": 100 }, { "criteriaName": "Naming", "criteriaDescription": "Experiment prefixed with team name", "status": "FAILED", "score": 0, "weight": 100 } ] } ``` Would: * Drop the original HYPOTHESIS\_LENGTH check * Keep the other original checks, with their weights * Add a new check, `MyCompany's Hypothesis Check`, for custom logic on the hypothesis * Add a new check, `Naming`, for custom logic on the name The other weights are normalized. If the original HYPOTHESIS\_LENGTH had a weight of 10, the total weight becomes 290 and scores are normalized accordingly. If all non-custom checks are passing, the score would be 190/290 or ~66%. The general flow for using this approach is: * Use Console API's `experiments/get` to pull all experiments * For each experiment: * Run custom logic * Patch results ## Calculation notes Statsig skips checks in an unready state during evaluation and renormalizes the other weights to 100%. For example, if the experiment hasn't started, the `Balanced Exposures` component is in an unready state and Statsig ignores it. Checks with a weight of 0 are omitted entirely from the card. ## View quality scores When enabled, quality scores appear in the details tab of an experiment. Statsig evaluates applicable checks and contributes them to the number shown. The score is color-coded based on the percentage threshold: * >= 85% corresponds to passing/green * >= 50% corresponds to warning/yellow * < 50% corresponds to error/red {% figure %} ![Experiment quality score display with color-coded status](/images/snippets/quality-score/d7483b96-8077-419d-9e23-a3e648b7e066.png) {% /figure %} Quality scores are also available through the console API, which lets you retrieve the data in bulk for analysis. ================================================================================ # Experiment Diagnostics URL: https://docs.statsig.com/statsig-warehouse-native/features/monitor-an-experiment Updated: 2026-07-11 ================================================================================ After you turn on your experiment, you can monitor its health and view the exposures for the control and variant groups. ### Monitoring experiment health To monitor the status of your experiment, * Navigate to **Experiments** in the left-hand navigation panel in the Statsig console * Select the experiment you want to monitor * **Experiment Health Checks** show alerts for problems with the experiment setup. Hover over the icon and click on a check for more details. {% figure %} ![Diagnostic health check image](/images/statsig-warehouse-native/features/monitor-an-experiment/8b557e40-2473-45e1-ac0c-bd61a2eb5208.png) {% /figure %} * **Pulse metrics available** monitors availability of Pulse results, which Statsig expects the day after the experiment starts if your metrics have landed. * **Exposures are balanced** checks that the number of units exposed in each group matches the expected allocation. The Sample Ratio Mismatch (SRM) check uses a Chi-Squared test of independence. Experiments can temporarily show small imbalances due to the randomness of user assignment; these typically resolve after a couple of days. True SRM imbalances that originate from systematic assignment or logging problems tend to persist over time. The following thresholds apply to SRM alerts: * **p-value between 0.001 and 0.01**: Warning (yellow) for possible imbalance. The p-value isn't low enough to confirm a real imbalance. Wait and check again the next day. * **p-value \< 0.01 and group size differs from expected size by less than 0.1% absolute**: Warning (yellow) indicating that an imbalance is possible, but the impact to the experiment is expected to be small. This scenario typically occurs in large web experiments (1M+ users) where small variations in performance across groups can cause a small fraction of exposures to be dropped for certain groups more than others. * **p-value \< 0.001 and more than 0.1% absolute deviation from expected group size**: Alert (red) meaning there is likely a problem with the experiment exposures and experiment results may not be trustworthy. * **Crossover users** monitors the percentage of units exposed to more than one experiment group. Statsig discards crossover users from the experiment analysis, which may reduce your sample size and statistical power. The following alerts apply based on the threshold for that experiment type: * **Warning (yellow)**: The presence of a number of crossover users has been detected, but the impact on the experiment is expected to be relatively small. * Assign & Analyze: **0.1% to 1%** * Analyze Only: **1% to 10%** * **Alert (red)**: There are a substantial number of crossover users, which can indicate a problem with your experiment setup. * Assign & Analyze: **Over 1%** * Analyze Only: **Over 10%** * **User metrics were computed** confirms that Statsig was able to join exposure data to metric data and produce experiment results. * **Metrics Available for Topline Impact** checks whether there is sufficient metric data to calculate topline impacts. Go to [topline impact](/experiments/statistical-methods/topline-impact) for details. * **Differential Impact Detection completed** checks whether the differential impact detection has finished and flags extreme differential impacts for any sub-population per your configuration. Go to [differential impact detection](/experiments/exploring-results/differential-impact-detection) for details. Some health checks apply only to end-to-end Statsig experiments where the Statsig SDK handles assignment; others apply only to analysis experiments where assignment is handled outside of Statsig. * **Checks started** (end-to-end only) verifies that config checks are occurring. Available shortly after the experiment starts. * **Checks have valid unit type** (end-to-end only) ensures that config checks contain the unit ID type selected for this experiment (user ID by default). Available when checks begin. * **Exposures found** (analysis only) verifies that exposures for this experiment are provided, and is populated after pulse is loaded. Some health checks apply to experiments where ID types are stitched: for example, a sign-up flow experiment where stitching Stable ID to User ID enables analysis before and after a user is logged in. Duplicate mapped IDs occur when the same primary ID is associated with multiple secondary IDs or vice versa. This happens when the primary identifier is reset, or when there are multiple conversions from the same primary identifier. Deduplication can reduce your sample size and may affect the overall magnitude of your results. * **Deduplication Rate Check** reports the percentage of exposures which have duplicate mapped ids across primary and secondary IDs. The following thresholds are used for displaying deduplication rate alerts: * **Deduplication rate between 1% and 5%** Warning (yellow) indicating that deduplications have been applied; affected units are excluded from analysis, which reduces the sample size and may affect the overall magnitude of results. * **Deduplication rate greater than 5%** Alert (red) indicating that a substantial number of deduplications have been applied and excluded from analysis. This can indicate a problem with the experiment setup or a 1-many mapping between the two IDs. :::note Statsig supports 1-many and many-many mapping in Advanced ID Resolution, in addition to 1-1 mapping. Go to [Advanced ID Resolution](/statsig-warehouse-native/features/id-resolution) for details. * **Deduplication Bias Check** reports the percentage of exposures which have duplicate mapped ids across primary and secondary IDs in each group and performs a Chi-Squared test of independence. The following thresholds are used for displaying deduplication bias alerts: * **p-value between 0.001 and 0.01**: Warning (yellow) indicating possible bias. The p-value isn't low enough to confirm a real imbalance in deduplication rate between groups. Wait and check again the next day. * **p-value \< 0.001**: Alert (red) indicating that Statsig is likely applying deduplication more to one treatment group, and experiment results may not be trustworthy. * **Pre-experimental Bias Check** In some cases, users in two experiment groups have meaningfully different average behaviors before the experiment applies any intervention. If this difference persists after the experiment starts, the analysis may attribute that pre-existing difference to the intervention. CUPED helps address this bias but can't fully account for it. [Learn more](/experiments/statistical-methods/pre-experiment-bias). * **Outlier Check** In some cases, metrics can be materially influenced by a few outliers. This check alerts you so you can evaluate whether tools like Winsorization or Capping are appropriate. It also verifies that capping isn't unduly influencing results if already applied. ================================================================================ # Loading Pulse URL: https://docs.statsig.com/statsig-warehouse-native/features/reloads Updated: 2026-07-11 ================================================================================ ## How the Pulse Engine works Statsig's experimentation engine runs the setup, diagnostics, and transformations required to generate the data points that power statistical experiment analysis. In Warehouse Native, this consists of a series of query jobs (referred to as a DAG, or Directed Acyclic Graph) that take data from your sources and metric configurations to a final result set. You can find reload controls on the pulse results page: {% figure %} ![Pulse Load Controls](/images/statsig-warehouse-native/features/reloads/f6c1aa47-8d6e-4b21-96ac-f2281d0d0491.png) {% /figure %} ### Types of reloads Statsig offers a number of ways to reload data: * [Full Reloads](/statsig-warehouse-native/features/full-reloads) completely restate your experiment data. This can be useful if your underlying data changes a lot (e.g. a full DBT reload) day-to-day, and you want to ensure your analysis matches your raw data. * [Incremental Reloads](/statsig-warehouse-native/features/incremental-reloads) update your data from the last load to the current date. Running daily incremental reloads is the recommended way to keep your data current without using unnecessary compute resources to recalculate data that hasn't changed. * [Metric Reloads](/statsig-warehouse-native/features/metric-reloads) are a useful feature for when you want to add a metric to an analysis, or when a metric definition has changed. This does an efficient spot replacement of the data for a single metric or set of metrics. You can [schedule](/statsig-warehouse-native/connecting-your-warehouse/scheduled-reloads/) full and incremental loads to get fresh results each day. ### Transparency For every load, Statsig logs the compute time and jobs, cost, and queries associated with the reload. This information is visible in your console and helps you understand what is taking time or delaying results. ### Efficient reloads Statsig optimizes the queries running in your warehouse. In head-to-head evaluations, customers report that Statsig uses significantly fewer resources than comparable platforms. Statsig also offers turbo mode, which skips some enrichment calculations (in particular some time series rollups) to compute the latest snapshot of your data at lower cost. Using turbo mode, customers have run experiments on 150+ million users in less than 5 minutes on a Snowflake S cluster. ### Cleaning up storage Statsig automatically cleans up explore datasets, power analyses, and stratification artifacts. After you make a decision on an experiment, you can choose to delete the staging datasets, the result datasets, or both. You can also return to the experiment later and clean up from the experiment menu. ================================================================================ # Turbo Mode URL: https://docs.statsig.com/statsig-warehouse-native/features/turbo Updated: 2026-07-11 ================================================================================ By default, Statsig and other warehouse-native platforms calculate cumulative results for every day in an experiment. This lets users review a rich historical view or make changes and see how those changes would have affected an analysis on previous days. This is a heavily optimized flow, but it does require additional compute. Turbo Mode was built to give customers control of the tradeoff between compute cost and historical tracking, particularly for large experiments. ## How to use turbo mode When loading Pulse, there is a checkbox to enable Turbo Mode. Subsequent scheduled loads use the last setting. Turbo Mode changes the shape of the underlying data, so switching back to standard analysis requires a full reload with Turbo Mode disabled. There is also a project-level setting for Turbo Mode being on or off by default in a project's experimentation settings. ## What Turbo Mode removes Turbo Mode removes two features: the ability to use the date picker to view Pulse results as of a historical date, and the cumulative timeseries. Both features are available when an experiment is loaded daily using incremental reloads. However, they may miss dates if there are gaps in the loading schedule, and they don't retroactively update when you run a full reload. {% figure %} ![Cumulative Timeseries](/images/whn/cumulative_timeseries.png) {% /figure %} ## What Turbo Mode keeps Turbo mode and standard loads both calculate identical pulse results (including CUPED and other advanced techniques) for the latest day of the experiment analysis, and Turbo mode still calculates the "daily" and "days since exposure" timeseries for diagnosis, as well as pre-experiment bias checks. ## What to expect Results vary depending on experiment settings, but Turbo jobs on large, long-running experiments typically show a 40-80% reduction in load time and compute cost. This estimate is likely biased and treat it as approximate, because the observation comes from cases where standard loads were slow and Turbo Mode was applied in response. There is no good counterfactual for companies that use Turbo Mode by default. ## When to use Turbo Turbo Mode is more effective for long-running experiments using full reloads, because without Turbo Mode the job must recreate the entire history of "user state" on each day. Turbo Mode calculates statistics only for the latest snapshot, skipping many calculations. Turbo Mode is also more effective for experiments with an unusually large number of users or metrics, because it reduces memory requirements and prevents the warehouse from spilling to disk. Disk spill dramatically slows jobs and increases cost. Holdouts benefit from Turbo Mode because they typically have a large battery of metrics, expose a large portion of a project's users, and commonly run for 3-6 months. Turbo Mode is less effective for experiments with scheduled incremental reloads or for smaller experiments. ================================================================================ # Full Reloads URL: https://docs.statsig.com/statsig-warehouse-native/features/full-reloads Updated: 2026-07-11 ================================================================================ Full reloads completely wipe the Staging/Results Datasets Statsig has used for previous pulse calculations, and Statsig recalculates results from scratch. Generally, use full reloads for * Initial or historical pulse loads. * Cases where data has been lost or dropped on the customer side, meaning incremental reloads have lost state. * Cases where data changes frequently, e.g. a DBT full reload changes historical data due to chargebacks, model changes, or other reasons. * There's complex data dependencies, and a team wants to ensure that the gap between Statsig and Internal Systems doesn't cause inconsistencies. ================================================================================ # Incremental Reloads URL: https://docs.statsig.com/statsig-warehouse-native/features/incremental-reloads Updated: 2026-07-11 ================================================================================ Incremental reloads save state from the last load, and load from the latest data read with a small buffer to ensure completeness. This job wipes data since the last load, plus that buffer, and then appends all new data to the staging datasets before calculating results for changed days. This is the recommended way to load active experiments, and works best for ongoing, daily loads, especially when datasets are large. ================================================================================ # Metric Reloads URL: https://docs.statsig.com/statsig-warehouse-native/features/metric-reloads Updated: 2026-07-11 ================================================================================ Metric reloads drop all data from staging pipelines associated with a metric and restate that data from scratch. Where this data is interconnected (for example, ratios and funnels), related entities are updated as well. This saves time when you need to add a new metric to an experiment or a metric definition has changed, because you can avoid reloading unrelated experiment metrics. ================================================================================ # Scheduled Reloads URL: https://docs.statsig.com/statsig-warehouse-native/connecting-your-warehouse/scheduled-reloads Updated: 2026-07-11 ================================================================================ ## How scheduled reloads work You can control daily reload settings for Metrics and Experiments. Configure these settings on each entity, or set project-level defaults that entities inherit from in your Project Settings. You can also use the Console API to [trigger experiment result loads](/console-api/introduction#post-/experiments/-experiment_id-/load_pulse). This is useful for triggering refreshes when your data pipelines are ready. {% figure %} <img src="/images/statsig-warehouse-native/connecting-your-warehouse/scheduled-reloads/d056be8d-19a0-43ae-91e1-cf5e16d23b53.png" alt="Project-level scheduled reload settings interface" width="1443" /> {% /figure %} {% figure %} ![Individual experiment pulse scheduling interface](/images/statsig-warehouse-native/connecting-your-warehouse/scheduled-reloads/efc232e7-4189-4ee7-b35a-5b1530041c70.png) {% /figure %} ## Gate/experiment Pulse scheduling For feature gates and experiments, individual Pulse scheduling is available separately from the project-level settings. Schedule daily Pulse metric reloads for individual feature gates and experiments. To access this feature: 1. Navigate to your feature gate/experiment in the Statsig Console 2. Go to the Pulse Results tab 3. Use the scheduling controls to configure daily reloads The scheduling allows you to: * Set a daily reload time in UTC * Choose between Full and Incremental reload types * Save, edit, or cancel scheduled reloads This feature requires gates with partial rollout rules and overrides project-level settings. ================================================================================ # Pipeline Overview URL: https://docs.statsig.com/statsig-warehouse-native/analysis-tools/pipeline-overview Updated: 2026-07-11 ================================================================================ This page gives a high-level overview of the pipeline Statsig Warehouse Native runs on your warehouse. ## Main steps The main steps in the pipeline are: * Identifying users' first exposures * Annotating Metric Sources with exposure data * Creating metric-user-day level staging data * Running intermediate rollups for better performance * Calculating group-level summary Statistics {% figure %} ![Pipeline View](/images/statsig-warehouse-native/analysis-tools/pipeline-overview/264113011-b0bdf1af-3ec6-4770-aabd-35f948ea842d.png) {% /figure %} ## Types of DAGs Statsig lets you run your pipeline in a few different ways: * A **Full Refresh** totally restates the experiment's data and calculates it from scratch. This is useful for starting an experiment, or if underlying data has changed * An **Incremental Refresh** appends new data to your experiment data. This reduces the cost of running scheduled updates to your results * A **Metric** refresh allows you to update a specific metric in case you changed a definition, or want to add new metrics to your analysis ## Artifacts and entity relationships Statsig generates and stores the following tables in your warehouse per-experiment. You have full access to these data sources for your own analysis, models, or visualizations. For experiments, `experiment_id` is the name of the experiment; for Feature Gates, `experiment_id` is the name of the gate along with the specific rule ID (for example, `chatbot_llm_model_switch_31e9jwlgO1bSSznKntb2gp_exposures_summary`) This is not an exhaustive list, but it includes most of the core result and staging tables useful for your own analysis. {% callout type="note" %} These are internal tables and will change as the product evolves. Changes will be documented here. {% /callout %} | Table | Description | Notes | | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | | `first_exposures_<experiment_id>` | Deduplicated and stitched (for experiments with ID resolution) first exposure events | Useful for ad-hoc analysis | | `exposures_summary_<experiment_id>` | Timeseries of exposures per group for display in Pulse | | | `unit_day_metrics_<experiment_id>` | User-day level metric aggregations table | Useful for ad-hoc analysis | | `unit_covariate_metrics_<experiment_id>` | User-level pre-experiment aggregations for regression adjustment/CUPED | | | `funnel_events_<experiment_id>` | Staging table for running funnel analysis | | | `percentile_values_<experiment_id>` | Staging table for running percentile analysis | | | `distinct_values_<experiment_id>` |Staging table for running count distinct analysis | | | `windowed_metrics_<experiment_id>` | Staging table for generating running totals when restating Pulse | | | `ratio_aggregations_<experiment_id>` | Staging table for generating running totals when restating Pulse | | | `results_<rollup>_<experiment_id>` | Outputs of Statistical Analysis for different rollups (e.g. daily, days-since-exposure, cumulative, 7-day). Exported to Statsig | Pulse inputs - useful for replicating Statistical analysis | | `ratio_results_<rollup>_<experiment_id>` | Outputs of Statistical Analysis for ratio metrics in different rollups (e.g. daily, days-since-exposure, cumulative, 7-day). Exported to Statsig | Pulse inputs - useful for replicating Statistical analysis | The high-level relationships and contents of these tables appear in the entity relationship diagram below. Refer to the Main steps diagram above for scheduling details. {% figure %} ![WHN ER Diagram](/images/statsig-warehouse-native/analysis-tools/pipeline-overview/120eb9ed-fe35-4a66-8acd-cbcd819a2bdf.png) {% /figure %} ## Other jobs Alongside and inside this main flow, Statsig also: * Run Health Checks and a Summary View for exposures * Calculate top dimensions for dimensional metrics * Calculate funnel steps * Run CUPED and Winsorization procedures during the group-level summaries to reduce variance and outlier influence * Calculate inputs to the Delta Method to avoid bias on Ratio and Mean metrics Statsig generates experiment-level tables, making it easy to run follow-up analyses on specific experiments. ## Visibility Select the history icon on your pulse results to see the jobs and IDs Statsig ran for each pulse reload, along with compute time and cost. This information is also visible in your warehouse's own history and usage management. Having the costs in the console is useful for cross-functional experimentation teams running the analysis. ## Exposure export table Statsig dedupes and records production exposures into the forwarded exposures table configured in your warehouse Data Connection. This table contains each user's first exposure to an experiment. For feature gates, Statsig dedupes and records exposures for partial rollouts (for example, 5% or 50% rollouts, but not 0% or 100% rollouts). | Column Name | Data Type | Description | | ----------------------- | --------- | --------------------------------------------------------------------------------------------- | | experiment\_id | string | The identifier for the gate/experiment | | group\_id | string | groupID for experiments; ruleID+Pass/Fail for gates | | group\_name | string | Name of the experiment group (e.g. Control vs Test) | | user\_id | string | The ID passed in as the Statsig userID | | stable\_id | string | Statsig Client SDK managed stable device identifier | | \[your custom ids] | string | One column for every custom unitID you use on Statsig | | timestamp | timestamp | Timestamp of the first exposure | | user\_dimensions | object | Warehouse specific object with all the user dimensions | Statsig populates `user_dimensions` in the daily deduplicated export. Fast-forwarded exposure rows can omit some fields in this object until the next daily load. ### Common fields in user\_dimensions `user_dimensions` contains user attributes captured alongside the first exposure. The exact shape can vary by SDK and project configuration, but these are some of the most common fields you may see: | Field | Description | Notes | | ---------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------- | | `os` | Normalized operating system name | Canonical OS field on the exposure side. | | `os_version` | Operating system version | Derived from SDK metadata or user agent parsing. | | `browser_name` | Browser name | Derived from SDK metadata or user agent parsing. | | `browser_version` | Browser version | Derived from SDK metadata or user agent parsing. | | `device_model` | Device model | Forwarded or inferred when available. | | `ip` | IP address | Present when available from the SDK or request context. | | `country` | Country | Derived from request context or IP lookup. | | `locale` | Locale | Forwarded or inferred when available. | | `language` | Language | Forwarded or inferred when available. | | `appVersion` | Application version | Forwarded when present on the SDK user object. | | `sessionID` | Session identifier | Forwarded when present on the SDK user object. | | `appIdentifier` | Application identifier | Forwarded when present on the SDK user object. | Additional non-null fields from the SDK user object may also appear in `user_dimensions`. Custom IDs are typically exported as dedicated top-level columns in the exposure table rather than being queried from this object. Statsig uses input fields such as `deviceOS` and `systemName` to derive the exported `os` field. If you want to analyze operating system on forwarded exposures, query `user_dimensions.os`. ## Event export table If you log custom events through a Statsig SDK, Statsig also forwards those events into a configurable table in your warehouse. This is the table used when Warehouse Native customers rely on Statsig SDK logging for outcome events. * Use `user_object` for user fields associated with the event. * Use `statsig_metadata` for SDK and exposure-processing metadata. * Use `company_metadata` for the event metadata payload you logged. | Column Name | Data Type | Description | | ----------------------- | --------- | --------------------------------------------------------------------------------------------- | | user\_id | string | The ID passed in as the Statsig userID | | stable\_id | string | Statsig Client SDK managed stable device identifier | | \[your custom ids] | string | One column for every custom unitID you use on Statsig | | timestamp | timestamp | Event timestamp | | event\_name | string | Name of the logged custom event | | event\_value | string | Optional event value | | user\_object | object | Warehouse specific object containing user fields associated with the event | | statsig\_metadata | object | Warehouse specific object containing Statsig SDK and exposure metadata | | company\_metadata | object | Event metadata payload logged with the event | ### Common fields in user\_object `user_object` contains user fields associated with the event. It often includes the same common fields as `user_dimensions`, plus any additional non-null fields sent on the SDK user object. | Field | Description | Notes | | ---------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------- | | `os` | Normalized operating system name | Canonical OS field on the event-side user object. | | `os_version` | Operating system version | Derived from SDK metadata or user agent parsing. | | `browser_name` | Browser name | Derived from SDK metadata or user agent parsing. | | `browser_version` | Browser version | Derived from SDK metadata or user agent parsing. | | `device_model` | Device model | Derived from `deviceModel` when provided. | | `ip` | IP address | Present when available from the SDK or request context. | | `city` | City | Added when geographic inference is available. | | `state` | State or region | Added when geographic inference is available. | | `country` | Country | Derived from request context or IP lookup. | | `locale` | Locale | Forwarded or inferred when available. | | `language` | Language | Forwarded or inferred when available. | | `appVersion` | Application version | Forwarded when present on the SDK user object. | | `sessionID` | Session identifier | Forwarded when present on the SDK user object. | | `appIdentifier` | Application identifier | Forwarded when present on the SDK user object. | ### Common fields in statsig\_metadata `statsig_metadata` contains SDK-level and exposure-processing metadata associated with the event. These are some of the most common customer-facing fields: | Field | Description | Notes | | ---------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------- | | `deviceType` | High-level device category | Derived from the normalized OS, for example `Desktop` or `Mobile`. | | `targetAppID` | Target app identifier | SDK target app metadata. | | `statsigTier` | Statsig environment tier | For example `prod` or `staging`. | | `keyEnvironment` | SDK key environment | Environment associated with the SDK key. | | `keyID` | SDK key identifier | Useful for debugging ingestion and environment issues. | | `samplingRate` | Event sampling rate | Present when the event is sampled. | | `is_bot` | Bot classification | Set when Statsig classifies the event as bot traffic. | | `billing_type` | Exposure billing classification | Common on exposure-related events. | | `groupID` | Experiment group identifier | Common on exposure-related events. | | `ruleID` | Rule identifier | Common on exposure-related events. | | `continuous_rollout_id`| Continuous rollout identifier | Present for continuous rollout exposures. | | `configExposureType` | Exposure config type | For example `dynamic_config` or `experiment`. | | `isSwitchback` | Switchback flag | Present when the exposure is associated with a switchback experiment. | | `is_autotune` | Autotune flag | Present when the exposure is associated with an autotune experiment. | Other SDK debugging and exposure-processing metadata may also appear in `statsig_metadata`. ### What goes in company\_metadata `company_metadata` stores the metadata payload logged with the event. This object doesn't have a fixed schema and varies based on the event and your SDK usage. Most event-specific business context, such as `price`, `currency`, `category`, `plan`, `screen`, `route`, or nested objects like `cart`, `items`, and `context`, will appear here. ================================================================================ # Early Diagnostics URL: https://docs.statsig.com/statsig-warehouse-native/features/freshness Updated: 2026-07-11 ================================================================================ ## Exposures from Statsig SDKs When Pulse is loaded, Statsig performs a just-in-time update of exposures in your warehouse for the first 1 million exposures per experiment. After that threshold, updates occur on a daily cadence. After launching an experiment, you may want to check certain results (such as page load time, impressions on key pages, and business-critical metrics) as early as possible to detect crashes or bugs. If you are using the Statsig SDK to generate exposures, Statsig updates exposures in your warehouse before computing Pulse results when Pulse is loaded soon after the experiment starts. This lets you see Pulse results **as fresh as approximately 15 minutes** (assuming events and metrics arrive at the same speed). After the first 1 million exposures, Statsig batches, deduplicates, and writes exposures to your warehouse once a day. The daily job also includes additional fields that the just-in-time write doesn't include (for example, Group Names). ## Custom Events from Statsig SDKs Statsig exports custom event logs to your warehouse hourly, plus a short processing delay. You can see Pulse results on metrics derived from those events **as fresh as approximately 1 hour**. ================================================================================ # Read Results URL: https://docs.statsig.com/statsig-warehouse-native/features/interpreting-results/read-results Updated: 2026-07-11 ================================================================================ ## Read experiment results To read the results of your experiment, go to the **Results** tab, where you can see your experiment hypothesis, **Exposures**, and **Scorecard**. ### Exposures {% figure %} ![Exposures chart showing cumulative users per experiment group](/images/snippets/pulse/read-results/7a80e74e-140d-4c36-a78e-c9b626e1fee5.png) {% /figure %} At the top of the Results page is the Exposures Chart. Exposures are the unique experimental units enrolled in the experiment. This is typically the number of unique users; for device-level experimentation, this is the number of devices. The timeline shows when the experiment started and how many exposures were enrolled on each day. Use the chart to see the rate at which users were added into each group, the total number of users exposed, and whether the target ratio matches what you configured in experiment setup. ### Scorecard The experiment **Scorecard** shows the metric lifts for all Primary and Secondary metrics you set up at experiment creation. #### Immediately post-experiment start For up to the first 24 hours after starting your experiment (before the daily metric results run), the **Scorecard** section is calculated hourly. This applies to Statsig Cloud only; for WHN projects you must reload results on demand or set up a daily schedule. This near-real-time scorecard lets you confirm that exposures and metrics are calculated as expected and debug your experiment or gate setup if needed. {% callout type="note" %} Do **not** make experiment decisions based on real-time results data in this first 24-hour window after experiment start. Make decisions only after the experiment has reached its target duration, as determined by your primary metric(s) reaching experimental power. For more about target duration, go to [target duration](/experiments/create-new#target-duration). {% /callout %} Because data in this early post-experiment window is intended for diagnostics rather than decision-making, there are a few key differences from the results shown after daily runs begin: - Metric lifts don't have confidence intervals - No time-series view of metric trends - No projected topline impact analysis - No option to apply more advanced statistical tactics, such as CUPED or Sequential Testing All of these are available in daily Results, which start showing in the next daily run. #### Post-first day scorecard {% figure %} ![Experiment scorecard table displaying metric lifts and confidence intervals](/images/snippets/pulse/read-results/8b855f5c-d26f-4185-ac31-5108fbebe18e.png) {% /figure %} The experiment Results daily run calculates the difference between comparable randomization groups (for example, test and control) across your organization's suite of metrics, and applies a statistical test to the results. For more about Statsig's stats engine, go to [the stats engine documentation](/experiments/statistical-methods/introduction). For every metric, Statsig shows: - The calculated relative difference (Delta %) - The confidence interval - Whether the result is statistically significant - Positive lifts are green - Negative lifts are red - Non-significant results are grey The formula for calculating lift is: Delta(%) = (Test - Control) / Control Confidence intervals are reported at the selected significance level (95% by default). In a typical two-sided Z-test, Statsig shows the confidence interval as +/- 1.96 \* standard error. Statsig automatically applies 99.9% winsorization to event_count, event_count_custom, and sum metrics. This caps extreme outlier values to reduce their impact on experiment results. For metrics added to the **Scorecard** or **Monitoring Metrics** sections of your experiment or gate, you can also apply optional statistical treatments, such as CUPED (pre-experiment bias reduction) and sequential testing adapted confidence intervals. For more details, go to [the stats engine documentation](/experiments/statistical-methods/introduction). - **Experiment results are computed for the first 90 days**: By default, Statsig computes experiment results for only the first 90 days of your experiment. You receive an email notification as you approach the 90-day limit, at which point you can extend the compute window by another 30 days at a time. If the experiment runs beyond the compute window, Statsig stops adding new users to the experiment's results, but analysis for existing exposed users continues until you make a decision on the experiment. {% callout type="note" %} This experiment result calculation window only affects whether a user is included in the experiment's analysis, and doesn't affect the treatment each user receives. New users still receive the experience for the group they get randomized into. {% /callout %} ### Experiment results views The following views are available for Scorecard metric lifts: - **Cumulative results (default view)**: Displays the aggregate difference between experiment groups and visualizes the corresponding confidence intervals. - **Table view**: Displays the same data as the cumulative view but in a table format with additional fields. - **Daily results**: Shows the difference between experiment groups aggregated based on days since start of experiment. - **Days since exposure**: Shows the difference between experiment groups aggregated based on days since exposure to the experiment. Cumulative results includes a detailed view on hover, where you can additionally view the raw statistics used in the metric lift calculations, as well as topline impact. {% figure %} ![Cumulative results view with hover details](/images/snippets/pulse/read-results/856c7750-df56-45d9-b253-6b63f336cac7.png) {% /figure %} ### Dimensions There are two ways to break down a Scorecard metric: by a **User Dimension** or by an **Event Dimension**. #### User dimensions User Dimensions refer to user-level attributes that are either part of the user object you log, or additional metadata that Statsig extracts. Examples of these user attributes include operating system, country, and region. You can create [custom "explore" queries](/experiments/interpreting-results/custom-queries) to _filter on_ or _group by_ available user dimensions. For example, view results for users in the US, or results for iOS users grouped by country. Go to the "explore" tab to create a custom query. {% figure %} ![custom queries](/images/snippets/pulse/read-results/e3afb526-8f9d-465e-af33-ea9575ac69e7.png) {% /figure %} #### Event dimensions Event Dimensions refer to the value or metadata logged as part of a custom event used to define the metric. To view results for a metric broken down by categories specific to that metric, [specify the dimension](/metrics/metric-dimensions) in the **value** or **metadata** attributes when you log the source event. For example, when you log a "click" event on your web or mobile application, you can log the target category using the **value** attribute. Statsig automatically generates results for each category in addition to the top-level metric. To see breakdowns for all categories within a metric, click on the (+) sign next to the metric. {% figure %} ![dimension button](/images/snippets/pulse/read-results/65cbe2a0-d269-4385-a606-c825ff2e8e05.png) {% /figure %} {% figure %} ![dimension results view](/images/snippets/pulse/read-results/f557dac6-e29f-4cb5-bd6a-19fc2b226193.png) {% /figure %} ### Significance level settings You can adjust these settings at any time to view Scorecard results with different significance levels. - **Apply Benjamini-Hochberg Procedure per Variant**: Select this option to reduce the probability of false positives by adjusting the significance level for multiple comparisons. Go to [Benjamini-Hochberg Procedure](/experiments/statistical-methods/methodologies/benjamini-hochberg-procedure) for details. - **Confidence Interval**: Changes the confidence interval displayed with the metric deltas. Choose a lower confidence interval (for example, 80%) when there is higher tolerance for false positives and fast iteration with directional results is preferred over longer experiments with greater certainty. - **CUPED**: Toggle CUPED on or off using the inline settings above the metric lifts. This setting applies only to **Scorecard** metrics; Statsig doesn't apply CUPED to non-Scorecard metrics. - **Sequential Testing**: Applies a correction to p-values and confidence intervals to reduce false positive rates when evaluating results before the target completion date of the experiment. This mitigates the increased false positive rate associated with the "peeking problem". Toggle **Sequential Testing** on or off using the inline settings above the metric lifts. This setting is available only for experiments with a set target duration. {% figure %} ![analysis settings](/images/snippets/pulse/read-results/d2d7405a-9e86-4317-8f32-51b369c66699.png) {% /figure %} ### Restarting results {% figure %} <img src="/images/snippets/pulse/read-results/201ab314-9304-43f1-bc11-58dcf9394aa2.png" alt="Restart results banner" width="1062" /> {% /figure %} If your experiment has stopped computing results, you can resume updates by selecting the Restart button. Before restarting, review the following: - A Restart isn't a [Reset](/experiments/ending/ending-experiment#stopping-an-experiment). A Restart doesn't re-randomize units in your experiment, and all users continue to receive the same group assignments. - Statsig begins computing experiment results from the restart point, so metric results start over. Old results may still be available in time series and explore query views, but Statsig doesn't carry them forward or update them. - The Cumulative Exposures chart updates based on new exposures, but the duration of the pause in computations affects whether the chart starts from zero or retains past exposure counts. Avoid restarting results by actively extending experiments while they are running. Monitor email alerts from Statsig and check your experiments regularly. ================================================================================ # Metric Drill-Down URL: https://docs.statsig.com/statsig-warehouse-native/features/interpreting-results/metric-drill-down Updated: 2026-07-11 ================================================================================ ## Metric tooltip A tooltip with key statistics and additional detail appears when you hover over a metric in Pulse. {% figure %} ![UI for metric hover card in experiments](/images/metric_hover_card.png) {% /figure %} * **Group**: The name of the group of users. For Feature Gates, the "Pass" group is considered the test group while the "Fail" group is the control. In Experiments, these are the variant names. * **Units**: The number of distinct units included in the metric. E.g.: Distinct users for user\_id experiments, devices for stable\_id experiments, etc. * **Mean**: The average per-unit value of the metric for each group. * **Total**: The total metric value across all units in the group, over the time period of the analysis. ### Calculation details | Metric Type | Total Calculation | Mean | Units | |------------------|-------------------------|----------------|----------------| | event\_count | Sum of events (99.9% winsorization) | Average events per user (99.9% winsorization) | All users | | event\_user | Sum of event DAU (distinct user-day pairs) | Average event\_dau value per user per day. Statsig calls this "Event Participation Rate" because it represents the probability a user is DAU for that event. | All users | | ratio | Overall ratio: sum(numerator values)/sum(denominator values) | Overall ratio | Participating users | | sum | Total sum of values (99.9% winsorization) | Average value per user (99.9% winsorization) | All users | | mean | Overall mean value | Overall mean value | Participating users | | user: dau | sum of daily active users | Average metric value per user per day. The probability that a user is DAU | All users | | user: wau, mau\_28day | Not shown | Average metric value per user per day. The probability that a user is xAU | All users | | user: new\_dau, new\_wau, new\_mau\_28day | Count of distinct users that are new xAU at some point in the experiment | Fraction of users that are new xAU | All users | | user: retention metrics | Overall average retention rate | Overall average retention rate | Participating users | | user: L7, L14, L28 | Not shown | Average L-ness value per user per day | All users | ### p-value In null hypothesis significance tests, the p-value is the probability that such an extreme difference arises by random chance when the experiment has no effect. A low p-value means the observed difference is unlikely to be due to random chance. In hypothesis testing, a p-value threshold determines which results reflect a real effect and which are plausibly due to random chance. ([p-value calculation](/experiments/statistical-methods/p-value)) ### Reverse power Reverse power is the smallest effect size that an experiment can reliably detect in its current state (some studies refer to this value as ex-post MDE). Statsig calculates it from the sample size and standard error of the control group. Reverse power does *not* depend on the observed effect size. In practice, reverse power answers questions such as: given how the test played out, what is the smallest effect detectable with sufficient power (typically 80%)? For a two-sided test, the reverse power for a given metric X is computed using the following equation: $$ Reverse Power = \frac{(Z\_\{1-\beta} + Z\_\{1-\alpha/2})}{\overline{X}\_\{\text{control}}}\times \sqrt{\frac{\mathrm{var}(\Delta \overline{X})}{N\_\{\text{control}}}} \times 100\\% $$ For a one-sided test, the reverse power for a given metric X is computed using the following equation: $$ Reverse Power = \frac{(Z\_\{1-\beta} + Z\_\{1-\alpha})}{\overline{X}\_\{\text{control}}}\times \sqrt{\frac{\mathrm{var}(\Delta \overline{X})}{N\_\{\text{control}}}} \times 100\\% $$ * $\overline{X}\_\{\text{control}}$ is the mean metric value across control users * $\mathrm{var}(\Delta \overline{X})$ is the population variance of delta * $N\_\{\text{control}}$ are the observed number of units in the control group * $Z\_\{1-\beta}$ is the standard Z-score for the selected power. Typically $1-\beta$ = 0.8 and $Z\_\{1-\beta}$ = 0.84 * $Z\_\{1-\alpha/2}$ and $Z\_\{1-\alpha}$ are the standard Z-scores for the selected significance level in a two-sided test and in a one-sided test. Reverse power is an optional feature. To manage it, go to *Settings > Product Configuration > Experimentation > Organization* and toggle it on or off. ## Detailed view Click **View Details** to access in-depth metric information. The detailed view contains three sections: * **Time Series**: How the metrics evolve over time * **Raw Date**: Group level statistics * **Impact**: How the experiment impacts the metric ### Time series In this view, select and drag to zoom in on different time ranges. Three types of time series are available in the drop-down: **Daily**: The metric impact on each calendar day without aggregating days together. This is useful for assessing day-over-day metric variability and the impact of specific events. This is the recommended time series view for Holdouts, because it highlights the impact over time as new features are launched. {% figure %} ![Daily metric impact visualization interface](/images/snippets/pulse/metric-drill-down/f42453a6-e413-47d5-b176-bb3e9b49a915.png) {% /figure %} **Cumulative**: Shows the cumulative metric impact from the start of the experiment over time. This is useful for observing trends and seeing how your confidence interval changes over time. {% figure %} ![Cumulative metric lift visualization interface](/images/snippets/pulse/metric-drill-down/b2e9aea9-98fb-4e29-bccf-abae356d0173.png) {% /figure %} **Days Since Exposure**: Shows the metric impact based on how long a user has been in the experiment. Daily data for each user is aligned by the day they entered the experiment (Day 0, Day 1, etc.), not by calendar date. This lets you distinguish early (novelty) effects from long-term effects. This view also shows pre-experiment data, which identifies biases between groups before the experiment started. Such biases can result from random chance or from an issue in the random assignment process. {% figure %} ![Days since exposure metric visualization interface](/images/snippets/pulse/metric-drill-down/17eeb054-43d8-424c-b43a-85c682bfcfb8.png) {% /figure %} ### Raw data This view shows the group-level statistics needed to compute the metric deltas and confidence interval. It includes Units, Mean, and Total (defined in the Metric Tooltip section above), as well as the Standard Error of the mean (Std Err). For details on the statistical calculations, go to the [stats engine documentation](/experiments/statistical-methods/introduction). ### Impact {% figure %} ![Experiment impact metrics interface](/images/snippets/pulse/metric-drill-down/171754168-624f740e-bdd6-46ea-a2e3-8d70e637ff75.png) {% /figure %} * **Experiment Delta (absolute)**: The absolute difference of the Mean between test groups i.e. Test Mean - Control Mean. Statsig shows the p-value to indicate whether the observed absolute difference is statistically significant. * **Experiment Delta (relative)**: Relative difference of the Mean i.e. 100% x (Test Mean – Control Mean) / Control Mean. * **Topline Impact**: The measured effect that the experiment has on the overall topline metric each day, on average. Statsig computes this daily and averages it across days in the analysis window. The absolute value is the net daily increase or decrease in the metric; the relative value is the daily percentage change. * **Projected Launch Impact**: An estimate of the daily topline impact expected if you make a decision and launch the test group to all users. This accounts for the layer allocation and the size of the test group. This assumes the targeting gate (if there is one) remains the same after launch. Go to [topline impact](/experiments/statistical-methods/topline-impact) for details on the exact calculation for topline and projected impact. **FAQs about topline impact** *Why is the projected launch impact smaller than the relative experiment delta?* An experiment may affect only a subset of the user base that contributes to a topline metric. The relative experiment delta is therefore diluted when measured against the topline metric value. For example: consider a top-of-funnel experiment on the registration page. Among users who visit that page, the treatment leads to more sign-ups and a 10% lift in daily active users (DAU). The topline DAU metric includes other user segments outside the experiment, such as long-term users who don't visit the registration page. A 10% lift in the test vs. control comparison may therefore amount to only a 1% increase in overall DAU. *How can the topline impact be higher than the experiment delta?* The topline impact can be higher or lower than the experiment delta because Statsig computes the two values differently and they have different meanings. Experiment deltas are based on unit-level averages: the mean metric value is computed for each user across all days, then averaged to get the group mean. The topline impact is computed daily based on the total pooled effect from all users, averaged across days to show the daily impact. Statsig computes topline impacts this way because most metrics are tracked daily and the topline value is typically an aggregation across all users, not a user-level average. For experiment analysis, best practice is to match the analysis unit to the randomization unit, so metrics are aggregated at the unit level before computing experiment deltas. ================================================================================ # Custom "Explore" Queries URL: https://docs.statsig.com/statsig-warehouse-native/features/interpreting-results/custom-queries Updated: 2026-07-11 ================================================================================ Custom queries run additional experiment analyses on your existing data beyond what is in your main Results tab. Use them to gain deeper insights from experiments and feature rollouts, debug interesting results, or scope results to specific sub-groups. Custom queries let you filter or group metrics by event or user dimensions, or filter to a specific set of users to see how an experiment or launch affected those users. Custom queries are experimental analyses, and all the same statistical procedures apply as in the main Results tab. Statsig computes results as p-values and confidence intervals for your metric deltas. Advanced statistical methods like [CUPED](/experiments/statistical-methods/methodologies/cuped) and [Sequential Testing](/experiments/advanced-setup/sequential-testing) are also available. {% callout type="warning" %} Use caution when drawing inferences from Custom Queries, especially when grouping by a dimension with many options. This increases the chance of a false-positive statistically significant result. {% /callout %} ### Dimension loading timing for precomputed user dimensions When viewing results for precomputed user dimensions (which are configured and run on a schedule), Statsig loads these dimensions through separate asynchronous explore queries. This means: - The main experiment results appear first. - Precomputed dimensions continue loading in the background and are available within a few minutes. - This timing gap is most noticeable immediately after the first reload of the day. - If you see "No dimensions available for this time range" for precomputed dimensions, wait a few minutes and refresh to check whether dimensions have finished loading. {% callout type="note" %} This timing behavior only affects precomputed user dimensions that run on a schedule. User-triggered custom queries don't experience this asynchronous loading delay. {% /callout %} ### Running a custom query To run a custom query, go to the **Explore** tab within your experiment. {% figure %} <img src="/images/snippets/pulse/custom-queries/b2737ba1-dbf4-4235-a806-afea5dfe9a78.png" alt="Custom query explore tab interface" width="467" /> {% /figure %} Custom Query fields: - **Metric(s):** The metric(s) you want to analyze. You can select a single metric, a few metrics, or a Metric Tag. Adding a Tag will include all the metrics within that Tag in your Custom Query. There are three "default" metric selections included as shortcuts: - "Scorecard Metrics", all metrics included in your experiment setup's Primary and Secondary Metrics sections - "Primary Metrics" - "Secondary Metrics" - **Metric Filter:** With metrics selected, you can filter metrics by either Event or User dimensions using the "Add Filter" dropdown. For example, to view experiment results for Canadian users only, filter to "Country = CA". {% figure %} ![Metric filter dropdown selecting Country equals CA](/images/snippets/pulse/custom-queries/f56b4259-4d18-44d1-9646-9d7aa2f8f847.png) {% /figure %} - **Group By:** You can group your custom query results by either an Event or User dimension. Whereas custom query filters can be applied at the _per-metric_ level, the Group By action is at the _query_ level (so all included metrics have whatever Group By you select applied to them). - **Time Range for Metric Data:** The date range you're running your analysis on. By default this is the "Full date range" of your experiment data. - **(Advanced) ID List Segment filters:** You can choose an ID-list based [Segment](/segments/overview), and your results will only be calculated for users in that segment. This is useful when you did not log an important user dimension to filter on, or when you only need results for a sub-population defined in your own data warehouse. - This option can easily lead to erroneous and biased results. The segment must be defined based on the user's status _before_ they were exposed to the experiment or feature gate. - You can also choose to _exclude_ a certain ID list segment. For example, exclude users who have been retroactively identified as bad actors from your lifts analysis. - **(Advanced) Filter by Exposure Date:** You can also filter the results by Exposure Date for more flexibility. You can include or exclude a date range, or in WHN, include/exclude users based on when they were first exposed to the experiment. - This is useful when your metrics have a novelty effect, delayed impact, or when you need to filter results to specific users. Use it cautiously because it can lead to biased results. {% callout type="note" %} User groups in experiment results are based on first-touch attribution. Filters and grouping are based on the user attributes collected at the time of first exposure in the gate/experiment/layer check. {% /callout %} {% figure %} <img src="/images/snippets/pulse/custom-queries/85264260-2f65-4bfd-b4e1-477bd043b87d.png" alt="Custom query definition form showing selected metrics and filters" width="1378" /> {% /figure %} ### Viewing a custom query in Explore These queries take a few minutes to run. After the query completes, Statsig sends an email notification and the results appear in the **Query History** section of the *Explore* interface. All historical queries across your team are stored there. You can assign a display name to a query inline for easier future identification. {% figure %} <img src="/images/snippets/pulse/custom-queries/46574581-604e-4d29-b231-fa51876c813d.png" alt="Explore tab query history list" width="1374" /> {% /figure %} ### Scheduling a custom query To get a daily refresh of a custom query, schedule it directly from the **Explore** tab. Create the custom query, then select the "..." menu, then **Schedule**. The custom query runs daily and appears in the **Scheduled** tab of your Metric Lifts. {% figure %} <img src="/images/snippets/pulse/custom-queries/56d11b46-f2de-4569-89f7-b0a439910964.png" alt="Scheduled custom queries tab displaying daily runs" width="1378" /> {% /figure %} {% figure %} <img src="/images/snippets/pulse/custom-queries/fad0c8ce-3f9c-46be-a09c-52263e124259.png" alt="Scheduled query configuration interface" width="505" /> {% /figure %} ### Reviewing custom query results Custom query results resemble the main Results tab because the statistical methods are the same. The key difference is that a custom query result is a snapshot in time. After a query runs, Statsig saves the results and doesn't update them when more metric data is collected. To update results, run a new custom query or schedule custom queries to run on a regular cadence. #### Sequential testing and custom queries If [Sequential Testing](/experiments/advanced-setup/sequential-testing) is enabled for your experiment, it applies to custom query results as well. The degree of adjustment to confidence intervals and p-values follows the standard sequential testing rules. If a custom query doesn't satisfy the experiment's target Days or Unique Exposures from your setup, Statsig applies sequential testing adjustments to account for the underpowered state of the experiment. Because custom queries are snapshots in time, sequential testing adjustments apply to that specific analysis only. Running additional custom queries with more or less data (for example, more days or more unique users) changes the adjustments accordingly. Custom queries that meet the configured minimum Days or Unique Exposures may have no sequential adjustments applied. {% figure %} ![Custom query results table displaying sequential testing adjustments](/images/snippets/pulse/custom-queries/df7c4087-82c3-41fb-8f5d-124094ec4526.png) {% /figure %} ================================================================================ # Export Pulse Results to Your Warehouse in Warehouse Native URL: https://docs.statsig.com/experiments/interpreting-results/access-whn Updated: 2026-07-11 ================================================================================ ## How to access Pulse data in Warehouse Native WHN lets you access exposures and metric results across all experiments directly in your warehouse through SQL Views defined in your Statsig project using a metric source. ### Exposures Statsig automatically writes exposures to your warehouse to the table configured in your project setup. To find the table's location, go to Settings > Data Connection. The table should be located at the `{Database Name}.{Schema Name}.{Exposures Forwarding Table Name}`, e.g. `experimentation.statsig.exposures`. ### Results With a SQL View, you have access to experiment metadata (such as experiment team, tags, target duration, and settings like CUPED and Sequential testing), each metric’s metadata (such as metric tags), and all metric lifts: the same set of results visible on the Console. To start using this feature, enable it at **Project Settings > Data Connection > Export**. After enabling it, Statsig automatically sets up the SQL View in your warehouse and creates the metric source in your Statsig project. Statsig automatically exports scorecard metric results to your data warehouse each time an experiment is loaded, generating a new copy. To differentiate result versions, use the `ds` column, which contains the timestamp when Statsig wrote the data to your warehouse. {% figure %} ![Project settings data connection export interface](/images/experiments/interpreting-results/access-whn/0355e284-7e3f-40db-b441-fa2a00ccf3ab.png) {% /figure %} ### Schema of the results data export table The default table name used is statsig\_daily\_results. When exports are enabled, Statsig also autocreates a metric source with this name in your Statsig project. | Column | Type | Description | |-|-|-| | ds | timestamp | The time when the data was written at | | experimentName | string | Name of the experiment | | experimentCreator | string | Creator of the experiment | | experimentTeam | string | Team conducting the experiment | | experimentTags | array of strings | Tags associated with the experiment, represented as an array of strings | | experimentStartTs | number | Start timestamp of the experiment, in milliseconds | | experimentEndTs | number | End timestamp of the experiment, in milliseconds | | targetExposures | number | The target number of exposures for the experiment | | targetDuration | number | The target duration of the experiment | | actualDuration | number | The actual duration the experiment ran, from Start Date to Decision Date. For analyze only experiments, this will be TODAY - Configured Start Date | | controlGroupName | string | Name of the control group in the experiment | | testGroupName | string | Name of the test group in the experiment | | useCUPED | boolean | Whether CUPED was applied in the experiment | | useSequential | boolean | Whether sequential testing was applied in the experiment | | metricName | string | Name of the metric being measured in the experiment | | metricType | string | Type of metric | | metricTags | array of strings | Tags associated with the metric, represented as an array of strings | | higherIsBetter | boolean | Whether a higher value of the metric is better | | isVerifiedMetric | boolean | Whether the metric is verified | | metricTeam | string | Team responsible for the metric | | absoluteDelta | number | The absolute change in the metric value between control and test groups | | absoluteDeltaCI | number | Confidence interval for the absolute delta | | relativeDelta | number | The relative change in the metric value between control and test groups | | relativeDeltaCI | number | Confidence interval for the relative delta | | absoluteDeltaPValue | number | P-value associated with the absolute delta metric result | | toplineAbs | number | The absolute topline metric value for the experiment | | toplineAbsCI | number | Confidence interval for the absolute topline metric | | toplineRel | number | The relative topline metric value for the experiment | | toplineRelCI | number | Confidence interval for the relative topline metric | | projectedTopline | number | Projected topline metric value based on current data | | projectedToplineCI | number | Confidence interval for the projected topline metric | | projectedToplineRel | number | Projected relative topline metric value based on current data | | projectedToplineRelCI | number | Confidence interval for the projected relative topline metric | | controlUnits | number | The number of control group units | | testUnits | number | The number of test group units | | controlTotal | number | Total value for the control group metric | | testTotal | number | Total value for the test group metric | | controlMean | number | The mean value for the control group | | testMean | number | The mean value for the test group | | sequentialTestingAbsoluteDeltaCI | number (optional) | Confidence interval for the absolute delta with sequential testing enabled| | sequentialTestingRelativeDeltaCI | number (optional) | Confidence interval for the relative delta with sequential testing enabled| | sequentialTestingAbsoluteDeltaPValue | number (optional) | P-value for the absolute delta with sequential testing enabled| ## Report types There are three types of exports: 1. Exposures - A table of all exposed users and their first exposures. This is useful for joining on your own internal data, and running custom queries within your own data warehouse. You can also use it to verify who was in the experiment, what group they were assigned to, and when they were first exposed (around 1-25MB). This contains: 1. `<experiment\>_first_exposures.csv` - contains a list of users and their first exposure to the experiment. 2. Pulse Summary - This provides precomputed summary experimental data for all metrics and test groups including everything that's visible on Pulse (**around 10-100 kb**). This contains: 1. `<experiment\>_pulse_summary.csv` - contains Pulse aggregate metrics computed over the duration of the experiment. 3. Raw Data - This provides raw exposures and metrics data at the user-day level. This is best used for manually inspecting data, or recomputing your own statistics (**around 10MB-1GB**). This contains: 1. `<experiment\>_first_exposures.csv` - contains a list of users and their first exposure to the experiment. If this is the only file you are interested in, you can get this by exporting an "Exposures" report which is much smaller in size. 2. `<experiment\>_user_metrics.csv` - contains a list of experimental users, and their calculated metrics for each day they were enrolled in the experiment. In WHN, only the Pulse Summary can be exported, because the other two types of data are only stored [in your warehouse](/statsig-warehouse-native/analysis-tools/pipeline-overview/#artifacts-and-entity-relationships). The availability of these exports is subject to the retention policy. Statsig holds exposures data for up to 90 days after an experiment is concluded, and holds raw user-level metrics data for 90 days. ### Pulse Summary file description - for Feature Gates | Column Name | Description | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | Name of the Experiment or Feature Gate | | rule | Name of the Feature Gate Rule. | | metric\_type | Category of the metric. Different metric\_types are computed differently, including how they're computed in Pulse. | | metric\_name | The name of the metric. For event metrics, this is the name of the event. | | metric\_dimension | The subcategory of the metric. For example, if you log value in LogEvent, then value shows up as a subdimension. dimension = !statsig\_topline indicates that this row reflects an aggregate across all dimensions. | | start\_date | The start date for this measurement | | end\_date | The end date for this measurement | | test\_units | The number of users in the test group | | test\_mean | The average value of this metric across test users (or participating units when applicable) | | test\_stderr | The standard error for the estimate of the mean for test users. Use this to compute confidence intervals. | | ctrl\_units | The number of users in the control group | | ctrl\_mean | The average value of this metric across control users (or participating units when applicable) | | ctrl\_stderr | The standard error for the estimate of the mean for control users. Use this to compute confidence intervals. | | abs\_delta | The absolute difference between the test and control mean (test\_mean - ctrl\_mean) | | abs\_stderr | The estimated standard error of abs\_delta | | rel\_delta | The relative difference between test and control mean, sometimes referred to as lift (test\_mean - ctrl\_mean)/ctrl\_mean | | rel\_stderr | The estimated standard error of rel\_delta (abs\_delta/ctrl\_mean) | | z\_score | The calculated Z-score | ### Pulse Summary file description - for experiments | Column Name | Description | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | Name of the Experiment or Feature Gate | | rule | Name of the Feature Gate Rule. | | experiment\_group | The group of users for which this metric is computed for. For a feature gate, this is pass/fail. For an experiment, this is the variant name. | | metric\_type | Category of the metric. Different metric\_types are computed differently, including how they're computed in Pulse. | | metric\_name | The name of the metric. For event metrics, this is the name of the event. | | metric\_dimension | The subcategory of the metric. For example, if you log value in LogEvent, then value shows up as a subdimension. dimension = !statsig\_topline indicates that this row reflects an aggregate across all dimensions. | | start\_date | The start date for this measurement | | end\_date | The end date for this measurement | | units | The number of users included in this metric estimate. | | mean | The average value of this metric across units (or participating units when applicable) | | stderr | The standard error for the estimate of the mean. Use this to compute confidence intervals. | ### First exposures file description | Column Name | Description | | ----------------------- | --------------------------------------------------------------------------------------------- | | unit\_id | Refers to the unit identifier used in the experiment (eg. user\_id, stable\_id, org\_id) | | name | The name of the gate/experiment | | rule | For gates, this refers to the rule name | | experiment\_group | The group the user was assigned to | | first\_exposure\_utc | The UTC timestamp when the user was first assigned to the experiment | | first\_exposure\_pst\_date | The date in PST when the user was first assigned to the experiment | | as\_of\_pst\_date | The date this data was generated | | user\_dimensions | JSON-formatted key-value pairs describing the user's attributes at the time of first exposure | ### Unit metrics file description | Column Name | Description | | ---------------- | ------------------------------------------------------------------------------------------- | | pst\_ds | The 24hr window the data refers to. All dates are anchored from 12:00a -> 11:59p PST. | | unit\_id | Refers to the unit identifier used in the experiment (eg. user\_id, stable\_id, org\_id) | | metric\_type | The category of the metric | | metric\_name | The name of the metric | | metric\_dimension | The name of the metric dimension. '!statsig\_topline' is the overall metric with no slicing. | | metric\_value | The numeric value of the metric | | numerator | For some metrics, Statsig tracks the numerator | | denominator | For some metrics, Statsig tracks the denominator | ================================================================================ # Participating Units URL: https://docs.statsig.com/statsig-warehouse-native/features/interpreting-results/participating-units Updated: 2026-07-11 ================================================================================ ## Definition In Statsig, Participating Units (sometimes referred to as participating users) are a subset of an experiment's total exposed unit count relevant to a particular metric. They count units with a non-zero denominator value for metric types that require a numerator and denominator. These units are used in statistical calculations for ratio-based metrics. {% figure %} ![Participating units diagram showing subset of total exposed users](/images/experiments/participating_units.png) {% /figure %} Statsig computes ratio metrics only for users with a non-zero denominator value. For example, a user must trigger the denominator event on a given day to be included in the daily ratio. Statsig excludes users who don't trigger the denominator event during an experiment from the test vs. control comparison of a ratio metric. ## Example An example of this type of metric for an e-commerce company could be "Total Items Purchased per Order", measuring the average basket size. The numerator would be defined as the count of total items bought, and the denominator would be the total number of orders submitted. If an experiment were run on the checkout flow, only users who actually had at least one Order event would be included in the "Total Items Purchased per Order" ratio metric. ================================================================================ # Best Practices and Avoiding False Positives URL: https://docs.statsig.com/statsig-warehouse-native/features/interpreting-results/best-practices Updated: 2026-07-11 ================================================================================ Follow these suggestions to interpret Pulse results in a scientifically sound way: 1. Form a hypothesis before viewing Pulse. Identify which metrics you expect to shift, what else could have happened, and what signals indicate something went wrong. 2. Establish a small set of key metrics directly related to your hypothesis. More than a handful of key metrics usually indicates an ill-defined hypothesis or unfocused experimentation. Examining too many metrics increases the false positive rate. 3. Avoid cherry-picking results. Don't selectively pick metrics that look good while ignoring those that don't. Avoid using numbers with no connection to your hypothesis. Statistically significant results should have a plausible explanation (a false positive is a plausible explanation). 4. Multiple independent effects that are consistent with a plausible explanation increase confidence that the observed effects are real, even with borderline p-values. 5. Expect false positives and be cautious about statistically significant results with borderline p-values. A 95% confidence interval (5% significance level) is expected to produce one statistically significant result out of twenty by random chance alone. This rate increases if you include borderline metrics (for example, p = 0.06). 6. Look beyond your hypothesis for additional effects, tradeoffs, and unexpected behaviors. These can reveal information about how users interact with your product and often lead to follow-up experiments. ================================================================================ # Pulse FAQs URL: https://docs.statsig.com/statsig-warehouse-native/features/interpreting-results/faq Updated: 2026-07-11 ================================================================================ Interpreting statistical results can be difficult, and many users have similar questions. This page covers the most common ones. ## I had a stat sig result, but it turned negative. How should I interpret this In general, trust the current result, because it incorporates more information about the users in your experiment. There are several reasons this can happen: * Random noise, which gets diluted as your sample size gets larger * Within-week seasonality (e.g. an effect is different on Mondays), which gets normalized with more data * The users who saw the experiment early are different from slower adopters. A daily user will likely see your experiment before someone who uses your product once a month. Use the time series view for more insight on this. * A novelty effect made the experiment meaningful early on, but the effect faded. For example, after changing a button, users may click it out of curiosity at first, then revert to prior behavior. Use the days-since-exposure view for more insight on this. Best practice is to set a readout date when you launch your experiment, based on a [power analysis](/experiments/power-analysis), and to disregard the statistical interpretation of results until that date. Reading results multiple times before the readout date dramatically increases the rate of false positives. ## How should I start interpreting results Start by using your scorecard metrics to understand whether you moved the metrics you expected to move. Before reviewing Pulse, form a hypothesis about what your experiment should drive. Your primary metrics should answer that hypothesis. The delta displayed is based on the observed difference between test and control populations. The error bars visualize a confidence interval. A confidence interval is a range of probable values for the difference between groups. A future sample's 95% confidence interval contains the true value 95% of the time. In practice, the CI is a representative range of what the true value might be. These results are statistical interpretations, not facts: * If a result is not stat sig, this means you don't have sufficient evidence to reject the null hypothesis (i.e., based on your experiment design the observed result is reasonably likely to have happened by chance). * Generally, you should treat these results as a lack of evidence for your hypothesis * Underpowered tests may lead to neutral results even if a true effect exists * If a result is stat sig, this means that you have sufficient evidence to reject the null hypothesis (i.e., the probability that you would observe this result, or one more extreme, if the two groups' results were identical is below the pre-determined threshold you set). * Generally, you should treat this result as evidence for your hypothesis * Multiple comparisons (many metrics, rerunning an experiment, or grouping by dimensions) greatly increase the chance of seeing a stat sig result when there's *not* a true effect. Be wary of interpreting results when you see those behaviors! * A test that was extremely unlikely to succeed (such as a moonshot) with a stat sig result has a high chance of being a false positive. This is a strong signal, but consider reproducing the result, running a back-test, or reducing your significance level. After reviewing the scorecard results, use the all-metrics tab and custom queries for more information about your experiment. Examining more metrics increases the chance of a false positive, so a statistically significant movement in those views isn't necessarily a statistically sound interpretation. Use that section to look for unexpected large regressions and to generate follow-up hypotheses. ## Results are missing for some metrics This typically happens when your organization uses both the SDK or event imports and precomputed metrics imported from your data warehouse. Because these pipelines can run at different times, data availability may differ. Adjust your analysis date range to get a full view of your data. ## Your external source shows more exposure events than Statsig. Are data missing Statsig doesn't count exposures on the last day (the day you made a decision). Filter out that day when you analyze your external data. The hours that define a "day" for your project depend on the timezone you assigned to your project. ## You log categorical metadata for a custom event, but Pulse doesn't show these breakouts. What's wrong Pulse shows experimental results for metric sub-groups (for example, iOS vs. Android) only when you configure your metadata as a Dimension. [Value Dimensions](/experiments/interpreting-results/read-results#value-dimensions) are the most common dimension type because their metadata is logged directly with your custom events. Define value dimensions in your [custom event setup](/metrics/metric-dimensions). ## Why do I see "No dimensions available for this time range" This error appears when you try to view precomputed user dimensions, particularly after the first reload of the day. This happens because: * Statsig loads dimensions asynchronously in separate explore queries after the main scorecard results load. * The main experiment results appear first, while dimensions continue loading in the background. * Dimensions are typically available within a few minutes after the main scorecard loads. If you encounter this error, wait a few minutes and refresh the page to check whether the dimensions have finished loading. ================================================================================ # Reports URL: https://docs.statsig.com/statsig-warehouse-native/features/reports Updated: 2026-07-11 ================================================================================ ## How report summaries work Report summaries combine experiment results with context and discussion to produce long-term records of what you learned by running the experiment. Reports live on the Statsig console and can be exported as a PDF to your learnings library. {% figure %} ![Experiment report summary interface](/images/statsig-warehouse-native/features/reports/337c3619-a91b-4837-8d02-3db3b3299ef8.png) {% /figure %} Reports capture results and in-context discussion, and support embedding media, charts, and rich text formatting for deeper discussion and sharing of follow-ups from outside Statsig. {% figure %} ![Report editor with rich text content](/images/statsig-warehouse-native/features/reports/96f9eb43-ca69-4d9f-98f4-740294b5671f.png) {% /figure %} {% figure %} ![Embedded charts and discussions in report](/images/statsig-warehouse-native/features/reports/f6071323-58fe-45f0-b0a2-f532e5719ecd.png) {% /figure %} {% figure %} ![Report export preview showing PDF layout](/images/statsig-warehouse-native/features/reports/5c782533-fa67-47bc-b21c-0f8ec6740489.png) {% /figure %} ================================================================================ # Aggregated Impact URL: https://docs.statsig.com/statsig-warehouse-native/features/exploring-results/aggregated-impact Updated: 2026-07-11 ================================================================================ ## Metric Insights and Aggregated Impact Statsig's Insights page provides a clear view of how experiments and feature gates affect a specific metric. It helps answer questions such as "How much impact have I driven?" and serves as a tool for diagnosing unexpected changes in metrics. Insights presents a reverse perspective of the [Pulse](/experiments/interpreting-results/read-results) view. Pulse measures the impact of a new feature on all your metrics; Insights focuses on a single metric and identifies which tests are affecting it the most. This makes Insights useful for assessing your team's impact and setting realistic goals. ## How to read Insights 1. Navigate to the Insights section in the Statsig console: https://console.statsig.com/. Insights is also available in the insight tab for each metric. 2. Select a metric from the drop-down at the top of the page. 3. Select the ID type, time window, and other filters. 4. Based on the filters you choose, you can see the relative impact, topline impact, and projected launch impact for any experiment or gate that uses this metric. 5. Statsig also sums the projected launch impacts, adjusts based on false positive risk ('winner's curse'), and displays the result as the 'Aggregated Impact Estimate'. {% figure %} ![Insights dashboard showing aggregated impact estimates for a metric](/images/snippets/pulse/aggregated-impact/430563dc-4794-4d69-a314-36c76a6fcf74.png) {% /figure %} ## How the math works Refer to the [topline impact documentation](/experiments/statistical-methods/topline-impact/#computing-projected-launch-impact) for details on how topline and projected launch impact are calculated. To estimate false positive risk and calculate Aggregated Impact, Statsig uses the methodology in this [paper](https://dl.acm.org/doi/10.1145/3534678.3539160) which is widely adopted across the industry. Specifically: $$ Aggregated Impact=\sum\_\{i}\{(1 - FPR\_i) \times Projected Launch Impact\_i} $$ Where the [projected launch impact](/experiments/statistical-methods/topline-impact/) is an estimate of the topline impact assuming a decision is made and the test group is launched to all users; Statsig calculates the false positive risk using the following formula: $$ FPR\_i = \frac\{\alpha\_i \times \pi}\{\alpha\_i \times \pi + (1 - \beta\_i) \times (1 - \pi)} $$ In this formula, $\alpha\_i$ is the significance level for experiment i, $\beta\_i$ is the type II error, and 1 - $\pi$ is the prior success rate based on historical experiment results. ================================================================================ # WHN Statistics Overview URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics Updated: 2026-07-11 ================================================================================ Statsig's statistics engine is designed to deliver high-quality analysis of experiment results. The Stats Engine has four core values: Transparency, Trust, Flexibility, and Power. The pages below cover the Pulse Statistics Engine in detail, including how Statsig calculates results. ## Transparency Statsig documents all of its methodology, and the analysis is fully visible in Warehouse Native. Statsig provides the tools, data, and queries you need to reproduce the results you see in the console, with no hidden logic. Most enterprise data science teams validate results as part of their evaluation process. Transparency includes: * Console-based access to the SQL Statsig runs. You have access to the tables and queries on your warehouse, and Statsig surfaces SQL snippets in the console whenever it runs a query so you can verify what is being calculated. * Visibility into costs. Most customers can run pulse analyses for very low cost, and Statsig surfaces run time and resource utilization to help you manage your warehouse bill for experimentation. * Support: Statsig offers support with access to the data science team for open discussion of methodology, approaches, and collaborative development of new features. ## Trust Experiments drive important business decisions, and it's critical that you can trust the analysis and statistics being run. Statsig has a rigorous evaluation process for its methodologies, including peer review, simulations, and publishing the thought process behind statistical designs. You can trust Statsig's results to be accurate and reliable as they help guide your decisions. Trust includes: * Detailed blog posts on the rationale behind decisions in new features * Documentation of methodologies in blogs, with references to prior art * A full suite of diagnostic health checks on experiment results to warn you when statistical assumptions or data quality have been compromised ## Flexibility Experimentation isn't a one-size-fits-all tool. Depending on your industry, philosophy, or the setup of a specific experiment, Statsig lets you configure your analysis to suit your needs, offering: * Standard T-Tests * Sequential Testing * Bayesian Tests * Switchback Tests * Multi-armed bandits Statsig also offers many options to control for multiple comparisons, outliers, and regression adjustment. ## Power Effective experimentation relies on having trustworthy results quickly. Statsig has invested heavily in accuracy and power, so your results are faster and more reliable. Examples of features focused on the power of Statsig's stats engine: * [CUPED](https://www.statsig.com/blog/cuped): reduces experiment run times and accounts for pre-experiment bias * [Stratified Sampling](/experiments/advanced-setup/stratified-sampling): makes experiment results more accurate and consistent ================================================================================ # Confidence Intervals URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/confidence-intervals Updated: 2026-07-11 ================================================================================ Confidence intervals are an intuitive way to quantify the uncertainty in the observed metric deltas. A 95% confidence interval should contain the true effect 95% of the time: if you ran an experiment 100 times, the true value of the metric delta should fall inside the confidence interval 95 times. {% figure %} ![Confidence interval visualization showing statistical significance](/images/snippets/stats-methods/confidence-intervals/168382034-73afed14-d9f5-42cb-ade1-034526002a0b.png) {% /figure %} In practical terms, a 95% confidence interval that doesn't contain zero (the green bar above) represents a statistically significant result (with *α = 0.05*). This isn't always the case: there are cases when the p-value of the difference between test and control is statistically significant, but due to uncertainty in the control, a relative delta confidence interval may cross zero (using [The Delta Method](/experiments/statistical-methods/methodologies/delta-method)) or be represented as a point estimate (using [Fieller Intervals](/experiments/statistical-methods/methodologies/fieller-intervals)) while the absolute difference's p-value is statistically significant. Only 5% of the time would you expect to see the confidence interval exclude zero if the true effect was zero (a.k.a. a false positive). Larger confidence intervals imply less certainty in the exact size of the effect with a larger range of likely values. ## Computing confidence intervals Statsig calculates confidence intervals using a two-sample z-test. This test requires knowledge of the variance in the metric delta being measured, which Statsig derives differently depending on the type of metric (details [here](/experiments/statistical-methods/variance)). After establishing the variance of the delta, Statsig computes the confidence intervals. ### Two-sided tests For the **absolute metric delta**, the confidence interval is given by: $$ CI(\Delta \overline\{X}) = \Delta \overline\{X} \pm Z\_\{\alpha/2} \cdot \sqrt\{\{var(\Delta \overline\{X})}} $$ where: * $Z\_\{\alpha/2}$ is the z-critical value for the desired significance level (1.96 for the standard $\alpha=0.05$ and 95% confidence interval) for a two-sided test * $var(\Delta \overline\{X})$ is the variance of the absolute delta (details [here](/experiments/statistical-methods/variance)) The confidence interval for the **relative metric delta** can use one of two methods: [Fieller Intervals](/experiments/statistical-methods/methodologies/fieller-intervals) or the [Delta Method](/experiments/statistical-methods/methodologies/delta-method). Customers can choose either method in their Statsig console. Statsig recommends Fieller Intervals and enables them by default for all new customers. When using Fieller Intervals, the relative metric delta CI can be computed using: $$ CI(\\% \Delta \overline\{X} ) = \frac\{1}\{1-g} ( \frac\{\overline\{X\_T}}\{\overline\{X\_C}} - 1 \pm \frac\{Z\_\{\alpha/2}}\{\sqrt\{n\_C} \cdot \overline\{X\_C}} \sqrt\{(1-g) \cdot \frac\{var(X\_T)}\{n\_T(n\_T-1)} + \frac\{\overline\{X\_T} var(X\_C)}\{\overline\{X\_C} n\_C (n\_C-1)}}) $$ When using the Delta Method, the confidence interval is: $$ \begin\{split} CI(\Delta \overline X\\%) &= \Delta \overline X\\% \pm Z\_\{\alpha/2} \cdot\sqrt\{\{var(\Delta \overline X\\%)}}\\\\ &= \frac\{\Delta \overline X}\{\overline X\_c} \pm Z\_\{\alpha/2} \cdot\sqrt\{(\frac\{\overline X\_t}\{\overline X\_c})^\{2} \cdot (\frac\{var(X\_c)}\{n\_c \cdot \overline X\_c^2} + \frac\{var(X\_t)}\{n\_t \cdot \overline X\_t^2})} \cdot 100\\% \end\{split} $$ If using the Delta Method and the control mean isn't significantly away from zero, then it's simplified to: $$ \begin\{split} CI(\Delta \overline X\\%) &= \Delta \overline X\\% \pm Z\_\{\alpha/2} \cdot\sqrt\{\{var(\Delta \overline X\\%)}} \\\\ &= \frac\{\Delta \overline X}\{\overline X\_c} \pm Z\_\{\alpha/2} \cdot \frac\{\sqrt\{\{var\left(\Delta \overline X\right)}}}\{\overline X\_c} \cdot 100\\% \end\{split} $$ ### One-sided tests When running one-sided tests, the form of the confidence interval calculation changes slightly to account for a redistribution of desired false positive rate when looking for increases or decreases in the metric: $$ CI(\Delta \overline\{X}) = \begin\{cases} \left\[\Delta \overline\{X} - Z\_\{\alpha} \cdot \sqrt\{\{var(\Delta \overline\{X})}}, \quad +\infty \right) & \text\{if right-hand test}\\\\ \\\\ \left(-\infty, \quad \Delta \overline\{X} + Z\_\{\alpha} \cdot \sqrt\{\{var(\Delta \overline\{X})}} : \right] & \text\{if left-hand test} \end\{cases} $$ where: * $Z\_\{\alpha}$ is the z-critical value for the desired significance level (1.645 for the standard $\alpha=0.05$ and 95% confidence interval) for a one-sided test * $var(\Delta \overline\{X})$ is the same as for two-sided tests * the choice of confidence interval depends on if the one-sided test is looking for increases or decreases in the metric ## Welch's t-test for small sample sizes For small sample sizes, Statsig uses Welch's t-test instead of a standard z-test. Welch's t-test handles samples of unequal size or variance without increasing the false positive rate. The structure of the confidence interval calculation remains the same (depending on whether a 1- or 2-sided test is used), replacing the z-critical value with the t-critical value with degrees of freedom $\nu$. For a two-sided test, the confidence interval is therefore: $$ CI(\Delta \overline\{X}) = \Delta \overline\{X} \pm t\_\{\alpha/2} \cdot \sqrt\{\{var(\Delta \overline\{X})}} $$ $$ \nu = \frac\{\left(var(\overline X\_t) + var(\overline X\_c)\right)^2}\{\frac\{var(\overline X\_t)^2}\{N\_t - 1}+\frac\{var(\overline X\_c)^2}\{N\_c - 1}}\ \= \frac\{var(\Delta\overline\{X})^2}\{\frac\{var(\overline X\_t)^2}\{N\_t - 1}+\frac\{var(\overline X\_c)^2}\{N\_c - 1}} $$ Where $N\_t$ and $N\_c$ are the number of users in the test and control groups, respectively. For a large number of degrees of freedom, the t-statistic converges with the z-statistic. Therefore, Statsig uses Welch's t-test only when $\nu \< 100$. ## Comparing experiment data to a fixed baseline: one-sample t-test Sometimes you want to answer questions like "Does my test variant lead to a click-through rate higher than 0.5?" You can define a fixed-baseline comparison when adding metrics to the experiment. Statsig calculates the confidence interval as $$ CI(\Delta \overline X) = (\overline X\_\{group} - fixed \ value) \pm Z \cdot\sqrt\{\{var( \overline X\_\{group})}} $$ ================================================================================ # p-Value Calculation URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/p-value Updated: 2026-07-11 ================================================================================ In Null Hypothesis Significance Tests, the p-value is the probability of observing an effect at least as large as the measured metric delta, under the assumption that the null hypothesis is true. In practice, a p-value below the pre-defined Type I Error threshold ($\alpha$) serves as evidence of a true effect. The methodology for p-value calculation depends on the number of degrees of freedom ($\nu$). A two-sample z-test is appropriate for most experiments. Welch's t-test is used for smaller experiments with $\nu \< 100$. In both cases, the p-value depends on the metric [mean](/experiments/statistical-methods/metric-deltas) and [variance](/experiments/statistical-methods/variance) computed for the test and control groups. Typically, a p-value below threshold $\alpha$ occurs only when the confidence interval does not cross 0. However, this is not always the case in the Statsig UI. When the p-value of the difference between test and control is significant, the relative delta confidence interval may still cross zero (when using [The Delta Method](/experiments/statistical-methods/methodologies/delta-method)) or appear as a point estimate (when using [Fieller Intervals](/experiments/statistical-methods/methodologies/fieller-intervals)), while the absolute difference's p-value remains statistically significant. ## Two-Sample Tests ### Two-Sided z-Test You can compute the z-statistic (also called the z-score) of a two-sample z-test in multiple equivalent formats: $$ \begin\{split} Z &= \frac\{\overline X\_t - \overline X\_c}\{\sqrt\{var(\overline X\_t)+ var(\overline X\_c)}} \\\\ &= \frac\{\overline X\_t - \overline X\_c}\{\sqrt\{var(\Delta \overline\{X})}} \\\\ &= \frac\{\overline X\_t - \overline X\_c}\{\sqrt\{\sigma\_\{\overline\{X}\_t}^2 + \sigma\_\{\overline\{X}\_c}^2}} \end\{split} $$ where: * $Z$ is the observed z-statistic (not the z-critical value $Z\_\{\alpha/s}$) * $var(\Delta \overline\{X})$ is the variance of the absolute delta of means * $var(\overline\{X}\_i)$ is the variance of sample means either control or treatment group (details [here](/experiments/statistical-methods/variance)) * $\sigma\_\{\overline\{X}\_t}$ is the standard error of the mean of either control or treatment group (these are the terms you can find in Pulse under the Statistics tab of a metric) The two-sided p-value is obtained from the standard normal cumulative distribution function: $$ p-value = 2 \cdot \frac\{1}\{\sqrt\{2\pi}} \int \limits \_\{-\infty}^\{-|Z|}\{e^\{-t^2/2}dt} $$ ### Welch's t-test For smaller sample sizes, Welch's t-test is preferred because it produces lower false positive rates when group sizes and variances are unequal. In Pulse, Statsig automatically applies Welch's t-test when the degrees of freedom $\nu \< 100$. The t-statistic (also called the t-score) is computed identically to the two-sample z-statistic above. The degrees of freedom $\nu$ are computed using: $$ \nu = \frac\{\left(var(\overline X\_t) + var(\overline X\_c)\right)^2}\{\frac\{var(\overline X\_t)^2}\{N\_t - 1}+\frac\{var(\overline X\_c)^2}\{N\_c - 1}}\ := \frac\{var(\Delta\overline\{X})^2}\{\frac\{var(\overline X\_t)^2}\{N\_t - 1}+\frac\{var(\overline X\_c)^2}\{N\_c - 1}} $$ The p-value is then obtained from the t-distribution with $\nu$ degrees of freedom. ### One-Sided Z-Test The one-sided z-test computes the z-statistic $Z$ in the same way as the two-sided test. The one-sided p-value differs as follows: $$ p-value = \begin\{cases} 1 - \frac\{1}\{\sqrt\{2\pi}} \int \limits \_\{-\infty}^\{Z}\{e^\{-t^2/2}dt} &\text\{if right-hand test}\\\\ \frac\{1}\{\sqrt\{2\pi}} \int \limits \_\{-\infty}^\{Z}\{e^\{-t^2/2}dt} &\text\{if left-hand test} \end\{cases} $$ where: * $Z$ is computed as in the two-sided test. This uses the signed z-statistic, not the absolute value used in the two-sided p-value. ================================================================================ # Metric Deltas URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/metric-deltas Updated: 2026-07-11 ================================================================================ ## Computing metric deltas A metric delta refers to the difference in metric values between two groups, by default the test and control groups. This is the impact measured when evaluating experiment results. To account for the different number of users (or units) in each group, Statsig compares the mean metric value per user, not the total. {% callout type="note" %} **Selecting groups** All deltas are defined as the difference between a "treatment" group and a presumably unchanged "control" group. However, Statsig allows comparison between any two groups. {% /callout %} Pulse provides two different metric deltas. The **absolute delta** is the difference between the two means: $$ \Delta \overline\{X}=\overline\{X}\_t-\overline\{X}\_c $$ Understanding the impact relative to the baseline value of the metric is often helpful. For example, an absolute delta of +1 clicks/user has different meanings with a baseline value of 1 (+100% increase) vs. a baseline value of 100 (+1% increase). The **relative delta** is computed using the mean of the control group as the baseline: $$ \Delta \overline\{X} \\%=\frac\{\overline\{X}\_t-\overline\{X}\_c}\{\overline\{X}\_c} \times 100 \\% $$ {% callout type="note" %} If you reverse the order of group comparison in Pulse to be "control" vs "treatment", all deltas reverse and the direction of change inverts. {% /callout %} ## Computing means Computing group means correctly is critical for obtaining meaningful metric deltas. The methodology for calculating metric means depends on the metric type. ### Event count and sum metrics These metrics represent totals: number of times an event occurs, sum of time spent, total purchase amount, and similar values. The mean is the average user-level total during the analysis period. The mean value of the metric $X$ for a group is given by: $$ \overline\{X}=\frac\{1}\{N} \sum\_\{i=0}^N \sum\_\{d=0}^\{n\_i} X\_\{i, d} $$ where: * $N$ is the number of users in the group * $n\_i$ is the number of days during the analysis period that user $i$ was the experiment * $X\_\{i,d}$ is the metric value for user $i$ on day $d$ {% callout type="note" %} Statsig includes only user metrics recorded after a user has been exposed to the experiment in the group mean. {% /callout %} ### User accounting and Event User metrics (and legacy Event DAU) Event User metrics set to "Daily Participation Rate" capture the number of distinct users that have the event each day. In Pulse results, these values are normalized by the number of days the user is in the experiment. This represents the probability that a user is daily active for that event (the daily participation rate). The group mean is given by: $$ \overline\{X}=\frac\{1}\{N} \sum\_\{i=0}^N \frac\{1}\{n\_i} \sum\_\{d=0}^\{n\_i} X\_\{i, d} $$ where: * $X\_\{i,d}$ takes value 0 or 1 depending on if user $i$ has the event on a given day $d$. The following user accounting metrics are computed the same way: *DAU, WAU, MAU\_28day, L7, L14, L28* For new user accounting (*new\_DAU, new\_WAU, new\_MAU\_28day*), Statsig counts users that are new xAU at some point during the analysis window. The group mean is given by: $$ \overline\{X}=\frac\{1}\{N} \sum\_\{i=0}^N \max \left(X\_i\right) $$ Where $\max(X\_i)$ is the maximum value of the new xAU metric for user $i$. {% callout type="note" %} **event\_dau** metrics are now in legacy support only and Statsig no longer creates them for new events. Existing event\_dau metrics continue to be available for any of your new experiments and continue to be computed daily. For all new events, create an event\_user metric to measure daily active users. {% /callout %} ### Custom ratios, means, retention, and stickiness metrics These metrics include click-through rate, average purchase value, sessions per user, and similar values. Each is obtained by dividing a numerator value, $X$, by a denominator value, $Y$. The mean value of a ratio metric $R$ for an experiment group is given by: $$ \overline\{R}=\frac\{\frac\{1}\{N} \sum\_\{i=0}^N \sum\_\{d=0}^\{n\_i} X\_\{i, d}}\{\frac\{1}\{N} \sum\_\{i=0}^N \sum\_\{d=0}^\{n\_i} Y\_\{i, d}}=\frac\{\overline\{X}}\{\overline\{Y}} $$ Where $N$ is the number of users in the experiment group that participate in the metric, i.e. have a non-zero denominator value. $X\_\{i,d}$ and $Y\_\{i,d}$ are the $X$ and $Y$ values for user $i$ on day $d$. Different approaches exist for ratio metrics in experiments. Statsig selected this implementation because it's statistically sound and interpretable: * $R$ is the ratio of two means of independent observations: a set of user-level $X$ values and a set of user-level $Y$ values. The central limit theorem can therefore be used to separately obtain the summary statistics of $X$ and $Y$. * The group means are computed in the same way as the topline metric value, making the means easier to interpret and relate to the topline metric. ### Event User one-time event For custom **event\_user** metrics with "One-Time Event" selected, Statsig computes how many users have the event at any time after entering the experiment. Statsig doesn't normalize this result by the number of days a user is in the experiment. The group mean is given by: $$ \overline\{X}=\frac\{1}\{N} \sum\_\{i=0}^N X\_\{i} $$ where: * $N$ is the number of users in the group * $X\_\{i}$ takes value 0 or 1 depending on if user $i$ has the event at any point after entering the experiment ================================================================================ # Standard Error & Mean Variance URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/variance Updated: 2026-07-11 ================================================================================ The standard error (sometimes denoted "SE" or "std err") of the mean of each group is required to compute the confidence interval and p-value of a metric delta between those groups. You obtain the standard error of the mean by dividing the sample standard deviation of $X$ by the square root of the number of users in the group. $$ \sigma\_\{\overline X} = \frac\{\sigma\_\{X}}\{\sqrt\{N}} = \sqrt\{\frac\{var(X)}\{N}} = \sqrt\{var(\overline\{X})} $$ Standard deviation is the square root of variance. Because variances are easier to manipulate algebraically, Statsig derives the variance for each metric type and then takes the square root to obtain the confidence intervals. Pulse shows the standard error of the mean of each group alongside the units and mean of each group. ## Computing Variance The variance of the absolute metric delta is the sum of the variances of the test and control means: $$ var(\Delta \overline X) =var(\overline X\_t - \overline X\_c) = var(\overline X\_t) + var(\overline X\_c) $$ Computing this correctly requires accurate variance of the means for each group. ### Count and Sum Metrics For count and sum metrics, the variance of the sample mean for a given group is obtained directly from the sample variance: $$ var(\overline\{X}) = \frac\{var(X)}\{N} = \frac\{\frac\{1}\{N-1}\sum\_\{i=0}^\{N}(X\_i-\overline\{X})^2}\{N} $$ Where: * $N$ is the number of users in the group * $X\_i$ is the metric value for user $i$ * $\overline\{X}$ is the user-level average of $X$ for users in that group ### Ratio and Mean Metrics Ratio and mean metrics combine multiple variables $X$ and $Y$ rather than a single variable $X$. The variance of these metrics depends on both the numerator and denominator variables, which are typically correlated. For metric $R$, the group mean $\overline\{R}$ and group variance of the mean $var(\overline\{R})$ must account for this correlation. For example, in a *clicks per session* metric, the number of clicks and the number of sessions come from the same group of users and aren't independent. To account for any correlation, Statsig computes the variance of the mean of a ratio metric $R$ using the delta method: $$ var(\overline R) = var\left(\frac\{\overline X}\{\overline Y}\right) := \left(\frac\{\overline X}\{\overline Y}\right)^2 \cdot \left(\frac\{var(\overline X)}\{\overline X^2} + \frac\{var(\overline Y)}\{\overline Y^2} - 2 \cdot \frac\{covar(\overline X, \overline Y)}\{\overline X\cdot \overline Y} \right) $$ where the variance of the numerator and denominator means are computed in the same way as detailed above for count metrics, and the covariance is $$ covar(\overline X, \overline Y) = \frac\{covar(X, Y)}\{N} = \frac\{\frac\{1}\{N-1}\sum\_\{i=0}^\{N}(X\_i-\overline X)\cdot (Y\_i-\overline Y)}\{N} $$ ================================================================================ # Topline and Projected Impact URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/topline-impact Updated: 2026-07-11 ================================================================================ The **topline impact** is the average daily effect that an experiment has on the overall metric value, measured between two groups. The **projected launch impact** is an estimate of the daily global metric impact expected if the test group is launched to all users (beyond those in the experiment). This impact is computed relative to the expected baseline metric value without the experiment running. Statsig shows both Topline Impact and Projected Launch Impact in absolute and relative units. Neither uses CUPED, because CUPED already adjusts for pre-exposure data (the starting point for topline metrics), and combining the two would double-count that adjustment. {% callout type="tip" %} **Example**: Take a simple example experiment with a Control group of 1000 users and a Test group of another 1000 users, which ran for 30 days. For an **event\_count** metric, we observed an Experiment Delta of +1.0 events per user (abs). The Topline Impact for this metric would be +33.33 events per day (abs). {% /callout %} ## Computing Topline Impact The topline impact is computed over the total duration of the experiment, which produces the most accurate estimate and tight confidence interval. The calculation depends on whether the metric represents an absolute quantity or a ratio: ### Count and Sum Metrics (event\_count, sum) The absolute topline impact is derived from the experiment results. It depends on the difference in means between test and control and the average number of users in the test group per day. $$ Impact\_\{abs}=(X\_t-X\_c) \cdot N\_t / n\_\{days} $$ Using the absolute impact and the overall metric value (as shown in the [metrics dashboard](/metrics/console)), Statsig computes the relative impact: the percentage change in the overall metric value over the rollup window attributed to the active experiment. $$ Impact\_\{rel}=\frac\{Impact\_\{abs}}\{Topline\_Value-Impact\_\{abs}} \times 100\\% $$ ### Ratio and Mean Metrics To derive the topline impact on a ratio metric, Statsig first determines the impact on the numerator (*X*) and denominator (*Y*) separately. The topline impact is the current ratio metric value minus the baseline value obtained by subtracting the numerator and denominator impacts: $$ Impact\_\{abs}=\frac\{Topline\_X}\{Topline\_Y}-Baseline\_Value $$ Where the baseline value is the expected value of the topline metric if the experiment wasn't running: $$ Baseline\_Value=\frac\{Topline\_X-(\bar\{X\_t}-\bar\{X\_c}) \cdot N\_t}\{Topline\_Y-(\bar\{Y\_t}-\bar\{Y\_c}) \cdot N\_t} $$ The relative impact for ratio metrics is obtained by dividing the absolute impact by the baseline value: $$ Impact\_\{rel}=\frac\{Impact\_\{abs}}\{Baseline\_Value} \times 100\\% $$ ## Computing Projected Launch Impact The layer allocation of the experiment and the size of the test group are used to estimate a scaling factor *m*, which represents the increase in absolute impact expected when the test group is launched. The launch factor over a rollup window is calculated as $$ m\_\{rollup}=\frac\{1}\{\sum\_\{1}^\{rollup}\{layer\_alloc \times group\_pct}} \times rollup $$ to accommodate changes in allocation during the experiment. {% callout type="note" %} The targeting gate isn't factored in. The projected impact calculation assumes that the target gate remains the same after you launch the experiment. {% /callout %} ### Count and Sum Metrics (event\_count, event\_dau, sum) For count and sum metrics, the projected absolute impact is the current topline impact scaled by factor *m*. For example, consider an experiment running at 50% layer allocation with a 50/50 test/control split, so that 25% of all users are in the test group. If the allocation changed during the experiment, Statsig uses a weighted average based on historical allocations. If the topline impact is +10 events per day, launching the experiment would produce +40 events per day. $$ Projected\_\{abs}=Impact\_\{abs} \times m $$ The relative projected impact is the expected percentage change in the topline metric, relative to the baseline metric value without the experiment running. $$ Projected\_\{rel}=\frac\{Projected\_\{abs}}\{Topline\_Value-Impact\_\{abs}} \times 100\\% = Impact\_\{rel} \times m $$ ### Ratio and Mean Metrics The projected impact of ratio metrics depends on the numerator and denominator impacts in the same way as the topline impact calculation. The same scaling factor *m* is used to obtain the projected impact for each term: $$ Projected\_\{abs}=\frac\{Topline\_X+(m-1) \cdot (\bar\{X\_t}-\bar\{X\_c}) \cdot N\_t}\{Topline\_Y+(m-1) \cdot (\bar\{Y\_t}-\bar\{Y\_c}) \cdot N\_t} - Baseline\_Value $$ Where the first term represents the projected metric value after launch. Finally, the projected relative impact of a ratio metric is the projected absolute impact divided by the baseline value of the ratio: $$ Projected\_\{rel}=(\frac\{Projected\_\{abs}}\{Baseline\_Value}) \times 100\\% $$ ## Confidence intervals The confidence intervals for topline and projected impact are computed using the same method as the [confidence intervals](/experiments/statistical-methods/confidence-intervals) for experiment deltas. $$ CI(Impact) = Impact \pm Z \cdot \sqrt\{var(Impact)} $$ For the absolute impact of count and sum metrics, the variance is a linear combination of the test and control variances: $$ var(Impact\_\{abs})=\[var(\bar\{X\_t})+var(\{\bar\{X\_c}})] \cdot N\_t^2 $$ And for projected launch impact: $$ var(Projected\_\{abs})=var(Impact\_\{abs}) \cdot m^2 $$ For ratio metrics and relative impacts, Statsig calculates the variance using the Delta method. This accounts for the correlation between numerator and denominator terms by using Taylor expansion to linearize expressions containing non-linear combinations of experiment variables. For example, the variance in the relative impact of a count metric is given by: $$ var(Impact\_\{rel})=var(Impact\_\{abs}) \cdot \frac\{(Topline\_Value - 2 \cdot Impact\_\{abs})^2}\{(Topline\_Value - Impact\_\{abs})^4} $$ ================================================================================ # Variance Reduction URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/variance-reduction Updated: 2026-07-11 ================================================================================ ## Variance reduction [Variance](/experiments/statistical-methods/variance) measures the dispersion (or "noise") in a metric or experiment results. Higher variance produces larger confidence intervals, requiring more sample to detect a statistically significant result for the same effect size. Lower variance reduces required sample size, which leads to shorter experiment run times. Statsig uses a form of CUPED based on a [2013 Microsoft paper](https://www.exp-platform.com/Documents/2013-02-CUPED-ImprovingSensitivityOfControlledExperiments.pdf) (Deng, Xu, Kohavi, & Walker). Statsig automatically applies CUPED to experiments and runs it for the topline results on key metrics in Pulse, producing significant variance reduction for most metrics. Go to the [CUPED launch post](https://blog.statsig.com/cuped-on-statsig-d57f23122d0e) for more details. ## CUPED - Controlled-experiment Using Pre-Existing Data CUPED (Controlled-experiment Using Pre-Existing Data) uses user information from before an experiment to reduce variance and increase confidence in experimental metrics. In Statsig, the pre-experiment data window is the 7 days before each user's exposure, rather than a fixed window before the experiment starts. This helps debias experiments where groups were randomly different before any treatment was applied. The Cloud product uses stratification alongside CUPED to account for users who may not have pre-experiment data. Users are grouped into strata based on available pre-experimentation information. Treatment and control effects are estimated within each stratum, then aggregated to produce an overall result. Standard difference-in-means and variance estimation is then applied. This approach retains users with missing pre-data while still benefiting from variance reduction where applicable. ## Winsorization Another technique for reducing noise is Winsorization, which manages the influence of outliers. Winsorization measures the percentile *P<sub>x</sub>* of a metric and sets all values over *P<sub>x</sub>* to *P<sub>x</sub>*. This reduces the influence of extreme outliers caused by factors such as logging errors or bad actors. ## Metric selection The metrics you use can dramatically influence the sensitivity of your analysis. The transformations above, along with techniques like creating threshold-based flags, let you trade exact numbers for significantly more statistical power. Go to the [Statsig blog post on variance reduction](https://www.statsig.com/blog/understanding-and-reducing-variance-and-standard-deviation) for more information. ## Related resources * [Deng, Xu, Kohavi, & Walker](https://exp-platform.com/Documents/2013-02-CUPED-ImprovingSensitivityOfControlledExperiments.pdf): the seminal paper on using CUPED for online controlled experiments * [Booking.com on CUPED](https://booking.ai/how-booking-com-increases-the-power-of-online-experiments-with-cuped-995d186fff1d): theory and practice of CUPED * [Improving the Sensitivity of Online Controlled Experiments: Case Studies at Netflix](https://www.kdd.org/kdd2016/papers/files/adp0945-xieA.pdf) ================================================================================ # Pre-Experiment Bias URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/pre-experiment-bias Updated: 2026-07-11 ================================================================================ In some cases, users in two experiment groups have meaningfully different average behaviors before any intervention is applied. If this difference persists after the experiment starts, the analysis may attribute that pre-existing difference to the intervention, making a result appear more or less impactful than it is. [CUPED](/experiments/statistical-methods/methodologies/cuped) helps address this bias, but can't fully account for it. Some metrics, such as retention, aren't viable candidates for CUPED and can't be easily adjusted. Statsig measures the pre-experiment values of all scorecard metrics for all experiment groups and determines whether the values are significantly different and could cause misinterpretations. If Statsig detects bias, it notifies users and places a warning on relevant Pulse results. ### How it works Statsig provides a "Days Since Exposure" view to help identify novelty effects and pre-experiment effects. For example, the test group in the following experiment had a consistently higher mean than the control group in the week before the experiment started: {% figure %} ![Pre-experiment bias visualization showing test group with consistently higher mean than control group](/images/snippets/stats-methods/pre-experiment-bias/246545035-22ff2db6-9c08-4227-a53d-8faa8feb5e92.png) {% /figure %} Statsig detects this bias by running the standard [pulse](/experiments/interpreting-results/read-results) calculation on the pre-experiment term (looking back one week in Cloud, and the configured CUPED lookback window in Warehouse Native), then calculating the p-value for the null hypothesis that the groups are identical. Statsig flags relevant results using logic that balances awareness against false positives from large numbers of scorecard metrics or groups. ### What to Do Pre-experiment bias can occur by chance and isn't always a major issue. * If the total delta is small, it may not meaningfully influence the interpretation of results. * If CUPED can account for the bias, the bias shouldn't affect results. In many cases, treat this warning as informational and proceed, applying extra scrutiny to impacted metrics. This is appropriate when the metric isn't critical to the experiment or when directional movement matters more than the exact value. Additional experiment time may also reduce the bias if no systemic source exists, because new users dilute the imbalance. If the metric is critical and the exact numerical value matters, consider resalting and restarting the experiment. ================================================================================ # Bonferroni Correction URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/methodologies/bonferroni-correction Updated: 2026-07-11 ================================================================================ ## What is Bonferroni correction A Bonferroni Correction is a statistical method that reduces the probability of false positives by adjusting the significance level for multiple comparisons. If you run a test with α = 0.05, the probability of a false positive is 5%. Running more comparisons at the same significance level increases the chance of at least one false positive, because each comparison is an additional opportunity for a false positive. Bonferroni corrections are an optional feature on Statsig experiments that reduce the probability of Type I errors (false positives) by adjusting the significance level (α). Statsig divides the significance level by the number of comparisons being evaluated. You can choose to apply these based on one or both of the following: * The number of test groups (multiple treatment hypotheses). Statsig divides the significance level by the number of variants being compared against control. * The number of metrics in the scorecard. Here you may select what percentage of your total α Statsig divides evenly among the Primary Metrics, and Statsig splits the remaining α equally among Secondary Metrics. For example: * Significance level of 0.05 * 2 Primary Metrics and 4 Secondary Metrics * 60% of α applied to Primary Metrics * Each Primary Metric is calculated with α = 0.6 \* 0.05 / 2 = 0.015 * Each Secondary Metric is calculated with α = 0.4 \* 0.05 / 4 = 0.005 * If both corrections are selected, Statsig applies them on top of each other. In the example above, to also correct for having 2 test groups, further divide each α by 2. When analyzing dimensions, if correction for metrics is enabled, Statsig applies it separately for the dimensional breakdown. Statsig uses the number of dimensions as the total metric count to correct for *in the dimensional analysis*, but this doesn't impact topline metrics. {% figure %} ![Bonferroni correction configuration interface](/images/snippets/stats-methods/bonferroni-correction/038d75eb-5745-4587-b180-86b88594ccb9.png) {% /figure %} ## How experiment metrics appear after applying Bonferroni correction In the experiment scorecard section, Statsig derives confidence intervals from (1 - adjusted α) for applicable metrics. Hovering over a confidence interval displays the adjusted α alongside other relevant metric details. In the experiment explore section, Statsig calculates a new adjusted α based on your selections, and the confidence intervals use (1 - adjusted α). ================================================================================ # Benjamini–Hochberg URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/methodologies/benjamini-hochberg-procedure Updated: 2026-07-11 ================================================================================ ## What the Benjamini-Hochberg procedure is The Benjamini-Hochberg Procedure ("BH" procedure) is a statistical method that reduces the probability of false positives by adjusting the significance level for multiple comparisons. It isn't as extreme as a [Bonferroni Correction](/experiments/statistical-methods/methodologies/bonferroni-correction), because instead of controlling the chance of at least one false positive (Family Wise Error Rate), BH controls the expected value of false positives when the null hypothesis has been rejected (False Discovery Rate). You can enable the BH procedure for individual experiments, or configure global Experiment Settings to enable it by default. {% figure %} ![Benjamini-Hochberg procedure configuration interface](/images/snippets/stats-methods/benjamini-hochberg-procedure/c865494e-0ae4-489c-a416-45848b4d10bc.png) {% /figure %} ## Methodology The [Benjamini-Hochberg Procedure](https://www.statisticshowto.com/benjamini-hochberg-procedure/) updates the significance level (modifying your pre-set $\alpha$). Statsig calculates the new significance level by sorting the p-values of metrics in ascending order and comparing each with a paired threshold. Each p-value’s paired threshold is the desired False Discovery Rate divided by the number of comparisons being evaluated, multiplied by the rank of that p-value in the ordered list. The largest threshold value that is higher than its corresponding p-value becomes the new significance level ($\alpha$). You can apply the Benjamini-Hochberg Procedure based on: * The number of test groups (multiple treatment hypotheses). For each metric aggregate the list of p-values from each variant and complete the Benjamini-Hochberg procedure. * The number of metrics in the scorecard. For each variant aggregate the list of p-values from each metric and complete the Benjamini-Hochberg procedure. * Both the number of test groups and number of metrics in the scorecard. Statsig aggregates all p-values to complete the Benjamini-Hochberg procedure. Statsig doesn't apply the BH procedure when evaluating the p-values of any event-dimension or user-property experiment metric results. Only the top-line metric results are compared to the new significance level. ## How experiment metrics appear after applying Benjamini-Hochberg In the experiment scorecard section, Statsig derives confidence intervals from (1 - adjusted α) for applicable metrics. Hovering over a confidence interval displays the adjusted α alongside other relevant metric details. In the experiment explore section, Statsig calculates a new adjusted α based on your selections, and the confidence intervals use (1 - adjusted α). ================================================================================ # CUPED URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/methodologies/cuped Updated: 2026-07-11 ================================================================================ ## CUPED: Controlled-experiment using pre-existing data CUPED (Controlled-experiment Using Pre-Existing Data) is a technique that uses user information from before an experiment to reduce variance and increase confidence in experimental metrics. At Statsig, this pre-experiment data is defined as the 7 days before each user's exposure, rather than a fixed window before the experiment starts for all users. This helps to debias experiments that have meaningful pre-exposure bias (for example, groups that were randomly different before any treatment was applied). The Cloud product uses a 7-day window for CUPED calculation. For Warehouse Native customers, a 7-day window is recommended, but you can customize it to any length. For more information, go to the [Variance Reduction](/experiments/statistical-methods/variance-reduction) page. For an in-depth look at the methodology, refer to <a href="https://drive.google.com/file/d/1cLXDhwK4jcu5Oss2rVGHtp22LxVLLzBC/view?usp=sharing" target="_blank" rel="noreferrer">CURE by Statsig</a>. ## CUPED for simple aggregations The methodology for simple aggregations is described in the original [Microsoft paper](https://www.exp-platform.com/Documents/2013-02-CUPED-ImprovingSensitivityOfControlledExperiments.pdf) and in Statsig's in-depth [article](https://www.statsig.com/blog/cuped) on the technique. The Cloud product uses stratification alongside CUPED to account for users who may not have pre-experiment data. Users are grouped into strata based on available pre-experimentation information. Treatment and control effects are first estimated within each stratum, then aggregated to produce an overall result. The standard difference-in-means and variance estimation is then applied. This approach retains users with missing pre-data while still benefiting from variance reduction where applicable. ## CUPED for ratio metrics The Microsoft paper also gives details on how to implement CUPED for a different analysis unit (Appendix B). Statsig extends this to work for ratio metrics, where each experiment unit has a numerator and a denominator. The variance reduction process works by finding the variance of experiment data, pre-experiment data, and the covariance between the two. Denote the numerator, denominator, pre-experiment numerator, and pre-experiment denominator of a unit as $Y$, $N$, $X$, and $M$, respectively. Using the CUPED-reduced variance formula, $$ Var(\frac\{Y\_\{cv}}\{N\_\{cv}})=Var(\frac\{Y}\{N})+\theta^2 Var(\frac\{X}\{M})-2\theta Cov(\frac\{Y}\{N}, \frac\{X}\{M}) $$ where optimal $\theta$ is found as $$ \frac\{Cov(\frac\{Y}\{N}, \frac\{X}\{M})}\{Var(\frac\{X}\{M})} $$ expanded to \\ $$ \frac\{Cov(\frac\{Y}\{\mu\_N}-\frac\{\mu\_Y N}\{\mu^2\_N}, \frac\{X}\{\mu\_M}-\frac\{\mu\_X M}\{\mu^2\_M})}\{Var(\frac\{X}\{\mu\_M}-\frac\{\mu\_X M}\{\mu^2\_M})} $$ From this: $$ \frac\{\hat\{Y\_\{c}}}\{\hat\{N\_\{c}}}=\frac\{Y\_\{c}}\{N\_\{c}}-\theta( \frac\{X\_\{c}}\{M\_\{c}} - \mathbb\{E}\[R]) $$ $$ \frac\{\hat\{Y\_\{t}}}\{\hat\{N\_\{t}}}=\frac\{Y\_\{t}}\{N\_\{t}}-\theta( \frac\{X\_\{t}}\{M\_\{t}} - \mathbb\{E}\[R]) $$ Because $\mathbb\{E}\[R]$ is difficult to derive and the expectation term is the same for both groups, Statsig substitutes $\mathbb\{E}\[R]$ with $\frac\{X\_\{c}}\{M\_\{c}}$, transforming the formulas above to: $$ \frac\{Y\_\{cv}(control)}\{N\_\{cv}(control)}=\frac\{Y(control)}\{N(control)} $$ $$ \frac\{Y\_\{cv}(test)}\{N\_\{cv}(test)} \\\\ :=\frac\{Y(control)}\{N(control)} - (\frac\{Y(control)}\{N(control)} - \theta \frac\{X(control)}\{M(control)}) + (\frac\{Y(test)}\{N(test)} - \theta\frac\{X(test)}\{M(test)}) \\\\ :=\frac\{Y(test)}\{N(test)} - \theta\frac\{X(test)}\{M(test)} + \theta \frac\{X(control)}\{M(control)} $$ Using the optimal $\theta$, Statsig reduces group-level variance by plugging the parameter back in to calculate the adjustment. Across-group $\theta$ doesn't necessarily reduce variance for one group, or the sum of variances of all groups, but in most cases it does. Simulation shows that 98.3% of metrics saw a decrease with CUPED. Statsig uses CUPED variance when all of the following conditions hold: * Core assumptions of the CUPED model are satisfied; this can be violated due to rounding error or other data artifacts * E(X\_hat) = E(X) * The pooled variance of the adjusted population across groups is \< the variance of the unadjusted population * Enough units have pre-experiment values (> 100) * Enough percentage of units have pre-experiment values (> 5%) ================================================================================ # Delta Method URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/methodologies/delta-method Updated: 2026-07-11 ================================================================================ ## Delta method for ratio metrics Statsig uses the delta method when calculating the variance for variables that have a numerator and denominator. The variance of ratio and mean metrics depends on the numerator and denominator variables, which are typically correlated. For example, consider a *clicks per session* metric. The number of clicks and the number of sessions are two sets of observations coming from the same group of users, so they aren't independent of each other. To properly account for this correlation, Statsig obtains the variance of a ratio metric *R* using the delta method: {% figure %} ![Delta method variance formula](/images/snippets/stats-methods/delta-method/167956015-cc3f9fca-2c4d-410c-bff1-3f13dd16d105.png) {% /figure %} where the variance of the numerator and denominator means are computed in the same way as detailed above for count metrics, and the covariance is {% figure %} ![Covariance calculation formula](/images/snippets/stats-methods/delta-method/167956127-c17017ef-07b2-4f76-88c4-00539eec50a7.png) {% /figure %} ## Delta method for relative lifts Statsig may also use the delta method when calculating the confidence interval for relative lifts. The other methodology for calculating confidence intervals for relative lifts is [Fieller Intervals](/experiments/statistical-methods/methodologies/fieller-intervals). The delta method is a heuristic for Fieller Intervals that converges with a large population. ================================================================================ # Fieller Intervals URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/methodologies/fieller-intervals Updated: 2026-07-11 ================================================================================ ## Fieller intervals You can use Fieller Intervals as the methodology for calculating confidence intervals for the relative change between test and control group. The Delta Method is an approximation for the variance of a ratio between two variables that is then used to establish a confidence interval. Fieller Intervals are an exact solution for the confidence interval. In most cases, Fieller Interval results are very similar to results from the Delta Method. Because Fieller Intervals are more accurate, Statsig recommends using this methodology. ## Calculation ### 1: Determine if a Fieller interval is well-defined Before applying Fieller’s Theorem, Statsig checks that the denominator of the relative lift metric $\overline\{X\_C}$ is significantly distinct from 0. Statsig calculates the parameter $g$: $$ g = \frac\{Z\_\{\alpha/2}^2 \cdot \mathrm\{var}(X\_C)}\{(n\_C-1) \cdot \overline\{X\_C}^2} $$ Where: $Z\_\{\alpha/2}$ is the critical value associated with the desired confidence level $\mathrm\{var}(X\_C)$ is the variance of the control group metric values $n\_C$ is the number of units in the control group $\overline\{X\_C}$ is the mean of the control group metric values When $g$ \< 1, the control mean is significantly different from 0, and Fieller intervals apply. ### 2A: Apply Fieller interval formula Since the control and test group results are independent of each other, covariance terms in Fieller's Theorem can be dropped. $$ CI(\\% \Delta \overline\{X} ) = \frac\{1}\{1-g} \left( \frac\{\overline\{X\_T}}\{\overline\{X\_C}} \pm \frac\{Z\_\{\alpha/2}}\{\overline\{X\_C}} \sqrt\{ \frac\{\overline\{X\_T}^2}\{\overline\{X\_C}^2} \cdot \frac\{\mathrm\{var}(X\_C)}\{n\_C-1} + (1-g)\frac\{\mathrm\{var}(X\_T)}\{n\_T-1} } \right) - 1 $$ ### 2B: Edge case: control mean not statistically distinct from zero In rare cases (less than 5% of observed metric comparisons on Statsig), g $\geq$ 1, which means the control group’s mean isn’t statistically distinguishable from 0. When $\overline\{X\_C}$ isn't statistically different from zero, the denominator of the relative lift calculation is unstable. This means that the confidence interval for the percent difference between test and control is unbounded. When this happens, Statsig surfaces the relative lift observed during the experiment. $$ \\% \Delta \overline\{X} = \frac\{\overline\{X\_T}-\overline\{X\_C}}\{\overline\{X\_C}} $$ ## Enabling Fieller intervals in Statsig Configure the relative confidence interval methodology in *Experimentation Settings* at the organization level. Changing this setting only affects experiments created after the setting change. {% figure %} ![Experimentation settings configuration interface](/images/snippets/stats-methods/fieller-intervals/9c8b90f8-1f0b-472f-883d-c3f30bd78696.png) {% /figure %} In many cases, the results are effectively the same as using the [Delta Method](/experiments/statistical-methods/methodologies/delta-method). However, if you are running experiments with small sample sizes or noisy denominators, Fieller Intervals are more reliable, and Statsig strongly recommends using them. In the experiment scorecard, Fieller Intervals appear as shown below. {% figure %} ![Experiment scorecard with Fieller intervals](/images/snippets/stats-methods/fieller-intervals/1cb4ebb6-0425-49bf-a319-48794d25ce96.png) {% /figure %} ================================================================================ # SRM Checks URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/methodologies/srm-checks Updated: 2026-07-11 ================================================================================ ## How SRM checks work Sample ratio mismatch (SRM) occurs when the observed allocation of **unique** users between test groups differs from the expected allocation or "split" of the test. Go to the [Statsig blog post on SRM](https://www.statsig.com/blog/sample-ratio-mismatch) for background. SRM is a signal that there could be unknown bias in the test. Unless you can clearly diagnose the reason for the imbalance, there's no reliable way to know how much this bias affects your results. Statsig runs SRM checks on all experiments and feature gates as part of Health Checks (described in [Health Checks](/experiments/monitor)). Statsig uses a Chi-squared test to identify whether the split of users between groups indicates a sample ratio mismatch. {% figure %} ![SRM health check results interface](/images/experiments/srm-checks-health.png) {% /figure %} Statsig automatically analyzes data by common dimensions logged by the Statsig SDK to identify potential drivers of SRM. These include sdk\_type, sdk\_version, reason, is\_bot, browser\_name, browser\_version, os, os\_version, and region. {% figure %} ![SRM dimension analysis breakdown](/images/experiments/srm-checks-dimensions.png) {% /figure %} ================================================================================ # Winsorization in Warehouse Native URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/methodologies/winsorization Updated: 2026-07-11 ================================================================================ Statsig Warehouse Native lets you configure winsorization per metric to cap extreme values and reduce outlier noise in experiment results. Winsorization measures the percentile Px of a metric and sets all values over Px to Px. Statsig computes the Px value using all non-zero and non-null unit-level values of the metric; metrics are aggregated from rows or events, and then the Px'th unit's value is used as the threshold to adjust other units' values. {% tabs %} {% tab title="Statsig Cloud" %} At Statsig, the default percentile for winsorization is 99.9%. This reduces the influence of extreme outliers caused by factors such as logging errors or bad actors. Statsig applies winsorization to sum, event count, mean, ratio, and funnel metrics, including imported metrics. Statsig doesn't apply winsorization to Participation or User Accounting metrics. {% /tab %} {% tab title="Warehouse Native" %} Statsig Warehouse Native lets you configure winsorization per metric and choose the upper and lower bounds to apply. {% figure %} ![Winsorization configuration interface](/images/snippets/stats-methods/winsorization/6d058842-27f7-4b6b-9bd8-245a5f894f90.png) {% /figure %} Statsig applies winsorization to sum, event count, mean, and ratio metrics. {% /tab %} {% /tabs %} ## Metric capping Metric capping is an effective technique for handling outliers. You can define maximum values for a metric for any unit type configured for that metric. Statsig automatically adjusts any value exceeding the cap downward to the cap value. For example, if purchases greater than $10,000 per day shouldn't skew analysis, Statsig adjusts any transaction exceeding that threshold downward to the limit. Metric capping supports Event Count and Aggregation (sum) metric types. ================================================================================ # One-Sided Tests URL: https://docs.statsig.com/statsig-warehouse-native/features/statistics/methodologies/one-sided-test Updated: 2026-07-11 ================================================================================ ## One-sided tests (also known as one-tailed tests and non-inferiority tests) A one-sided test lets you test for a metric moving in only one direction, which you specify in advance. This trade-off gives you additional sensitivity (or power). It differs from the standard Pulse results, which show two-sided results by default. Use cases for one-sided testing include detecting regressions in guardrail metrics and testing for a change where only one direction has meaningful business impact. For example, you may be less interested in detecting whether a new feature reduces crash rates, but very interested in learning whether the new feature increases crash rates. In that case, you are willing to forgo detecting the reduction in favor of better detecting the increase. {% callout type="info" %} One-sided tests completely disregard the possibility of detecting the metric moving in the direction that isn't specified, but they give you higher sensitivity in the direction you are looking (which allots all your alpha to testing statistical significance in the one direction of interest). This results in one-sided confidence intervals (CIs) that are narrower in the direction of interest than their two-sided counterparts. {% /callout %} ## How to enable one-sided tests When setting up an experiment and identifying metrics to measure, the default setting is to run a two-sided test. To change this, click the metric name on the experiment setup screen. A popup opens where you can modify the test type and indicate the direction you want to measure. {% callout type="note" %} V1 doesn't support Bayesian testing yet. {% /callout %} {% figure %} ![One-sided test configuration interface](/images/snippets/stats-methods/one-sided-test/23044f21-6249-4fc1-9895-22111bb16010.png) {% /figure %} ## How to read one-sided test results Metrics using one-sided tests appear in Pulse very similarly to two-sided tests. The only difference is that Statsig shows a one-sided CI rather than a two-sided CI. One-sided CIs can be confusing at first. They extend to infinity or negative infinity, which is expected because Statsig only detects changes in the specified direction. As with all CIs, they indicate that the real mean value of the metric likely falls within this range. Because the CI for a one-sided metric is wide, it is equally useful to read the results as indicating high confidence that the mean value doesn't fall outside the CI. {% figure %} ![One-sided confidence interval visualization](/images/snippets/stats-methods/one-sided-test/7ab3d680-b4c4-4321-b1fc-cc506b0ab21b.png) {% /figure %} ## FAQ #### Why can't I just run two one-sided tests Running two one-sided tests would result in a less powerful test. One-sided tests work by allocating the entirety of Type I error (alpha/significance) to one direction. Adding a one-sided test in the other direction reintroduces the chance of making a Type I error in that direction. The result is confidence intervals that appear tighter but actually produce higher rates of decision error than the specified confidence level (default: 95%). #### Why use a one-sided test rather than a two-sided test The choice depends on your use case, metric of interest, and the business impact of any decision. Choose one-sided tests when you plan to act only on changes in one direction and detecting changes in the other direction has no business value. ================================================================================ # CURE URL: https://docs.statsig.com/statsig-warehouse-native/cure/introduction Updated: 2026-07-11 ================================================================================ CURE (variance Control Using Regression Estimates) is Statsig's extended implementation of [CUPED](/experiments/statistical-methods/methodologies/cuped), a technique that uses pre-experiment data for each experimental unit to control variance observed in an online experiment. CUPED can reduce variance moderately to significantly and can correct for some pre-existing differences between experiment groups. CUPED provides additional statistical power without requiring additional users or experiment runtime. However, CUPED has one major limitation: it relies on pre-experiment data for a given metric to reduce that metric's variance. This means that: * New user experiments can't leverage the technique * Metrics which aren't autocorrelated don't see significant variance reduction ## What is CUPED Refer to the [CUPED documentation](/experiments/statistical-methods/methodologies/cuped) or the [CUPED blog post](https://www.statsig.com/blog/cuped). ## How CURE improves on CUPED CURE solves the no-covariate problem by allowing practitioners to specify additional covariates, which are used in the CUPED regression. The predictions from this regression are used to generate an estimator set that is unbiased, but has lower variance than the original estimator. CURE can reduce experiment runtimes more than CUPED alone. When combined with Statsig's Entity Properties, CURE allows practitioners to connect existing feature stores to this variance reduction technique. {% callout type="note" %} Like CUPED, CURE can modify the point estimates of groups, though the total value across all experimental groups sums to the same value as in the unadjusted dataset. Drift in value between groups is expected, particularly when there are pre-experiment differences and the correlation is high. This is the algorithm adjusting for pre-existing deltas in experiment groups. Contact the team in Slack if you have concerns. {% /callout %} ## How CURE differs from standard regression This technique is similar to standard regression estimates used in other experimentation tools, but differs in several ways: * Statsig applies CURE across all major experimental surfaces, including experiment scorecards, drill-down explore queries, filtered queries, and power analysis. This ensures results match between scorecards and explore queries, which can diverge in tools with incomplete CUPED implementations. * Multivariate regressions enable variance reduction for new users or users without pre-experiment data. * CURE manages feature selection, preventing overfitting issues seen in similar multivariate approaches, and making regression adjustments more transparent. ## How CURE calculates variance reduction The original CUPED implementation by Microsoft [(Deng, et. al.)](https://www.exp-platform.com/Documents/2013-02-CUPED-ImprovingSensitivityOfControlledExperiments.pdf) is equivalent to an OLS regression with one independent variable. However, the adjustment doesn't need to be a single-variable regression. A regression guarantees identical or lower variance, but any unbiased adjustment is mathematically valid. By calculating the covariance matrix between covariates and experiment outcomes in SQL, you can generate the inputs for multivariate regressions and use these to adjust post-exposure values in an unbiased manner, meaning the regression is demeaned and uses only pre-experiment data. This yields an estimator with variance less than or equal to the original estimator. Specifically: * if covariates don't exist, nothing happens and the adjusted estimator is identical to the unadjusted * in the worst case where covariates are uncorrelated, the adjusted estimator is identical to the unadjusted * if no additional covariates are applied, CURE is mathematically equivalent to CUPED * if additional covariates are applied that increase the $R^2$ (i.e. portion of explained variance), CURE reduces variance more than CUPED ## CURE for ratio metrics Combining Statsig's CUPED implementation for ratio metrics with CURE covariates enables ratio metrics to use both pre-experimental data and user-provided covariates. With an adjusted formula to calculate the covariance of a ratio random variable to a simple variable, Statsig establishes a multi-covariate regression similar to CURE for simple metrics. The variance and mean adjustments follow the ratio CUPED approach. * As an artifact of the regression, Statsig obtains the shrinkage $R^2$, which it uses to scale the post-adjustment variance. * Statsig constructs the mean adjustment as the inner product of the coefficient vector $\theta$ and the covariate group means $C$. By calculating the difference in adjustment between the test and control group, only the test group mean is adjusted while holding the control group mean the same. ## Data sources When CUPED is enabled, pre-experiment metric data is still an input to the CURE regression. CURE allows you to pull data from: * Assignment records: if using end-to-end experiments with Statsig, log user attributes using the SDK and they are exported for use in CURE. * Entity property sources: use any timestamp-enabled entity property source to provide covariate data. Non-timestamped entity property sources are disabled to prevent data leakage into CURE. Entity property sources are a convenience feature; you could alternatively manage joining user features to your assignment data. In practice, this enables use cases such as providing your own table of regression estimates as covariates, similar to DoorDash's [CUPAC](https://careersatdoordash.com/blog/improving-experimental-power-through-control-using-predictions-as-covariate-cupac/) approach. ## Configuration You can manage CURE in two places: * Project Settings under Experimentation: the default CURE Covariates setting lets you specify covariates to use across any CUPED-enabled metric in your project. * Advanced Settings in the Experiment Settings page: specify covariates for a single experiment. ## Datasets Statsig surfaces CURE information in experiment diagnostics, providing details on coefficients and relevant covariates for any given metric. View the adjustments used under the Regression Adjustment job, and check the coefficients table to run your own analysis on the regression model. ## Outputs View the DAG history to see the exact coefficients used and the table where they are stored. Statsig also renders an in-console explanation of which variables contributed to variance reduction and by how much, which helps users decide which features to use. ## Known limitations Like CUPED, CURE can modify the point estimates of groups. The total value across all experimental groups sums to the same value as in the unadjusted dataset, but drift in value between groups is expected, particularly when there are pre-experiment differences and the correlation is high. This is the algorithm adjusting for pre-existing deltas in experiment groups. Contact the team in Slack if you observe a concerning change. ## Feature selection Statsig uses Lasso regression to select important features, reducing the computational cost of adjusting the unit metric level and controlling for multicollinearity. View the contribution of each feature on the variance reduction card. The estimated proportion of variance reduction is: $$ \text\{Variance Reduction}\_\{i} = \frac\{\{Coefficient\_\{i}}^2\*Var(feature\_\{i})}\{\sum \{Coefficient\_\{i}}^2\*Var(feature\_\{i})} $$ by that, $$ \sum \text\{Variance Reduction}\_\{i} = 100\\% $$ ## References * [From Augmentation to Decomposition: A New Look at CUPED in 2023](https://arxiv.org/html/2312.02935v1) * [Improving Experimental Power through Control Using Predictions as Covariate (CUPAC)](https://careersatdoordash.com/blog/improving-experimental-power-through-control-using-predictions-as-covariate-cupac/) * [Agnostic notes on regression adjustments to experimental data: Reexamining Freedman’s critique](https://projecteuclid.org/journals/annals-of-applied-statistics/volume-7/issue-1/Agnostic-notes-on-regression-adjustments-to-experimental-data--Reexamining/10.1214/12-AOAS583.full) ================================================================================ # Get Started With CURE URL: https://docs.statsig.com/statsig-warehouse-native/cure/cure-setup Updated: 2026-07-11 ================================================================================ ## Defaults By default, CURE runs a univariate regression when enabled, which is the same as the [standard implementation of CUPED](https://www.exp-platform.com/Documents/2013-02-CUPED-ImprovingSensitivityOfControlledExperiments.pdf). To add additional covariates ## Project defaults In project settings under Experimentation, specify default covariates for your company: {% figure %} ![Project Settings](/images/cure/project_setting.png) {% /figure %} You can source covariates from the assignment source for the experiment, or from Entity Property Sources. Properties with an Entity Property Source name attached come from a property source and Statsig always includes them on the experiment. Properties without an EPS come from an assignment source and apply if the column exists on the assignment source of a given experiment. ## Experiment settings Per-experiment, specify additional covariates or remove covariates specific to your analysis: {% figure %} ![Experiment Settings](/images/cure/experiment_setting.png) {% /figure %} Statsig populates this list from the experiment assignment source columns and relevant Entity Property Sources. ## Metric:Metric One tool this framework enables is metric:metric covariates, such as using units' pre-experiment clicks as a covariate for units' in-experiment revenue. Statsig doesn't use this approach for several reasons: * Consistency: adding a new metric to your analysis should not significantly alter the results of other metrics. * Unnecessary complexity: if you have key metrics that function as covariates, explicitly provide these for all metrics as a covariate in an entity property source. This achieves the same result without opaque outputs from unknown metric covariances. Statsig recommends explicitly including key metric covariates as numerical covariates rather than adding arbitrary covariates based on metric selection. Contact the team in Slack to discuss further. ## Preventing adjustments You can turn off CUPED in your Pulse results, and can create a project-level setting to enforce this. CURE still runs when CUPED is turned off, which incurs some compute cost. To avoid running CURE, turn it off on a given metric by clearing the CUPED option in the metric's setup page. {% figure %} ![Metric Settings](/images/cure/metric_setting.png) {% /figure %} ================================================================================ # Autotune URL: https://docs.statsig.com/statsig-warehouse-native/features/autotune Updated: 2026-07-11 ================================================================================ Autotune Experiments in Warehouse Native have a setup similar to cloud [Autotunes](/autotune/overview). In Warehouse Native, Autotune success events are pulled from a Metric Source, and results are computed in your warehouse. {% callout type="info" %} Contact the team to get Continuous Autotune enabled in your account. {% /callout %} ## Creating an Autotune To set up an Autotune on Warehouse Native, start by defining your objective. The objective can be an *event* or a *value* from a Metric Source. For detailed instructions, refer to [how to set up a Metric Source](/statsig-warehouse-native/configuration/metric-sources). You can also specify the optimization direction (Maximize or Minimize) to determine how Autotune evaluates variant performance. {% figure %} ![Autotune configuration interface](/images/statsig-warehouse-native/features/autotune/autotune-config.png) {% /figure %} 1. Navigate to the [Experiments section](https://console.statsig.com/experiments) in the sidebar of the Statsig Console. 2. Click on the [Autotune tab](https://console.statsig.com/autotune) at the top. 3. Click the Create button and enter the name and description of the Autotune Experiment that you want to create. 4. Select an ID Type for your Experiment. 5. Create and name your variants for your Autotune Experiment. The variant listed as Control/Default is returned when the Autotune Experiment isn't running. 6. Select your Metric Source that you defined earlier as shown below. {% figure %} ![Autotune metric source configuration interface](/images/statsig-warehouse-native/features/autotune/autotune-metric-source.png) {% /figure %} The following parameters are available: | Parameter | Description | |---|---| | Exploration Window | The initial time period where Autotune splits traffic equally. Useful for noisy or temporal metrics where hourly swings can bias initial measurements. | | Attribution Window | The maximum duration between the exposure and success event that counts as a success. 1 hour is recommended for most applications; adjust if the success event is expected to lag the exposure event by several hours. | | Winner Threshold | The "probability of best" threshold a variant must reach for Autotune to declare it the winner, stop collecting data, and direct all traffic. A lower value results in faster decisions but increases the probability of selecting the wrong winner. | Click **Create** to finalize the setup. 7. The Autotune is configured and ready. Click **Start** when you are ready to launch your Autotune test. ================================================================================ # Meta-Analysis URL: https://docs.statsig.com/statsig-warehouse-native/features/meta-analysis Updated: 2026-07-11 ================================================================================ ## How meta-analysis works As teams run many experiments, it becomes possible to identify patterns across those experiments. This is meta-analysis. Common questions that meta-analysis helps answer include: - How hard is a metric to move? - Are there more sensitive proxies for the metric you care about? - How are teams performing relative to each other? Statsig has worked with multiple organizations to help them run thousands of trustworthy experiments per year. These tools are useful whether you are running 50 experiments a year or 5000. To influence the roadmap, reach out in [Slack](https://statsig.com/slack). ## Experiment timeline view This view lets you filter to experiments a team has run. At a glance you can answer questions like: 1. What experiments are running now? 2. When are they expected to end? 3. What % of experiments ship Control vs Test? 4. What is the typical duration? 5. Do experiments run for their planned duration - or much longer or shorter? 6. Do experiments impact key business metrics - or only shallow or team level metrics? 7. How much do they impact key business metrics? {% figure %} ![Experiment timeline view dashboard](/images/snippets/stats-methods/meta-analysis/d1e1db49-18b4-46af-93f7-4c0e58c5bdb0.png) {% /figure %} ## Metric impact (batting average) The "batting average" view shows how easy or hard a metric is to move. You can filter to a set of shipped experiments and see how many moved a metric by 1% vs 10%. As with other meta-analysis views, you can filter by team, tag, or statistical significance. Common uses include: - Validating whether a claim that the next experiment will move a metric by 15% is reasonable. - Establishing realistic goals based on past ability to move the metric. {% figure %} ![Metric batting average analysis chart](/images/snippets/stats-methods/meta-analysis/72f987a4-02ba-4937-9f3d-06892c55e4e9.png) {% /figure %} ## Metric correlation view This view plots two metrics on the same chart, where each data point represents one experiment's impact. Use it to assess whether the metrics tend to move together. Often the metric you want to move isn't very sensitive and takes a while to measure. Finding more sensitive, faster-to-measure proxy metrics is useful for running experiments. You can also remove outliers, filter to a team's experiments, or download the underlying dataset. In this example: "Checkouts" is the target metric, but it isn't very sensitive. "AddToCart" correlates well with "Checkouts", while "ViewItemDetail" doesn't. {% figure %} ![Metric correlation scatter plot](/images/snippets/stats-methods/meta-analysis/016285b0-cdc4-46a4-8fdf-204b20b5b0e4.png) {% /figure %} {% figure %} ![Metric correlation analysis interface](/images/snippets/stats-methods/meta-analysis/c29e5d7d-d8b6-4841-acaf-8d521a5db398.png) {% /figure %} ## Metric insights This view lets you pick a metric and see all experiments and feature rollouts that impact this metric. [Learn more](/experiments/exploring-results/aggregated-impact). {% figure %} <img src="/images/snippets/stats-methods/meta-analysis/8d61ada1-bf1f-41cf-b52b-f01d24052568.png" alt="417923655-430563dc-4794-4d69-a314-36c76a6fcf74" width="1003" /> {% /figure %} ## Knowledge bank The Knowledge Bank is a searchable repository of experiment learning across teams. Use it to find shipped, healthy experiments, gain context on past work, and generate ideas for new experiments. It gives new team members a way to explore experiments by keyword when the structured meta-analysis views don't fit the need. The meta-analysis tools provide more structured discovery, but free text search is also available here. {% figure %} ![Knowledge bank search interface](/images/snippets/stats-methods/meta-analysis/860430c4-a754-4ff8-aa85-8bf49a755801.png) {% /figure %} ================================================================================ # Practical Use Cases URL: https://docs.statsig.com/statsig-warehouse-native/features/use-case Updated: 2026-07-11 ================================================================================ ## How can I define and filter results to 'new users' only? ### Scenario I want to filter the experiment results to new users only: A user is 'new' if they had never visited the website before they were exposed to this experiment. If the first time the user visited the website was after the exposure timestamp in this experiment, the user was a 'new user'; otherwise the user was an existing user. ### Solution Assuming you have a logging table that contains every visit with a relative timestamp (`logging`), and another table with all users' `user_id` values (`user`), first create an Entity Property with the following logic: ```sql expandable select distinct user_id, 'new' as new_user, timestamp('1900-01-01') as timestamp from user -- you can add filters here to include only targeted population only union all select distinct user_id, 'existing' as new_user, timestamp as timestamp from logging ``` Select **Run Query** to return a table like this. Select **Save Results** to use this Entity Property across all experiments and gates to filter or group experiment results by whether a user is new or existing. | user\_id | new\_user | timestamp | | ------------- | -------------- | ------------------------- | | A | new | 1900-01-01T00:00:00+00:00 | | A | existing | 2024-01-01T10:10:18+00:00 | | B | new | 1900-01-01T00:00:00+00:00 | *** ## How can I analyze logged-in metrics when my experiment exposures are at logged-out grain? ### Scenario I want to run an experiment to find out which version of my website design leads to a higher signup rate among new visitors. The experiment assignment will occur when a logged-out user visits my website, and they will be exposed to one of the design variants. When a user decides to sign up, a new and unique logged-in user ID will be generated for them. To calculate the conversion rate (CVR) accurately and consistently, I need a reliable way to map the logged-out IDs to the corresponding logged-in IDs. This will allow me to attribute each signup to the correct experiment variant and evaluate which design performs better in driving conversions. ### Solution Statsig supports two commonly used approaches for this mapping, each suited to different business scenarios: **Strict 1:1 Mapping:** Keeps only records where there is a unique, unambiguous mapping between the logged-out ID and the logged-in ID. Statsig discards any records with duplication (for example, multiple logged-out IDs mapping to the same logged-in ID or the reverse). Use this approach when accuracy and clarity are the top priorities and data duplication is rare. **First Touch Mapping:** When a logged-in ID maps to multiple logged-out IDs, retains only the first association and discards the rest. Better suited for scenarios where you want to preserve as much data as possible and duplications are common (for example, users frequently access the website from multiple devices or sessions). For either mapping approach, you need to create a mapping between the logged-out ID and the logged-in ID by setting up an Entity Property with both IDs present or creating an exposure assignment source with columns for both ID types. #### Step 1: Advanced settings for your experiments When setting up your experiment, navigate to *Setup > Advanced Settings*, and select your Secondary ID type (the log-in user ID in this scenario). {% figure %} ![Experiment setup advanced settings selecting secondary ID](/images/statsig-warehouse-native/features/use-case/76f83c44-0389-4441-a5fc-bf29b9cab119.png) {% /figure %} #### Step 2: Identify the mapping mode that suits your need {% figure %} ![ID resolution mapping mode options](/images/statsig-warehouse-native/features/use-case/e49a030e-4933-4019-80f3-812c46cdd493.png) {% /figure %} #### Step 3: Choose your Entity Property Source {% figure %} <img src="/images/statsig-warehouse-native/features/use-case/abceaaf3-b15d-481e-9e7f-abc8a5c2cef3.png" alt="Entity property source selection for ID mapping" width="1014" /> {% /figure %} * **\[Recommended]** Create a new Entity Property Source for your ID Resolution mapping if you haven't already. Go to *Data > Entity Properties*. You can either enter an existing table with the ID mappings or write a new query to create the mappings with the following logic: ```sql SELECT stable_id, user_id, timestamp FROM id_mapping ``` * You can also choose **"None"** to use your assignment source for the mapping. Create your new assignment source by going to *Data > Assignment Sources*. Statsig doesn't recommend this option because it becomes more complex to manage as your experiments scale. ================================================================================ # MEX on Warehouse Native URL: https://docs.statsig.com/statsig-warehouse-native/features/mex-on-warehouse-native Updated: 2026-07-11 ================================================================================ [Metrics Explorer](/product-analytics/overview) (or MEX) is Statsig's analytics solution. With Warehouse Native, MEX integrates directly on top of your tables to provide analysis on the same datasets you use for experimentation. {% callout type="info" %} Contact the team to get Metrics Explorer enabled in your account. {% /callout %} ## Functionality ### [Drill-Down and Filter](/product-analytics/overview) Filter and group by fields to calculate measures as timeseries, bar-charts, and more. ### [Funnel Analysis](/product-analytics/funnels) Run funnel analysis to understand how users move through your product. ### [Group By Experiment](/product-analytics/drilldown#drilling-down) View arbitrary timeseries and rollups grouped by experiment membership, allowing more observational analysis of what happened during your experiment. ### [Retention Chart](/product-analytics/drilldown#retention) Analyze user retention reports to understand how effectively your product maintains user engagement over time. ### [Distribution Chart](/product-analytics/drilldown#distribution) View distribution charts to help you visualize the range of user experiences across your product. ### Save, share, and view SQL Save analyses and share them with coworkers. Viewing the SQL gives you access to the queries Statsig ran internally, which you can use as a starting point for deeper analysis. ## Use it with replays MEX works together with [Session Replay](/session-replay/overview) to help you understand the user behavior you measure in your experiments. ================================================================================ # Geotests URL: https://docs.statsig.com/statsig-warehouse-native/geotests/introduction Updated: 2026-07-11 ================================================================================ Sometimes you cannot run an A/B test at a per-user level. Common causes include: * You don't have per-user control over who sees what, for example, a third-party marketing platform like Meta or Google Ads. * You can't attribute metrics to individuals, for example, total store foot traffic can't be tied to which users received your email campaign. Geotests help solve these problems. One of the most common use cases is marketing: you can't A/B test on third-party ad platforms, but geo-testing lets you evaluate the incrementality of your program. Geotests are specialized experiments that allow you to target users based on geographic location. These tests enable you to test features, content, or experiences across any type of geographic area you specify. By splitting your campaign across DMAs or postal codes, you can measure incrementality even when an ads platform doesn't support it. Geotests are particularly valuable for businesses with global audiences who need to optimize their product for regional differences in user needs, preferences, or regulatory requirements. ## Why use Geotests Statsig's Geotesting combines the flexibility and statistical rigor of Statsig with your own data sources in your warehouse. Geotesting: 1. Uses the data already in your warehouse, avoiding expensive and time-consuming exports. 2. Includes an automated Experiment Designer to help you select where and when to run your campaign. 3. Exports your campaign definition to your campaign platform, making setup faster and less error-prone. 4. Automates analysis of the campaign, providing statistical rigor to measure incrementality. ## GeoLift Statsig's Geotesting is built on top of Geolift, a best-in-class industry tool for running geospatial experimentation for marketing. [GeoLift](https://facebookincubator.github.io/GeoLift/) is an open-source package from Meta that’s used by many to infer causal relationships in timeseries data. It builds on advancements made in prior packages like Google’s [Causal Impact](https://google.github.io/CausalImpact/CausalImpact.html) package. Statsig uses GeoLift in its implementation of Geotesting, allowing you to connect your own internal metrics and feed them into the analysis package. ## Related resources * [GeoLift](https://facebookincubator.github.io/GeoLift/) * [CausalImpact](https://google.github.io/CausalImpact/CausalImpact.html) * [Synthetic Control Methods (wiki)](https://en.wikipedia.org/wiki/Synthetic_control_method) * [Causal inference using synthetic controls (medium)](https://medium.com/data-science-at-microsoft/causal-inference-using-synthetic-controls-d96a890c83a7) ================================================================================ # Get Started with Geotests URL: https://docs.statsig.com/statsig-warehouse-native/geotests/geotests-setup Updated: 2026-07-11 ================================================================================ This guide walks you through creating a Geotest experiment using Statsig. The steps include: 1. Define your Geo Types 2. Configure your Metric Source 3. Create and Configure 4. Evaluate Design Options 5. Run Analysis ## Define your geo types First decide on one or more geo types relevant to your business. Some of the most common are Postal Codes and DMAs (Designated Market Areas), but Statsig allows you to define any arbitrary geo type. {% figure %} ![Geo types configuration interface](/images/geotests/geo_types_settings.png) {% /figure %} ## Configure your metric source Assuming you've already added a [metric source](/statsig-warehouse-native/configuration/metric-sources/) to Statsig, you can next indicate which column(s) represent the geo type(s) you've created. {% figure %} ![Geo column mapping configuration screen](/images/geotests/geo_column_mapping.png) {% /figure %} ## Create and configure * Navigate to your *Experiments* page. * Click the **+ Create** button. * In the modal, select **Analyze** to analyze existing experiment data in your warehouse. {% figure %} ![Experiment creation modal with analyze option](/images/geotests/CreateXP.png) {% /figure %} * Enter a name and description for your experiment. * Choose **Geotest** as the experiment type from the dropdown. Fill in the remaining fields as usual. {% figure %} ![Geotest experiment type selection dropdown](/images/geotests/SetType.png) {% /figure %} In the Setup tab: * Define your hypothesis. * Set the Geo Type (e.g., country\_id) from one of the types you've defined in your project and metric sources * Choose your Primary Metrics. {% callout type="note" %} **Important Metric Types** GeoTest metrics are limited to SUM, COUNT, and COUNT\_DISTINCT types. The inference techniques involved in synthetic control methodologies require this limitation. {% /callout %} {% figure %} ![Geotest experiment setup configuration screen](/images/geotests/SetupXP.png) {% /figure %} Configure: * Expected Treatment Start/End Dates * Pre-treatment duration (days) * Effect Size % (the MDE you want to measure; smaller MDEs will make finding a valid design harder) * Type of Test: 1-sided or 2-sided * Alpha level (the type I error rate you want, inverse of significance) * Optionally enable Budgeting * Additional advanced modeling parameters for users familiar with [Geolift's Advanced API](https://github.com/facebookincubator/GeoLift/blob/main/R/pre_test_power.R) {% figure %} ![Experiment configuration settings interface](/images/geotests/XPAdvancedConfigs.png) {% /figure %} ## Generate design options When your initial settings are ready, click **Use Experiment Designer** to begin the design process. {% figure %} ![Experiment designer launch button](/images/geotests/StartDesigner.png) {% /figure %} The Experiment Designer automates the process of evaluating potential splits of geos. Creating a new design set involves 3 steps: 1. **Define your targeting** 2. **Set any Inclusion or Exclusion overrides** 3. **Define your date range** {% figure %} ![Geographic limitations configuration interface](/images/geotests/CreateDesign.png) {% /figure %} ### Define targeting Targeting allows you to define the set of geos eligible to be included in your experiment. By default, Statsig pulls the unique set of geo types from your metric source from the last 90 days. You can add custom filters based on columns in your metric source. For example, you can filter geos to those in a certain region or by transactions that occurred in a certain language. {% figure %} ![Geotest targeting filters for eligible regions](/images/geotests/targeting.png) {% /figure %} You can also manually specify a set of geos yourself. The geo id types must match those in your metric source exactly. ### Set inclusion or exclusion overrides After defining your targeting set, you can manually assign any of these geos to treatment or control groups. {% figure %} ![Override interface assigning specific geos to treatment or control](/images/geotests/targetingoverwrites.png) {% /figure %} For example, when testing a new marketing campaign, you might want to include Austin because a contract has already been signed, and exclude New York, Los Angeles, or Washington D.C. because of local regulations. Specify these constraints in the designer and all design options considered will conform to these rules. ### Define date range Define the data range of your design dataset. This date range defines what data is pulled as the historical baseline to train a synthetic control model. A good guideline is that your design data duration should be at least 4x the expected duration of your treatment period, to achieve good fits and maximize sensitivity and power of your experiments. ### Advanced options * **Rolling Lookback Windows:** Control how many iterative simulations should be done to evaluate power. For instance, a value equal to 5 would simulate power for the last five possible tests in the date range. Corresponds to `lookback_window` in the GeoLift API. * **Dynamic Time Warping:** Control how much the synthetic control model depends on auto-correlations in the metric vs. correlations between different geos on the same days. A value of 1 focuses exclusively on the metric while a value of 0 (default) relies on correlations only. Corresponds to `dtw` in the GeoLift API. * **Control Cell Size Requirements:** Sets limits on the share of primary metric value from geos in any considered control group. If not set, Statsig analyzes all design options regardless of their size. For example, if designing on a Revenue metric, a control size range of \[50%, 90%] means that only design options where 50% to 90% of revenue is in the designated control geos. Corresponds to `holdout` in the GeoLift API. ## Evaluate design options Once a Design Option Set is created (usually takes a few minutes), Statsig shows it in your *Experiment Designs* tab. All design option sets Statsig generates appear here, so you can look through prior design iterations as needed. {% figure %} ![Experiment designer options display](/images/geotests/DesignerOptions.png) {% /figure %} Click into a completed design set (e.g. “Design Set 3”). * Review the ranked recommendations for your best design options * Compare MDE, Power, Cost and Control/Test Allocation * Click View Cell Details to expand and see specific geo assignments * Review additional model performance details * Select your design using the radio button, then click **Save Design to Experiment**. {% figure %} ![Design option selection interface](/images/geotests/DesignOption.png) {% /figure %} ## Run analysis {% figure %} ![Geotest analysis results dashboard](/images/geotests/AnalysisResults.png) {% /figure %} ================================================================================ # Geotesting Methodology URL: https://docs.statsig.com/statsig-warehouse-native/geotests/methodology Updated: 2026-07-11 ================================================================================ ## How geotesting differs from standard A/B testing Standard A/B testing relies on a few core assumptions: 1. You can reliably and randomly split your users into similar groups. 2. While each group has heterogeneity inside it, random user-level variations average out at larger sample sizes, resulting in homologous (similar) groups. 3. You control how each group gets treated, and there's no interference between groups. When these core assumptions are true, the treatment effect is the only observable difference between the groups. Measure each group’s mean value and find the difference between them. There are times however when these assumptions don’t hold: * Your intended treatment can’t be controlled and scoped to the user level * You don’t know who the users are * You can’t track outcomes at the user level Running geographically-based campaigns is a prime example that often features at least some of these violations. Examples include: * Some classic marketing, like billboards, can’t be randomly split between test and control users at a given location. You can’t track who sees them and how they act afterwards. * Some digital campaigns might be able to split users 50/50 (if the platform allows it), but without user-level data that can be tied to outcomes of interest, you can’t determine how the two groups performed. ## Using synthetic controls “Geotesting” as referred to here is an experimental framework that relies on a different basic setup than AB tests: 1. Split your users into geographical boundaries at some useful level (zip codes, states, countries, etc.). 2. Apply a known treatment in some “treatment” geos, and no treatment in some “control” geos. 3. Use the control geos to figure out what would have happened in the treatment geos had no treatment been applied (that is, “synthetic” results). 4. Measure the delta in the treatment geos between the actual and synthetic values. Determining what would have happened is the most difficult part of this setup. Deducing things that didn't occur is generally unreliable, but in some circumstances there is enough predictability in a system to make it possible. Geotesting relies on sufficient correlations between control and test geos to make predicting this “counterfactual” possible. General trends and seasonality tend to be shared across some geos. Example: pretty much all zip codes in the United States exhibit similar quantities of mail sent on any given day. If you were to split zip codes 50/50, you could reasonably infer that the two groups would see similar trends. ## Estimation approach There are many ways to estimate unknown data from other known data, depending on a multitude of factors. In the case of Geotesting, the data has: * timeseries data that is autocorrelated (i.e. data is correlated from one day to the next within a group) * timeseries data that is correlated between groups * regular seasonal patterns that repeat at regular intervals * granular data from among many distinct geographies Plotting a metric of interest for a set of relevant geographies can show these relationships clearly. {% figure %} ![Example data from the GeoLift package walkthrough. ](/images/whn/GeosPreTest.png) {% /figure %} Example data from the GeoLift package walkthrough. But what happens when a campaign runs in only some of these geos? How can you tell if a fluctuation in this graph is random noise or a statistically significant result? {% figure %} ![A marketing campaign that starts on day 91 (black dotted line) could have affected the treatment cities of Chicago and Portland. But how can you tell any real effect from all the noise?](/images/whn/GeosTest.png) {% /figure %} A marketing campaign that starts on day 91 (black dotted line) could have affected the treatment cities of Chicago and Portland. But how do you distinguish any real effect from all the noise? Causal inference modeling addresses this question. A variety of models and methods have been developed for this purpose. The basic steps are: * Split your geos into test and control groups. * Using the pre-treatment period data, train a model on the control data to predict how the treatment geos behave. * Using the post-treatment period data for the control geos, create a “synthetic control” dataset to predict how the treatment geos would have behaved after the treatment event. * Subtract the synthetic data from the actual observed data to determine the induced effects of your treatment on the treatment geos. When your training data is able to produce strong models that can predict metric outcome well, you end up with strong estimates of an induced effect: {% figure %} ![Geotest period split diagram showing pre-treatment and post-treatment phases](/images/whn/Period_Split.png) {% /figure %} {% figure %} ![Subtracting the modeled Synthetic Control values from the observed Treatment values reveals any incremental effect for the Treatment geos.](/images/whn/Incremental_Effects.png) {% /figure %} Subtracting the modeled Synthetic Control values from the observed Treatment values reveals any incremental effect for the Treatment geos. ================================================================================ # Other Features URL: https://docs.statsig.com/statsig-warehouse-native/features/other-useful-features Updated: 2026-07-11 ================================================================================ Many features are not listed in the Warehouse Native section because they function nearly identically in both Cloud and Warehouse Native. These are worth exploring as you integrate with Statsig: * [Feature Gates](/feature-flags/overview) are a best-in-class tool for managing feature release and targeting. Statsig Warehouse Native can automatically analyze feature gates with partial rollouts as experiments. * [Session Replay](/session-replay/overview) is a powerful tool for viewing how users interact with your product, giving richer context to changes in user behavior * [Layers](/experiments/layers-overview) allow you to create mutually-exclusive experiments for experiments where changes may collide with other experiments to create a bug or odd result * [Dynamic Configs](/dynamic-config/working-with) provide server-side configuration tools with optional targeting and integrate naturally with experiments. * [Admin](/access-management/introduction) tools let you control projects, teams, and user access across Statsig. ================================================================================ # Get started with the Statsig SDK URL: https://docs.statsig.com/sdks/quickstart Updated: 2026-07-11 ================================================================================ For a more detailed guide, go to the [SDK Overview](/sdks/getting-started) or read about choosing between [client or server SDKs](/sdks/client-vs-server). {% tabs %} {% tab title="React" %} {% steps %} {% step title="Install Statsig packages" %} ```bash npm install @statsig/react-bindings ``` {% /step %} {% step title="Wrap child components" %} Update your app's default function (typically App.tsx or Layout.tsx) so that the StatsigProvider wraps all child components. ```tsx import { StatsigProvider } from "@statsig/react-bindings"; // [!code ++] export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <StatsigProvider // [!code ++] sdkKey={"client-MY-STATSIG-CLIENT-KEY"} // [!code ++] user={{ userID: "quickstart-user" }} // [!code ++] loadingComponent={<div>Loading...</div>}> // [!code ++] {children} </StatsigProvider> // [!code ++] ); } ``` {% callout type="note" %} This example assumes client-side React. For Server-Side Rendering, refer to the [Next.js docs](/client/Next). {% /callout %} {% /step %} {% step title="Add client key" %} Create a client API key in the [Statsig console Settings](https://console.statsig.com/api_keys). Copy and paste it to replace `<REPLACE_WITH_YOUR_CLIENT_KEY>` in the code snippet from the previous step. {% /step %} {% step title="Basic Usage" %} {% tabs %} {% tab title="Check a Gate" %} First, create a gate on the [Feature Gates page](https://console.statsig.com/gates) in console, then check it in-code: ```jsx const { client } = useStatsigClient(); return ( <div>Gate is {client.checkGate('check_user') ? 'passing' : 'failing'}.</div> ); ``` {% /tab %} {% tab title="Get an Experiment Value" %} First, create an Experiment on the [Experiments page](https://console.statsig.com/experiments) in console ```jsx const { client } = useStatsigClient(); const experiment = client.getExperiment('my_experiment_name'); return ( <div>Headline Parameter: {experiment.get('my_experiment_parameter_name', 'fallback_value')}.</div> ); ``` {% /tab %} {% tab title="Log an Event" %} Use events to power metrics in your experiment or gates. Events don't need to be set up in console first, just add to your code: ```jsx const { client } = useStatsigClient(); return <button onClick={() => client.logEvent("button_click")}>Click Me</button> ``` {% /tab %} {% /tabs %} {% /step %} {% /steps %} ## Next steps You've set up the Statsig SDK in React. Continue with the tutorials, or go to the full [Next.js](/client/Next) or [React](/client/React) SDK reference. {% /tab %} {% tab title="JS snippet" %} {% steps %} {% step title="Paste the code snippet" %} In the `<head>` section of your website, paste the following code snippet: ```html <script src="https://cdn.jsdelivr.net/npm/@statsig/js-client@3/build/statsig-js-client+session-replay+web-analytics.min.js?apikey=<REPLACE_WITH_YOUR_CLIENT_KEY>"></script> ``` {% /step %} {% step title="Add client key" %} Create a client API key in the [Statsig console Settings](https://console.statsig.com/api_keys). Copy and paste it to replace `<REPLACE_WITH_YOUR_CLIENT_KEY>` in the code snippet from the previous step. {% /step %} {% step title="Basic usage" %} {% tabs %} {% tab title="Check a Gate" %} First, create a gate on the [Feature Gates page](https://console.statsig.com/gates) in console, then check it in-code: ```jsx window.Statsig.instance().checkGate("my_feature_gate_name"); ``` {% callout type="note" %} Wait for the SDK to initialize before checking a gate to ensure it has fresh values. One way to do this is to wait for the ["values\_updated"](/client/javascript-sdk#client-event-emitter) event. {% /callout %} {% /tab %} {% tab title="Get an Experiment Value" %} First, create an Experiment on the [Experiments page](https://console.statsig.com/experiments) in console ```jsx window.Statsig.instance().getExperiment("my_experiment_name").get('my_experiment_parameter_name'); ``` {% callout type="note" %} Wait for the SDK to initialize before getting an experiment to ensure it has fresh values. One way to do this is to wait for the ["values\_updated"](/client/javascript-sdk#client-event-emitter) event. {% /callout %} {% /tab %} {% tab title="Log an Event" %} Use events to power metrics in your experiment or gates. Events don't need to be set up in console first, just add to your code: ```jsx window.Statsig.instance().logEvent("my_checkout_event_name", "event_value_item_1234", {"event_metadata": "my_metadata"}) ``` {% /tab %} {% /tabs %} {% /step %} {% /steps %} ## Next steps You've set up the Statsig JavaScript snippet. You can now: * [Record events](/webanalytics/overview) * [Watch session replays](/session-replay/overview) * [Run experiments](/experiments/overview) * [Use feature flags](/feature-flags/overview) Go to the [Javascript SDK reference](/client/javascript-sdk) for more information. {% /tab %} {% tab title="Python" %} {% steps %} {% step title="Install the Statsig package" %} ```shell pip install statsig-python-core ``` {% /step %} {% step title="Initialize the Statsig SDK" %} ```python from statsig_python_core import Statsig, StatsigOptions options = StatsigOptions() options.environment = "development" statsig = Statsig("<REPLACE_WITH_YOUR_SERVER_SECRET_KEY>", options) statsig.initialize().wait() statsig.shutdown().wait() ``` {% /step %} {% step title="Add server secret key" %} Create a server secret key in the [Statsig console Settings](https://console.statsig.com/api_keys). Copy and paste it to replace `<REPLACE_WITH_YOUR_SERVER_SECRET_KEY>` in the code snippet from the previous step. {% /step %} {% step title="Basic Usage" %} {% tabs %} {% tab title="Check a Gate" %} First, create a gate on the [Feature Gates page](https://console.statsig.com/gates) in console, then check it in-code: ```python user_object = StatsigUser(user_id="123", email="testuser@statsig.com") //add any number of other attributes gate_value = statsig.check_gate(user_object, "my_feature_gate_name"): ``` {% /tab %} {% tab title="Get an Experiment Value" %} First, create an Experiment on the [Experiments page](https://console.statsig.com/experiments) in console ```python user_object = StatsigUser(user_id="123", email="testuser@statsig.com" my_experiment_object = statsig.get_experiment(user_object, "my_experiment_name") my_experiment_parameter_value = my_experiment_object.get_string('my_experiment_parameter_name') ``` {% /tab %} {% tab title="Log an Event" %} Use events to power metrics in your experiment or gates. Events don't need to be set up in console first, just add to your code: ```python user_object = StatsigUser(user_id="123", email="testuser@statsig.com" statsig.log_event( user=user_object, event_name="my_checkout_event_name", value="SKU_12345" ) ``` {% /tab %} {% /tabs %} {% /step %} {% /steps %} ## Next steps You've set up the Statsig SDK in Python. Continue with the tutorials, or go to the full [Python SDK Reference](/server-core/python-core). {% /tab %} {% tab title="Node" %} {% steps %} {% step title="Install the Statsig package" %} ```bash npm i @statsig/statsig-node-core ``` {% /step %} {% step title="Initialize the Statsig SDK" %} ```jsx // Basic initialization const statsig = new Statsig("<REPLACE_WITH_YOUR_SERVER_SECRET_KEY>"); await statsig.initialize(); // or with StatsigOptions const options: StatsigOptions = { environment: "staging" }; const statsigWithOptions = new Statsig("secret-key", options); await statsigWithOptions.initialize(); ``` {% /step %} {% step title="Add server secret key" %} Create a server secret key in the [Statsig console Settings](https://console.statsig.com/api_keys). Copy and paste it to replace `<REPLACE_WITH_YOUR_SERVER_SECRET_KEY>` in the code snippet from the previous step. {% /step %} {% step title="Basic Usage" %} {% tabs %} {% tab title="Check a Gate" %} First, create a gate on the [Feature Gates page](https://console.statsig.com/gates) in console, then check it in-code: ```js const userObject = new StatsigUser({ userID: "123", email="testuser@statsig.com" }); const is_gate_enabled = statsig.checkGate(userObject, "my_feature_gate_name"): ``` {% /tab %} {% tab title="Get an Experiment Value" %} First, create an Experiment on the [Experiments page](https://console.statsig.com/experiments) in console ```js const userObject = new StatsigUser({ userID: "123", email="testuser@statsig.com" }); const myExperimentObject = statsig.getExperiment(userObject, "my_experiment_name") const myExperimentParameterValue = myExperimentObject.getValue('my_experiment_parameter_name') ``` {% /tab %} {% tab title="Log an Event" %} Use events to power metrics in your experiment or gates. Events don't need to be set up in console first, just add to your code: ```js userObject = StatsigUser(user_id="123", email="testuser@statsig.com" statsig.logEvent( userObject, "my_checkout_event_name", "SKU_12345" //value for the event ); ``` {% /tab %} {% /tabs %} {% /step %} {% /steps %} ## Next steps You've set up the Statsig SDK in Node.js. Continue with the tutorials, or go to the full [Node.js SDK reference](/server-core/node-core). {% /tab %} {% tab title="+24 more SDKs" %} ## Explore SDKs Statsig offers SDKs for a wide variety of platforms: ### Client SDKs {% card-grid columns="3" %} {% card title="JavaScript" href="/client/javascript-sdk" icon="js" %} Browser JavaScript {% /card %} {% card title="React" href="/client/React" icon="react" %} Client-Side React {% /card %} {% card title="React Native" href="/client/ReactNative" icon="react" %} Bare React Native SDK {% /card %} {% card title="Next.js" href="/client/Next" icon="n" %} Next.js SSR, SSG & Client-Side {% /card %} {% card title="Angular" href="/client/Angular" icon="angular" %} Angular bindings for Javascript SDK {% /card %} {% card title="Swift" href="/client/iosClientSDK" icon="swift" %} iOS, MacOS, tvOS SDK {% /card %} {% card title="Android" href="/client/Android" icon="android" %} Android Kotlin/Java SDK {% /card %} {% card title=".NET Client" href="/client/DotNet" icon="dotnet" %} Client SDK for .NET framework {% /card %} {% card title="Roku" href="/client/Roku" icon="roku" %} Roku Brightscript SDK {% /card %} {% card title="Unity" href="/client/Unity" icon="unity" %} Unity game engine SDK {% /card %} {% card title="Dart/Flutter" href="/client/Dart" icon="flutter" %} Flutter/Dart Mobile App SDK {% /card %} {% card title="C++ Client" href="/client/CPP" icon="cplusplus" %} C++ client-side SDK {% /card %} {% /card-grid %} ### Server side SDKs {% card-grid columns="3" %} {% card title="Node.js" href="/server-core/node-core" icon="node-js" %} Node.js server SDK {% /card %} {% card title="Java" href="/server-core/java-core" icon="java" %} Java server SDK {% /card %} {% card title="Python" href="/server-core/python-core" icon="python" %} Python server SDK {% /card %} {% card title="Go" href="/server/go" icon="golang" %} Go server SDK {% /card %} {% card title="Ruby" href="/server/ruby" icon="ruby" %} Ruby server SDK {% /card %} {% card title=".NET Server" href="/server-core/dotnet-core" icon="dotnet" %} .NET server SDK {% /card %} {% card title="PHP" href="/server-core/php-core" icon="php" %} PHP server SDK {% /card %} {% card title="Rust" href="/server-core/rust-core" icon="rust" %} Rust server SDK {% /card %} {% card title="C++ Server" href="/server-core/cpp-core" icon="cplusplus" %} C++ server SDK {% /card %} {% /card-grid %} ### Integrations {% card-grid columns="3" %} {% card title="Webflow" href="/guides/webflow-sidecar-ab-test" icon="w" %} Webflow integration {% /card %} {% card title="Shopify" href="/guides/shopify-ab-test" icon="shopify" %} Shopify integration {% /card %} {% card title="Segment" href="/integrations/data-connectors/segment" icon="segment" %} Segment data connector {% /card %} {% card title="Rudderstack" href="/integrations/data-connectors/rudderstack" icon="rudderstack" %} Rudderstack connector {% /card %} {% card title="Hightouch" href="/integrations/data-connectors/hightouch" icon="hightouch" %} Hightouch integration {% /card %} {% card title="mParticle" href="/integrations/data-connectors/mparticle" icon="mparticle" %} mParticle connector {% /card %} {% card title="Framer" href="/guides/framer-analytics" icon="f" %} Framer integration {% /card %} {% card title="Slack" href="/integrations/slack" icon="slack" %} Slack notifications {% /card %} {% card title="Integrations" href="/integrations/introduction" icon="puzzle" %} View more integrations {% /card %} {% /card-grid %} {% /tab %} {% /tabs %} ================================================================================ # Identify Users URL: https://docs.statsig.com/sdks/identify-users Updated: 2026-07-11 ================================================================================ ## Why identify users? When you run an experiment, roll out a feature, or log events, Statsig needs to know who the user is to determine: * Targeting: whether a feature gate should pass or fail for a given user * Experiment bucketing: which group a user belongs to * Analytics: how many unique users triggered an event Pass a User Object to Statsig SDKs to provide this information. The User Object is also required for experiment analysis: users are allocated to the test or control group by an ID, and the Statsig stats engine uses the same ID on the users' events to calculate metric differences between test and control groups. ## Basic user object Start by defining a basic user object: {% codetabs %} ```jsx Basic User Object { "userID": "u_123", // required for most setups "email": "user@example.com" // optional } ``` {% /codetabs %} Add more attributes when you need finer targeting. Refer to [Enriched attributes](#enriched-attributes) for details. ## Identifying users in client SDKs In client SDKs, the SDK is initialized for a single User Object at any one time. If you call a method like `checkGate` or `getExperiment` before the SDK is initialized, results are incorrect. You'll also see warnings in the Statsig Console on the diagnostics tab of any gate or experiment. Initialization requires a network request, which you start by calling the `initialize()` method, or in React, `useClientAsyncInit()`. In client apps with asynchronous logic, wait for initialization to complete before checking a gate. For example, in React: {% codetabs %} ```jsx React Example export function App() { const { client, isLoading } = useClientAsyncInit( YOUR_CLIENT_KEY, user={ userID: "u_123"} // user object ); if (isLoading) { return <div>Loading...</div>; } return ( <StatsigProvider client={client}> <MainApp /> </StatsigProvider> ); } ``` {% /codetabs %} If you initialize with a given user object and the user object changes (for example, the user logs in and now has a `userID`), call the `updateUser` method to refresh values from the Statsig server. For example, in React: {% codetabs %} ```jsx Update User const { user, updateUserSync } = useStatsigUser(); return <div> <p>Current User: { user.userID }</p> <button onClick={() => updateUserSync({userID: 'some-other-user'})}> Update User </button> </div>; ``` {% /codetabs %} If you don't update the user before checking gates or experiments, gates and experiments evaluate using the previous user object, which can produce unexpected results. Any attribute added to the user object is targetable in the Statsig console. To target users based on `email`, `companyID`, or other attributes, add those attributes to your user object. ## Identifying users in server SDKs In server SDKs, pass a User Object with each evaluation or event call, such as `logEvent`, `checkGate`, or `getFeatureGate`. The server SDK holds the necessary rules in memory for evaluation. Pass all attributes needed to evaluate your gate or experiment on every call. Statsig SDKs don't store or enrich attributes from previous calls. Example (Node.js) {% codetabs %} ```jsx Node.js Example import { Statsig } from "statsig-node"; await Statsig.initialize("<YOUR_SERVER_KEY>"); const user = { userID: "u_123", custom: { plan: "premium" } }; const gate = await Statsig.checkGate(user, "beta_feature"); if (gate) { console.log("Gate passed"); } ``` {% /codetabs %} ## Enriched attributes Statsig enriches the user object with useful attributes to expand targeting options. Examples of what Statsig can enrich: * `stableID`: an anonymous identifier which identifies a unique device on client SDKs * `country`: derived from IP or device locale * `appVersion`: pulled from mobile SDKs * `browser` / `OS`: captured from user agent * and more... To learn about all available fields (including `custom` attributes, `customIDs`, and `privateAttributes`), go to the [StatsigUser object](/sdks/user) docs. ================================================================================ # Create your first feature flag URL: https://docs.statsig.com/guides/check-gate Updated: 2026-07-11 ================================================================================ This tutorial walks you through how to check your first Feature Gate in Statsig from end to end. Feature Gates, also known as feature flags, are a way to safely control the rollout of new features to your users without deploying additional code. Common examples for using Feature Gates include shipping new UI elements, API endpoints, or product features. By the end of this tutorial, you have: * Created a **Feature Gate** in the Statsig console, with **targeting rules** to enable the feature for a segment of Users * Initialized a **Statsig Client SDK** * Checked a single **feature gate** in your code using the `checkGate` function ## Prerequisites 1. A [Statsig account](https://console.statsig.com/sign_up) 2. An existing application you can integrate the Statsig Client SDK into ## Step 1: In the Statsig console ### Create a Feature Gate This example adds a Feature Gate to deploy a new UI element to a user with the "statsig.com" email domain. You can follow along with a specific feature if you have your own scenario in mind. 1. Navigate to [Feature Gates](https://console.statsig.com/gates) in the Statsig console. 2. Then, click on **Get Started** if you don't have any Feature Gates set up yet, or **Create** to create a new one. 3. Name your gate "Example Gate". The SDK also uses this name to identify the Feature Gate later. 4. Enter a description for your Feature Gate. It's good practice to describe it in a way that other teammates can easily understand. For example: "This Feature Gate is for launching an example feature for Statsig employees only." ### Create a targeting rule When you create a Feature Gate in Statsig, Statsig excludes all users from the feature by default. To enable the feature, add a rule that lets users pass the gate. The steps below walk through adding that rule in the console. 1. In the console, on the page for the Feature Gate you just created, click on **Add New Rule**. 2. Give this rule a **Name**, such as "Statsig Users Only". 3. Select **Email** as the targeting criteria to target users based on their email address. 4. In the User section of the dropdown, select the **Any Of (Case Insensitive)** operator, and then add `statsig.com` for email-based user targeting. 5. Set the **Pass Percentage** to `100%`. Doing so ensures that all users with the `statsig.com` email domain pass the Feature Gate and see the new feature. 6. Click **Add Rule** to add this rule to your Feature Gate. 7. Next, hit **Save** on the bottom right to commit these changes to the Feature Gate. You can now test this feature gate by configuring the User object in the "Test Gate" section. ### Create a Client API Key Now that you've set up the Feature Gate from the console, it's time to integrate it into your product with the Statsig SDK. First, create a new Client API key to use in your product. 1. Navigate to [**Keys & Environments**](https://console.statsig.com/api_keys) in the Statsig console. You can also find this by going to **Settings** at the bottom left of the Statsig console. 2. Scroll down to **API Keys**. Click on **Generate New Key**. 3. In the dropdown, select **Client**. 4. Copy the Client API Key you just created to your clipboard. ## Step 2: In your code ### Initialize the Statsig SDK Now that you have a Client API Key, integrate the Statsig Client SDK into your product. This tutorial uses the React SDK, but you can use a different SDK if you prefer. {% callout type="tip" %} Statsig offers over 20 client and server-side SDKs. Check out the full list of [SDKs](/sdks/quickstart#all-sdks) to find the one that best fits your needs. {% /callout %} 1. Install the Statsig React SDK using your preferred package manager. This tutorial uses npm. {% codetabs %} ```bash npm npm install @statsig/react ``` {% /codetabs %} 2. Import the SDK in your `App.js` file: {% codetabs %} ```tsx Import SDK import { StatsigProvider } from "@statsig/react-bindings"; ``` {% /codetabs %} 3. Wrap your app's content within the `StatsigProvider` component. The following code snippet also creates a [User](/sdks/user) object for targeting your Feature Gate. {% codetabs %} ```tsx Setup Provider function App() { return ( <StatsigProvider sdkKey="client-KEY" user={{ userID: "Example", email: "example@statsig.com" }}> <div>Hello world</div> </StatsigProvider> ); } export default App; ``` {% /codetabs %} 4. Make sure to also replace `client-KEY` with the Client API Key you copied in Step 3. ### Check your Feature Gate Evaluate a Feature Gate in your product code by getting the client with the `useStatsigClient` hook and calling `checkGate`. If you aren't sure where to place your flag, refer to [Best Practices for feature flags](/feature-flags/best-practices). 1. Add the following code to your `App.js` file. In this snippet, the `example_gate` is the name of the Feature Gate you created in Step 1. {% codetabs %} ```tsx Check Gate const { client } = useStatsigClient(); return ( <div>Gate is {client.checkGate('check_user') ? 'passing' : 'failing'}.</div> ); ``` {% /codetabs %} 2. Run your app. The app renders "Gate is passing" because the [targeting rule](#create-a-targeting-rule) targets all users with the `statsig.com` email domain, and this client's User object uses that same domain. 3. After you set up your gate, [monitor the impact of your new feature rollout](/feature-flags/view-exposures) or [manage flag lifecycles](/feature-flags/feature-flags-lifecycle). ## Run your first A/B test This tutorial configured a simple feature flag. To do more complex feature rollouts or metric analysis, continue to the next tutorial to run your first A/B test in Statsig. ================================================================================ # Run your first A/B test URL: https://docs.statsig.com/guides/abn-tests Updated: 2026-07-11 ================================================================================ In this guide, you create and implement your first experiment in Statsig from end to end. There are many types of experiments you can set up in Statsig. This guide walks through the most common one: an A/B test. By the end of this tutorial, you have: * Created a new user-level **Experiment** in the Statsig console, with **parameters** set for a Control and Experiment group * **Checked the experiment** in your application code using the **Statsig Client SDK** `getExperiment` function ## Prerequisites 1. You already have a [Statsig account](https://console.statsig.com/sign_up) 2. You already [installed the Statsig Client SDK](/sdks/quickstart) in an existing application ## Step 1: In the Statsig console ### Create an experiment 1. Log in to the Statsig console at https://console.statsig.com/ and navigate to Experiments in the left-hand navigation panel. 2. Click on the **Create** button and enter the name `Quickstart Experiment` and enter a brief hypothesis. For example, "A new homepage banner will improve engagement." 3. Click **Create**. ### Set up scorecards In the Experiment Setup tab, fill out scorecards for the metrics you want to track. For this SDK tutorial, leave most settings at their default values. 1. In the **Hypothesis** card, enter a hypothesis for what you are testing: "Showing a homepage banner will increase user engagement, measured by daily\_stickiness." 2. In the **Primary Metrics** card, add a new metric called `daily_stickiness`. 3. Leave the **Secondary Metrics** and **Duration** settings at their default values. For more details on experiment setup, refer to [Creating an experiment](/experiments/create-new). ### Configure groups and parameters By default, the experiment runs on 100% of users: Statsig assigns 50% to a Control group and 50% to an Experiment group. 1. In the **Groups and Parameters** card, create a parameter called `enable_banner` and select the type as `Boolean`. 2. Set `enable_banner` to `false` for the Control group and `true` for the Experiment group. 3. Click **Save**. 4. Select **Save** in the bottom right to finalize this experiment setup. 5. The experiment isn't live yet. To launch it, select **Start** at the top of the page. This makes production traffic eligible for the experiment. {% callout type="note" %} You can't configure experiment parameters and groups after launching an experiment. To test in a staging environment, refer to [using environments](/guides/using-environments/#configuring-environments). {% /callout %} ## Step 2: In your application code ### Check the experiment {% callout type="note" %} This tutorial assumes you have already [installed the Statsig Client SDK](/sdks/quickstart) in an existing application. {% /callout %} Use a Statsig Client SDK to check a user's assigned experiment group and parameters in real time. The SDK changes the user's experience according to the assigned variant. In this case, fetch the value of the `enable_banner` parameter created in Step 1. {% codetabs %} ```tsx Check Experiment const quickstartExperiment = myStatsigClient.getExperiment(user, "quickstart_experiment"); // the second parameter is the default fallback value if the experiment is not found if (quickstartExperiment.get("enable_banner", false)) { showBanner(); } ``` {% /codetabs %} In this snippet, users assigned to the Experiment group see the banner. Users assigned to the Control group don't. You don't need to [hardcode the experiment group name](/experiments/implementation/getting-group) ("Control" or "Experiment") in the code. The Statsig SDK automatically fetches the experiment configuration and returns the correct value based on the user's variant. ## Step 3: Monitor experiment diagnostics When you run your code, the SDK calls the `showBanner()` function for users in the Test group. Navigate to your experiment in the Statsig console and select the **Diagnostics** tab to see a live log stream of checks and events from your application. Refer to the [Diagnostics tab documentation](/experiments/implementation/getting-group#rules) for more information. {% figure %} ![Experiment Diagnostics](/images/guides/abn-tests/b0ab8c0e-4f13-4b2b-bf58-1f8a09d8d4a7.png) {% /figure %} ## Step 4: Read experiment results Within 24 hours of starting your experiment, the **Results** tab in the Statsig console shows cumulative exposures and their impact on the scorecard metrics configured in the **Setup** panel. Here's a sample of what that looks like: {% figure %} ![Example experiment results](/images/guides/abn-tests/7e203d73-dbf9-4f27-87fb-b05269dd4173.png) {% /figure %} ## Log custom events and metrics In this tutorial, you used `daily_stickiness` as the primary metric. Statsig logs this metric automatically, so you don't need to log it explicitly. To measure other events or custom metrics that occur after the experiment check, log them to Statsig. Continue to the next tutorial to learn how to log custom events and metrics with the Statsig SDK. ### Related guides * [Initialization techniques for Statsig SDKs](/client/concepts/initialize) * [Run a device-level experiment](/guides/first-device-level-experiment) * [Run an experiment on custom unit ID types](/guides/experiment-on-custom-id-types) * [Configure the Statsig SDK for staging environments](/guides/using-environments) ================================================================================ # Log your first custom event URL: https://docs.statsig.com/guides/logging-events Updated: 2026-07-11 ================================================================================ Tracking events is the foundation of building better products. Whether you plan to use Statsig for web analytics, product analytics, or experimentation, defining the key events and metrics for your product is the best place to start. You can view event data in [Metrics Explorer](/product-analytics/overview), turn it into [custom metrics](/metrics/how-metrics-work), visualize it in [dashboards](/product-analytics/dashboards), and use it in [experiment results](/metrics/pulse) to show the impact of your experiments. Statsig provides several ways to get started: 1. Statsig web SDKs offer [autocaptured web analytics](/webanalytics/overview) to automatically log common events like pageviews, clicks, and more. 2. Statsig offers [integrations](/integrations/introduction) to connect your existing event data in Segment, mParticle, and other sources. 3. All Statsig client and server SDKs provide a simple `logEvent` API to instrument your own events in your app or webserver. For general guidance on event logging and core concepts, read on or navigate to the "Logging events via SDKs" section below. ## Identifying users with the StatsigUser object Many analytics platforms have a concept of "identifying" a user. In Statsig, this is the `StatsigUser` object, which is set at initialization time in client SDKs, or with each event in server SDKs. The [`StatsigUser`](/sdks/user) is a set of properties that describe the user. The JSON definition is consistent across all SDKs and integrations: {% codetabs %} ```json StatsigUser Object { "userID": "123", "customIDs": {}, "email": "user@example.com", "ip": "192.168.1.1", "userAgent": "Mozilla/5.0...", "country": "US", "locale": "en-US", "appVersion": "1.0.0", "custom": {}, "privateAttributes": [] } ``` {% /codetabs %} The `userID` field is reserved for a unique identifier for the user. Use the ID of the logged-in user. `customIDs` are explained in the "Group Analytics" section below. The other fields support targeting and evaluation for feature flags, and can also be used for custom metrics and data queries in Metrics Explorer. ## Group analytics Another core concept in analytics is grouping. A user can belong to a company, organization, page, or other entity. In Statsig, groups are represented by the `customIDs` field, a dictionary that can contain multiple IDs for a single user. For example, a user can have the following `customIDs`: {% codetabs %} ```json Group Example { "userID": "123", "customIDs": { "companyID": "456", "projectID": "abc" } } ``` {% /codetabs %} Statsig computes all metrics at the user level and also for each custom identifier. This means you can run experiments at a company level, where all users in a company receive the same experience, and compare the impact on company-level metrics. You can also use Metrics Explorer to slice metrics at the company level rather than the user level, if you have configured a `customID` for it. For example, the Statsig project can display console page views at a user or company level: {% figure %} ![Viewing Console Page Views at a User Level](/images/guides/logging-events/67719f66-63cb-41c7-8d83-d2a567738a84.png) {% /figure %} To set up a new "group" or "customID": 1. Go to your [project settings](https://console.statsig.com/settings) 2. Locate the "Custom IDs" section and hit "edit" {% figure %} ![CustomIDs in project settings](/images/guides/logging-events/e26cc341-13c2-4b78-898f-02d56bfc2474.png) {% /figure %} 3. Name the new `customID` and add a description so other members of your project understand its purpose 4. Start logging events with that customID in the `customIDs` field of the StatsigUser object {% callout type="note" %} You must provide the set of all IDs on each StatsigUser object. {% /callout %} ## Best practices ### Set all known IDs on each StatsigUser object To generate metrics for each user and group, Statsig requires all IDs on each `StatsigUser` object. Pass all identifiers for the user at client SDK initialization time or when calling `logEvent` in a server SDK, so each identifier contributes to the correct metrics. If you use a logged-out identifier for anonymous users, continue passing that identifier after the user logs in and you populate `userID`. This allows gates or experiments targeting the logged-out identifier to continue bucketing the user correctly and enables metrics to be calculated at both the logged-out and logged-in levels. ### Deduplicating custom events Statsig doesn't provide a way to deduplicate custom events that you log. ### Naming conventions Consistency is the most important aspect of event naming. An inconsistent events catalogue becomes difficult to navigate. Statsig accepts any naming convention (for example, `Page View`, `PageView`, `Page-view`, or `page_view`), but the recommended format is `page_view`: lowercase, no spaces. You can't use special characters in event names. Statsig drops events that contain the following character set: `"\\[\]{}<>#=;&$%|\u0000\n\r"`. Using the recommended convention avoids duplicate entries with different casing and ensures compatibility with downstream systems connected through integrations. ### What to measure For experimentation and product analytics, log events that correspond to user actions. How you classify events depends on your goals: 1. Avoid high-cardinality event names if you have a small user base, because sparse data reduces the reliability of metrics. For example, an eCommerce website should use a generic `product_page_view` event with contextual page information in the `optional_event_metadata` object, rather than a separate event name for every product page. 2. If you have a well-defined user funnel, log each step as a separate event. 3. If you have a less defined user journey, log generic events (for example, `page_view`) and add more detail to the value field (the primary dimension of interest) or the `optional_event_metadata` object. ## Logging events through SDKs All Statsig SDKs provide an API to log events. The API looks like this: {% codetabs %} ```js JavaScript statsig.logEvent( event_name, optional_event_value, optional_event_metadata ); ``` {% /codetabs %} Where event name describes a notable event in your product, like `sign_up`, `achievement_unlocked`, `add_to_cart`, `check_out`, etc. Events can optionally include an event value: for example, the type of achievement unlocked, the name of the product added to cart, or the price of the item purchased. In experiments, the value field generates an automatic breakdown of the event when there are fewer than 8 distinct values. The metadata field lets you specify additional details about the event. The following examples show how a `logEvent` call looks: {% codetabs %} ```js JavaScript Example statsig.logEvent( 'add_to_cart', // Name 19.99, // Price { item_id: 'BC22010', cart_size: '2', user_segment: 'first_time_purchaser', } ); ``` ```csharp C# Example StatsigClient.LogEvent( "level_completed", // Event Name 11, // Level number new Dictionary<string, string>() { { "score", "452" } } ); ``` {% /codetabs %} {% callout type="info" %} **Size limits on event payload** There are limits to how large each event field can be. Object fields have an overall limit of 4096 on its stringified length. String fields have a limit of 64 on its length. {% /callout %} Refer to the [SDK reference](/sdks/getting-started) for more details on logging events in your language. ================================================================================ # SDK Overview URL: https://docs.statsig.com/sdks/getting-started Updated: 2026-07-11 ================================================================================ Statsig's SDKs are the in-code tool you'll use to show experiment variants, flag features, and log key business metrics. Statsig's SDKs: * **Abstract the complexity:** Statsig handles caching, retry mechanisms, and networking. * **Encourage best practices:** Built around parameters and language-specific best practices. * **Broad deployment support:** 30+ SDKs across clients, servers, the edge, and more. *** ## When to use Statsig SDKs ### 1. Targeting and assignment Decide in code who sees new features and experiment variants. Target based on any **user attributes** (for example, location or device type) or **environment-level attributes** to control rollouts and experiment enrollment. ### 2. Logging events Log key business metrics to power experiment and product analytics. Events flow into Statsig automatically. For web-based platforms (for example, JavaScript, React), Statsig also supports **[Autocaptured events](/webanalytics/overview)**. *** ## Choose between client and server SDKs Statsig offers both **client-side** and **server-side** SDKs, each suited to different use cases: * **Client-side SDKs**: Designed for user-facing applications where events are logged directly from the browser or mobile app. These SDKs operate in real time and provide instant feedback on user behavior. * **Server-side SDKs**: Designed for backend services. Use server-side SDKs to control experiments, feature flags, and log server-side events. Server-side SDKs provide more control for use cases involving system-level actions or business logic. For a detailed comparison, go to the [Client vs Server SDK Overview](/sdks/client-vs-server). For frameworks like **Next.js** that bridge client and server-side logic, go to the [Next.js SDK](/client/Next) guide. *** ## Available SDKs Statsig offers SDKs for a wide variety of platforms: ### Client SDKs {% card-grid columns="3" %} {% card title="JavaScript" href="/client/javascript-sdk" icon="js" %} Browser JavaScript {% /card %} {% card title="React" href="/client/React" icon="react" %} Client-Side React {% /card %} {% card title="React Native" href="/client/ReactNative" icon="react" %} Bare React Native SDK {% /card %} {% card title="Next.js" href="/client/Next" icon="n" %} Next.js SSR, SSG & Client-Side {% /card %} {% card title="Angular" href="/client/Angular" icon="angular" %} Angular bindings for Javascript SDK {% /card %} {% card title="Swift" href="/client/iosClientSDK" icon="swift" %} iOS, MacOS, tvOS SDK {% /card %} {% card title="Android" href="/client/Android" icon="android" %} Android Kotlin/Java SDK {% /card %} {% card title=".NET Client" href="/client/DotNet" icon="dotnet" %} Client SDK for .NET framework {% /card %} {% card title="Roku" href="/client/Roku" icon="roku" %} Roku Brightscript SDK {% /card %} {% card title="Unity" href="/client/Unity" icon="unity" %} Unity game engine SDK {% /card %} {% card title="Dart/Flutter" href="/client/Dart" icon="flutter" %} Flutter/Dart Mobile App SDK {% /card %} {% card title="C++ Client" href="/client/CPP" icon="cplusplus" %} C++ client-side SDK {% /card %} {% /card-grid %} ### Server side SDKs {% card-grid columns="3" %} {% card title="Node.js" href="/server-core/node-core" icon="node-js" %} Node.js server SDK {% /card %} {% card title="Java" href="/server-core/java-core" icon="java" %} Java server SDK {% /card %} {% card title="Python" href="/server-core/python-core" icon="python" %} Python server SDK {% /card %} {% card title="Go" href="/server/go" icon="golang" %} Go server SDK {% /card %} {% card title="Ruby" href="/server/ruby" icon="ruby" %} Ruby server SDK {% /card %} {% card title=".NET Server" href="/server-core/dotnet-core" icon="dotnet" %} .NET server SDK {% /card %} {% card title="PHP" href="/server-core/php-core" icon="php" %} PHP server SDK {% /card %} {% card title="Rust" href="/server-core/rust-core" icon="rust" %} Rust server SDK {% /card %} {% card title="C++ Server" href="/server-core/cpp-core" icon="cplusplus" %} C++ server SDK {% /card %} {% /card-grid %} *** ## Install your SDK 1. **Select your SDK**: Choose the client or server SDK that fits your platform from the lists above. 2. **Follow the installation guide**: Each SDK has an installation guide that walks you through setup. 3. **Start experimenting**: After you integrate the SDK, set up feature flags, run experiments, and log events for analysis. If you have questions or need help with installation, reach out in the [Slack community](https://statsig.com/slack). ================================================================================ # User (StatsigUser) Object URL: https://docs.statsig.com/sdks/user Updated: 2026-07-11 ================================================================================ ## Introduction to the StatsigUser object The StatsigUser object is the sole input you provide to SDKs to target gates and assign users to experiments. To target on an attribute, add it to your user object. Assembling this data is an important part of SDK setup. Provide as much information as practical, because each additional field can enrich your analyses and expand targeting options. Statsig also infers some information about each user from other traits (for example, Statsig resolves IP addresses into countries). ## Usage example in Node.js {% codetabs %} ```jsx Node.js Example //Create a user object const user = new StatsigUser({ userID: "12345", email: "vincent@statsig.com"}); //Use it in getExperiment() const my_experiment = statsig.getExperiment(user, "my_experiment_name") //<- any attribute you pass, you can target on ``` {% /codetabs %} ### I passed that attribute before: why do I need to pass it again? You can view a specific user's historical attributes for analytics purposes through [User Profiles](/product-analytics/users-tab) in the Statsig console. Historical attributes can't be used for targeting or gate/experiment evaluation at runtime. Statsig evaluates gates and experiment buckets based **only on the information you provide at the time of the check.** Millisecond evaluation performance depends on having all information available at request time, rather than querying historical data or storing large amounts of state in memory. ## User attributes {% tabs %} {% tab title="Client SDKs" %} All user attributes can be explicitly supplied, and some can be inferred from a user's device or connection. Supplying one always overrides an inferred value. | Key | Description | Example | Client SDK Support | Auto-infer | |---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------|--------------------|------------| | `userID` | ID representing a unique user. Statsig uses this ID to guarantee targeting consistency for Feature Gates and Experiments and to evaluate experiment results. If `User ID` doesn't exist yet, leave this empty; Statsig uses a `Stable ID` persisted locally for evaluations. | `your_user_id` | All | | | `email` | Email of the user. | `marcos@statsig.com` | All | | | `userAgent` | User agent of the browser. The SDK decodes this to determine the Browser and Operating System of the user's context. The SDK infers this if not provided. | `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.40 Safari/537.36` | Web | ✔ | | `ip` | IP address of the user. Inferred from the request to /initialize if not provided | `192.168.1.101` | All | ✔ | | `country` | 2-letter country code of the user. This can be supplied or inferred, and you can target based on the country code in both cases. When inferred, the country code follows ISO-3166. | `US` | All | ✔ | | `locale` | Locale of the user. When using the Android or iOS SDK, the SDK infers this if not provided. | `en_US` | Mobile | ✔ | | `appVersion` | Version of the app the user is using. When using the Android or iOS SDK, the SDK infers this if not provided. | `1.0.1` | Mobile | ✔ | | `systemName` | When using the Android/iOS SDKs, the SDK automatically assigns this, but you can also provide an explicit operating system to override. | `Android` | All | ✔ | | `systemVersion` | When using the Android/iOS SDKs, the SDK automatically assigns this, but you can also provide an explicit OS version to override. | `15.4` | All | ✔ | | `browserName` | When using the Web SDK, the SDK automatically assigns this, but you can also provide an explicit Browser Name to override. | `Chrome` | Web | ✔ | | `browserVersion` | When using the Web SDK, the SDK automatically assigns this, but you can also provide an explicit Browser Version to override. | `45.0` | Web | ✔ | | `custom` | Dictionary that can contain key/value pairs that can be used for Feature Gate targeting. Statsig stores this dictionary and makes it available after targeting. | `{subscriber: "yes", ...}` | All | | | `privateAttributes` | Dictionary that can contain key/value pairs that can be used for Feature Gate targeting. Statsig does **not** store this dictionary after using it for targeting, and removes it from any `log_event` calls. | `{sensitive_field: "sensitive_information", ...}` | All | | | `customIDs` | Dictionary that can contain key/value pairs used as the randomization unit ID for experiments that are set up using these IDs instead of the `User ID`. | `{account_id: "23456555", company_id: "company_xyz"}` | All | | ### How to override the stable ID Client SDKs generate a `stableID` for logged-out or device-level targeting. If you already manage a durable device identifier, override the SDK-generated value before or during initialization so Statsig uses your identifier for stable ID-based evaluations. {% tabs %} {% tab title="JavaScript" %} ```tsx import { StatsigClient, StatsigUser } from '@statsig/js-client'; const user: StatsigUser = { userID: 'a-user', customIDs: { stableID: 'my-custom-stable-id', }, }; const client = new StatsigClient('client-sdk-key', user); await client.initializeAsync(); ``` {% /tab %} {% tab title="Android" %} ```kotlin val options = StatsigOptions(overrideStableID = "my-custom-stable-id") Statsig.initialize(app, "client-sdk-key", user, options = options) ``` {% /tab %} {% tab title="iOS" %} ```swift let options = StatsigOptions(overrideStableID: "my-custom-stable-id") Statsig.initialize(sdkKey: "client-sdk-key", user: user, options: options) ``` {% /tab %} {% /tabs %} {% callout type="note" %} When you override the Stable ID in a client SDK, the SDK persists that value locally and reuses it on later sessions unless local storage is cleared or the app is reinstalled. {% /callout %} {% /tab %} {% tab title="Server SDKs" %} All user attributes can be explicitly supplied, and some can be inferred from a provided IP Address or User Agent. Supplying one always overrides an inferred value. | Attributes | Description | Example | Auto Infer | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |------------| | `userID` | ID representing a unique user. Statsig uses this ID to guarantee targeting consistency for Feature Gates and Experiments and to evaluate experiment results. | `your_user_id` | | | `email` | Email of the user | `marcos@statsig.com` | | | `userAgent` | User agent of the browser. The SDK decodes this to determine the Browser and Operating System of the user's context | `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.40 Safari/537.36` | | | `ip` | IP address of the user | `192.168.1.101` | | | `country` | 2 letter country code of the user | `US` | ✔, from IP | | `locale` | Locale of the user | `en_US` | ✔, from IP | | `appVersion` | Version of the app the user is using | `1.0.1` | | `custom` | Dictionary that can contain key/value pairs that can be used for Feature Gate targeting. Statsig stores this dictionary and makes it available after targeting | `{skill_level: "5", is_subscriber:"false" ...}` | | `privateAttributes` | Dictionary that can contain key/value pairs that can be used to evaluate feature gate conditions and segment conditions. Statsig does **not** store this dictionary after using it for targeting, and removes it from any log\_event calls | `{sensitive_field: "sensitive_information", ...}` | | `customIDs` | Dictionary that can contain key/value pairs used as the randomization unit ID for experiments that are set up using these IDs instead of the `User ID` | `{account_id: "23456555", company_id: "company_xyz"}` | ### How to override "Operating System" and "Browser" explicitly Operating system and browser are two default targeting options in Statsig. When you set the `userAgent` field, server SDKs parse the OS and browser information from it. To set these values explicitly, provide them either at the top level of the user object (which some language type systems may not allow) or inside the `custom` object, using these keys: * Operating System: os\_name * OS Version: os\_version * Browser Name: browser\_name * Browser Version: browser\_version You can set these values either of the following ways in the user object: {% codetabs %} ```json Example { "userID": "uuid", "os_name": "Android", // top level "custom": { "os_name": "iOS" } } ``` {% /codetabs %} If you explicitly set either of these fields, it takes precedence over the value inferred from the `userAgent` field. {% /tab %} {% /tabs %} ### Have sensitive user PII data that shouldn't be logged? The StatsigUser object includes a `privateAttributes` field, which is a key/value dictionary for setting private user attributes. Statsig uses attributes in `privateAttributes` only for evaluation and targeting, and removes them from any logs before sending them to the Statsig server. For example, if you have feature gates that should only pass for users with emails ending in "@statsig.com" but don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ### Time-based conditions All SDKs (server and client-side) support Unix timestamps in milliseconds to evaluate time-based conditions (After time, Before time). Convert your DateTime field to a standard format before evaluation. Statsig has added ISO timestamp support to some server SDKs: * `java-server` SDK v1.6.0 and later: supports [DateTime fields in `ISO_INSTANT` format](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_INSTANT) * `go` SDK v1.12.1 and later: supports [DateTime fields in `RFC3339` format](https://pkg.go.dev/time#pkg-constants) * `python` SDK: supports epoch time in seconds. The `time.time()` function may include sub-second components, so round the value to an integer. For server SDKs supported by server core (Java, Python, PHP, Rust, Node), ISO timestamp support covers: * [DateTime fields in `ISO_INSTANT` format](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#ISO_INSTANT) * [DateTime fields in `RFC3339` format](https://pkg.go.dev/time#pkg-constants) ### Why is an ID always required for server SDKs? In server SDKs, a StatsigUser with a `userID` (or `customID`) is required for `checkGate`, `getConfig`, and `getExperiment`. Pass the user ID whenever it's available: users get a stable experience, and events are attributed to the correct users so you can accurately measure downstream metrics. Guidance for cases where you can't pass an ID: 1. **On/off feature gates or non-percent-based rules (such as country targeting):** Pass any non-empty identifier or a hardcoded string. If you do not have the actual user ID, use **a random ID under 100** as a placeholder. Do not use a purely random ID, because this prevents event deduplication, inflates event volume, and increases your Statsig bill. 2. **Partial rollout with regression checks before full rollout:** Pass an ID in every `checkGate`, `getConfig`, and `getExperiment` call, as well as in any `logEvent` calls. Without an ID, Statsig cannot attribute logged events to the correct users or calculate metrics accurately. 3. **A/B experiment to decide whether to ship a feature:** Pass persistent user IDs, for the same reason as case 2. 4. **No logged-in user (for example, optimizing the login flow):** Set a stable identifier as a cookie or in local storage and pass it with each Statsig call. Client SDKs provide a stable ID automatically. For use cases not covered here, reach out in the [Slack community](https://statsig.com/slack). ### Pass all IDs when you have them A common mistake is accidentally exposing users without a `userID` to a user-ID-based experiment. All such users are bucketed into the same group (wherever the null `userID` hashes to), which skews results. If you run experiments on multiple ID types, pass every available identifier. ================================================================================ # Initializing SDKs URL: https://docs.statsig.com/client/concepts/initialize Updated: 2026-07-11 ================================================================================ The first step in using a Statsig SDK is calling `initialize()`, which retrieves the values needed to evaluate experiments and send events. Before initialization, Statsig SDKs don't have the latest values in memory and may return stale values or none at all. Unlike Server SDK initialization, which happens at server startup, Client SDK initialization happens when a screen is rendered, so initialization has more impact on user experience. Statsig offers several client initialization methods to tune performance to your needs. {% tabs %} {% tab title="Client SDKs" %} ## General Initialization Flow `initialize` will take an SDK key and `StatsigUser` object. The SDK will then: 1. Check local storage for cached values. The SDK caches previous evaluations locally so they are available in the next session if there is no successful network call. 2. Create a `STATSIG_STABLE_ID`: an ID that stays consistent per device, which is useful for logged-out experiments. 3. Set the SDK as initialized so checks don't throw errors. Checks return cached values or defaults. 4. Issue a network request to Statsig to get the latest values for all gates/experiments/configs/layers/autotunes for the given user. If the project definition hasn't changed from the most recent cached values, this request may succeed without returning new data. 5. Resolve the asynchronous `initialize` call. If the request to the server failed, the SDK uses cached values or returns defaults for this session. Depending on when you check a gate or experiment after initializing, fresh values may not be available yet. Awaiting initialization resolves this, with some performance tradeoffs (discussed below). ## Client Initialization Strategies Below are the various strategies summarized at a high level, ordered from most common to least common: * [**Asynchronous Initialization (Awaited)**](#1-asynchronous-initialization-awaited): Wait for the initialization network call to finish before rendering content. * [**Bootstrap Initialization**](#2-bootstrap-initialization): Generate the assignment values on your own server, and pass them down with other request, resulting in zero-latency rendering. Best of both worlds for latency and availability of fresh assignments, but requires additional engineering effort. * [**Asynchronous Initialization (Not Awaited)**](#3-asynchronous-initialization-not-awaited): Do not await the return of the initialization network call. This ensures immediate rendering, but in a state that reflects stale assignments or no assignments available. * [**Synchronous Initialization**](#4-synchronous-initialization): Renders immediately, but with stale or no assignments available. First-visit users will never be assigned to gates and experiments. | Method | Speed-to-render? | Render consistency? | Latest content? | Engineering Complexity? | |-------------------------|---------------------|-----------------|-----------------|------------------| | **Explanation** | When I visit the webpage, how fast does the content appear? | Does the content ever change/flicker? | Does the user ever see an out-of-date config value? | How easy is this to implement? | | Await InitializeAsync() | ❌ Slow | ✅ Good | ✅ Yes | ✅ Easy | | InitializeAsync() | ✅ Fast | ❌ Poor | ✅ Yes | ✅ Easy | | InitializeSync() | ✅ Fast | ✅ Good | ❌ No | ✅ Easy | | BootstrapInit | ✅ Fast | ✅ Good | ✅ Yes | ❌ Extra Effort | ### 1. Asynchronous Initialization - Awaited > Ensures latest assignments but requires a loading state When calling `StatsigClient.initializeAsync`, the client loads values from the cache and fetches the latest values from the network. This approach waits for the latest values before rendering. It isn't immediate, but ensures the values are up to date. {% codetabs %} ```tsx React Example const { client, isLoading } = useClientAsyncInit( YOUR_CLIENT_KEY, { userID: "u_123" } ); if (isLoading) { return <div>Loading...</div>; } // Continue with initialized client ``` ```js JavaScript Example const client = new StatsigClient(YOUR_CLIENT_KEY, { userID: "u_123" }); await client.initializeAsync(); // Client is now initialized with latest values ``` {% /codetabs %} ### 2. Bootstrap Initialization > Ensures both latest assignments with no rendering latency Bootstrapping allows you to initialize the client with a JSON string. Values are immediately available without the client making any network requests. You are responsible for keeping these values up to date. Your server serves the configuration payload to your client app on page load (for web implementations) or during app launch (for mobile implementations). {% codetabs %} ```tsx React Bootstrap // Server-generated initialization values const initValues = getStatsigValuesFromServer(user); const client = useClientBootstrapInit( YOUR_CLIENT_KEY, { userID: "u_123" }, initValues ); // Client renders immediately with server values — no network request ``` ```js JavaScript Bootstrap const bootstrapValues = getInitializationValuesFromServer(); const client = new StatsigClient(YOUR_CLIENT_KEY, { userID: "u_123" }); client.dataAdapter.setData(bootstrapValues); client.initializeSync(); ``` {% /codetabs %} ### 3. Asynchronous Initialization - Not Awaited To fetch the latest values without awaiting the asynchronous call, call `initializeAsync` and catch the promise. This approach provides immediate rendering with cached values initially, then updates to the latest values mid-session. {% callout type="warning" %} Be aware that the values may switch when checked a second time after the latest values have been loaded. {% /callout %} ### 4. Synchronous Initialization > Ensures immediate rendering but uses cached assignments (when available) When calling `StatsigClient.initializeSync`, the client uses cached values if they are available. New values are fetched in the background and the cache is updated. This approach provides immediate rendering, but values may be stale or absent during the first session. {% /tab %} {% tab title="Server SDKs" %} ## General Initialization Flow Server SDKs require only a secret key to initialize. Because servers handle many users, server SDKs download all rules and configurations in your project and evaluate them in real time for each user. The Server SDK initialization process: 1. Your server checks if you have locally cached values (which you can set up with a [DataAdapter](/server/concepts/data_store/)). 2. If your server found values on the last call, it's ready for checks with the reason "DataAdapter". Whether it found local data or not, it next goes to the network to find updated values. 3. The server retrieves updated rules from the network, and is now ready for checks even if it didn't find values in step 1. 4. Going forward, the server retrieves new values every 10 seconds from the network, updating the locally cached values each time. DataAdapters provide resilience and ensure your server is ready to serve requests as soon as it starts up, without waiting for a network round trip. This is especially useful for short-lived or serverless instances. For advanced setups, Statsig offers a [Forward Proxy](/server/concepts/forward_proxy/) for additional resilience. {% codetabs %} ```js Node.js Initialization import { Statsig, StatsigUser } from '@statsig/statsig-node-core'; const statsig = new Statsig("YOUR_SERVER_KEY"); await statsig.initialize(); // Server SDK is now ready to evaluate gates/experiments for any user const user = new StatsigUser({ userID: "123" }); const gate = statsig.checkGate(user, "my_gate"); ``` ```python Python Initialization from statsig_python_core import Statsig, StatsigUser statsig = Statsig("YOUR_SERVER_KEY") statsig.initialize().wait() # Server SDK is now ready user = StatsigUser("123") gate = statsig.check_gate(user, "my_gate") ``` {% /codetabs %} {% /tab %} {% /tabs %} ================================================================================ # How Evaluation Works URL: https://docs.statsig.com/sdks/how-evaluation-works Updated: 2026-07-11 ================================================================================ ## Why evaluation matters The essential function of the Statsig SDKs is reliable, consistent, high-performance allocation of users to the correct bucket in your experiment or feature gate. Understanding how evaluation works can help you answer questions like: * Why do I have to pass every user attribute every time? * Why do I have to wait for initialization to complete? * When does Statsig decide each user's bucket? ## How evaluation works Evaluation in Statsig is deterministic. Given the same user object and the same state of the experiment or feature gate, Statsig always returns the same result, even across different platforms (client or server). The process is: 1. **Salt Creation**: Each experiment or feature gate rule generates a unique salt. 2. **Hashing**: The user identifier (e.g., userId, organizationId) is passed through a SHA256 hashing function, combined with the salt, which produces a large integer. 3. **Bucket Assignment**: The large integer is then subjected to a modulus operation with 10000 (or 1000 for layers), assigning the user to a bucket. 4. **Bucket Determination**: The result defines the specific bucket out of 10000 (or 1000 for layers) where the user is placed. This process ensures randomized but deterministic bucketing across experiments and feature gates. The unique salt per rule ensures that the same user can be assigned to different buckets in different experiments. If you roll out a feature gate rule to 50%, back to 0%, then back to 50%, the same 50% of users are re-exposed, **as long as you reuse the same rule** and don't create a new one. Go to the [FAQ](/faq/#when-i-change-the-rollout-percentage-of-a-rule-on-a-feature-gate-will-users-who-passed-continue-to-pass) for details. For more details, go to the [open-source SDK evaluator](https://github.com/statsig-io/node-js-server-sdk/blob/main/src/Evaluator.ts). For advanced use cases (for example, a series of related experiments that must reuse control and test buckets), Statsig exposes the ability to copy and set the salts used for deterministic hashing. Use this with care. This option is only available to Project Administrators and appears in the Overflow (...) menu in Experiments. ## Evaluation order When evaluating gates, experiments, and layers, the SDK iterates through a list of rules generated by the server. Rules are evaluated sequentially and the first matching rule determines the result. Overrides always take precedence because they appear first in the rule list. {% callout type="note" %} Each step uses the hash-based bucketing described above. Layer allocation and group assignment use different salts, so a user's position in the layer is independent of their group assignment within the experiment. {% /callout %} ### Experiments When an experiment is evaluated (you call `getExperiment`), it follows this evaluation order: 1. **ID overrides**: Specific user/unit IDs mapped to a group 2. **Conditional overrides**: Segment or gate-based overrides, evaluated in order 3. **Layer holdouts**: If the experiment is in a layer, layer-level holdout gates are checked 4. **Holdout gates**: Experiment-level holdout gates; users in a holdout receive default values 5. **Experiment exclusion**: Mutual exclusion segments that prevent users from being in multiple experiments 6. **Start status**: If the experiment is not started, users receive default values (with optional non-production environment exceptions) 7. **Layer allocation**: For experiments in a layer, the user's bucket (based on the layer's universe salt) must fall within the experiment's allocated segments. This is checked **before** targeting. 8. **Targeting gate**: Users who fail the targeting gate receive default values. This is checked **after** layer allocation. 9. **Group assignment**: The user's bucket (based on the experiment salt) determines which group they fall into. Groups are cumulative ranges across 1000 buckets. ### Layers When a layer is evaluated (you call `getLayer`), it follows this evaluation order: 1. **Override rules**: ID overrides from all experiments in the layer 2. **Layer holdout gates**: Holdout gates attached to the layer 3. **Experiment allocation**: Each experiment in the layer has a `configDelegate` rule. The user's bucket determines which experiment they are delegated to. 4. **Delegated experiment evaluation**: Once delegated, the experiment's own evaluation runs (start status, targeting gate, group bucketing as described above) If no experiment allocation rule matches, the user receives the layer's default values. ### Holdouts Holdout gates evaluate in this order: 1. **Experiment exclusion**: Exclusion segments (if applicable) 2. **ID overrides**: Specific user/unit IDs 3. **Population targeting gate**: If the holdout has a targeting gate, users who fail it are not held out 4. **Holdout percentage**: The pass percentage on the holdout rule determines the holdout rate ### Gates When a feature gate is evaluated (you call `checkGate`), it follows this evaluation order: 1. **ID overrides**: Specific user/unit IDs mapped to pass/fail 2. **Conditional overrides**: Segment or gate-based overrides 3. **Holdout rules**: If the gate has holdouts attached 4. **Rules**: The gate's targeting rules, evaluated in the order they appear in the console. Each rule has its own conditions and pass percentage. ## When evaluation happens On server SDKs, evaluation happens when the gate or experiment is checked. Server SDKs hold the entire ruleset of your project in memory (a JSON representation of each gate or experiment). On client SDKs, Statsig evaluates all gates and experiments server-side when you call `initialize`. This logic applies to both SDK types. In both cases, the user's assignment bucket isn't sent to Statsig until you call `getExperiment` or `checkGate`. ## What this means * **Performant evaluation:** No evaluations require a network request. Checks take under 1 ms after initialization. * **SDKs don't remember user attributes or previous evaluations:** Pass all required user attributes consistently on every call. Statsig returns the same value for the same inputs. A common assumption is that Statsig maintains a list of all IDs and their assigned groups. Statsig's data pipelines track users exposed to each variant to compute experiment results, but Statsig doesn't cache previous evaluations or maintain distributed evaluation state across client and server SDKs. * **Server SDKs can handle multiple users:** Because server SDKs hold the ruleset in memory, they can evaluate any user without a network request. Pass a user object into `getExperiment` on server SDKs; on client SDKs, pass the user object into `initialize`. * **Each user receives the same bucket:** ID-based hashing guarantees consistency. If you make a change in the console that could affect user bucketing on an experiment, Statsig provides a warning. ## Evaluation of null or empty unit IDs Statsig doesn't apply any filtering or business logic before assigning an individual user ID. Statsig buckets a null or empty unit ID based on the salt. ================================================================================ # SDK Debugging URL: https://docs.statsig.com/sdks/debugging Updated: 2026-07-11 ================================================================================ ## Debugging tools When a user sees an unexpected value, start with the tooling built into the Statsig console and SDKs. ### Diagnostics & log stream Every gate, config, experiment, and layer has both a **Setup** and **Diagnostics** tab. The diagnostics view highlights pass/fail rates and bucketing counts so you can spot anomalies over time. {% figure %} ![Diagnostics tab showing pass and fail counts](/images/sdks/debugging/diagnostics-overview.png) {% /figure %} Scroll to the log stream to inspect individual evaluations. Entries arrive within seconds for both production and non-production environments. {% figure %} ![Log stream showing recent exposures](/images/sdks/debugging/log-stream.png) {% /figure %} {% callout type="note" %} Enable **Show non-production logs** in the diagnostics view to surface checks coming from test keys and development builds. {% /callout %} ### Logging levels and expected information Statsig SDKs emit runtime logs across four verbosity levels: * `Debug` – deep tracing meant for onboarding or issue triage. * Missing gate/config warnings when a definition is unavailable. * Step-by-step messages that follow SDK initialization and evaluations. * `Info` – healthy lifecycle events for day-to-day operation. * Initialization summaries with source and SDK version details. * Notifications when the configuration store is populated. * `Warning` – recoverable issues that might affect functionality. * Non-critical errors automatically handled by the SDK. * gRPC reconnection attempts or similar transient network events. * `Error` – critical failures that block expected behavior. * Initialization timeouts or outright failures. * Fallback notices that indicate gRPC is unavailable or misconfigured. ## Evaluation details Click any exposure in the log stream to drill into the precise rule, user attributes, evaluation reason, SDK metadata, and server timestamps. That detail is often enough to pinpoint why a user received a specific value. {% figure %} ![Evaluation details modal with rule match information](/images/sdks/debugging/evaluation-details.png) {% /figure %} ### Evaluation reasons Evaluation reasons answer two questions: where the SDK sourced its definitions and why a particular value was returned. Use them to distinguish between healthy results, overrides, and error states. {% figure %} ![Evaluation reason popover showing source and reason](/images/sdks/debugging/evaluation-reason.png) {% /figure %} {% tabs %} {% tab title="Client SDKs" %} #### Data source | Source | Description | Type | Debugging Suggestions | | --- | --- | --- | --- | | `Network` | Values fetched during initialization from Statsig servers. | Normal | None | | `Bootstrap` | Supplied during bootstrap (often from a server SDK). | Normal | None | | `Prefetch` | Loaded via the `prefetchUsers` API (JS only). | Normal | None | | `NetworkNotModified` | Network request succeeded but cached values were already current. | Normal | None | | `Sticky` (legacy) | Persisted from a sticky evaluation. | Normal | None | | `LocalOverride` (legacy) | Set locally via override APIs. | Normal | None | | `Cache` | Served from local cache because network values were unavailable. | Warning | Ensure `initialize()` has completed before checks run. | | `InvalidBootstrap` / `BootstrapPartialUserMatch` | Bootstrap values were generated for a different user profile. | Error | Refer to [Fixing InvalidBootstrap](#invalid-bootstrap). | | `BootstrapStableIDMismatch` | StableID differed between bootstrap and runtime user. | Error | Refer to [BootstrapStableIDMismatch](#bootstrapstableidmismatch). | | `Error` | A generic evaluation failure that was logged to Statsig. | Error | Ask for help in [Slack](https://statsig.com/slack). | | `Error:NoClient` (JS) | No Statsig client was found in context. | Error | Wrap checks in `<StatsigProvider>` or equivalent. | | `Unrecognized` (legacy) | The definition was missing from the initialize payload. | Error | Confirm the config exists and the correct API key is used. | | `NoValues` | Initialization ran but failed to retrieve values. | Error | Verify client key and network connectivity. | | `Loading` | Initialization is still in progress. | Error | Await `initializeAsync()` or guard checks until ready. | | `Uninitialized` | Initialization never started. | Error | Call `initializeAsync()`/`initializeSync()` explicitly. | | `UAParserNotLoaded` | UA parsing was disabled while targeting relies on it. | Error | Remove UA-based targeting or re-enable parsing. | | `CountryLookupNotLoaded` | Country lookups were disabled while targeting relies on them. | Error | Avoid IP-based targeting or re-enable lookups. | #### Reason (new SDKs only) | Reason | Description | Type | Debugging Suggestions | | --- | --- | --- | --- | | `Recognized` | The definition was present and matched the current values. | Normal | None | | `Sticky` | Result persisted because `keepDeviceValue` was set. | Normal | None | | `LocalOverride` | Value came from a developer override. | Normal | None | | `Unrecognized` | Definition missing from the payload. | Error | Confirm targeting and [Target Apps](/sdks/target-apps). | | `Filtered` | Definition filtered from `/initialize` because the default value was `false`. | Error | Check [client bootstrapping](/client/concepts/initialize#bootstrapping-overview) or targeting. {% /tab %} {% tab title="Server SDKs" %} #### Data source | Source | Description | Type | Debugging Suggestions | | --- | --- | --- | --- | | `Network` | Configurations loaded from Statsig servers at initialization. | Normal | None | | `Bootstrap` | Server SDK bootstrapped with precomputed values. | Normal | None | | `DataAdapter` | Values retrieved from a data adapter or external store. | Normal | Review your [data adapter setup](/server/concepts/data_store#dataadapter-or-datastore). | | `LocalOverride` (legacy) | Value supplied via server-side override APIs. | Normal | None | | `StatsigNetwork` | Proxy/streaming fell back to Statsig APIs directly. | Fallback | Revisit your [proxy configuration](/server/concepts/forward_proxy/). | | `Uninitialized` | SDK has not completed initialization. | Error | Ensure initialization succeeds before evaluations. | | `Unrecognized` (legacy) | Definition missing from the cached payload. | Error | Confirm configuration exists and the API key is correct. | #### Reason (new SDKs only) | Reason | Description | Type | Debugging Suggestions | | --- | --- | --- | --- | | `LocalOverride` | Result supplied by a developer override. | Normal | None | | *None* | Successful evaluation with the expected payload. | Normal | None | | `Unrecognized` | Definition missing from the payload. | Error | Confirm configuration exists and the API key is correct. | | `Unsupported` | SDK lacks support for a condition/operator. | Error | Upgrade to the latest SDK version. | | `Error` | Generic evaluation failure captured by the SDK. | Error | Ask for help in [Slack](https://statsig.com/slack). | {% /tab %} {% /tabs %} ### Evaluation times Evaluation timestamps reveal whether an SDK is serving fresh definitions. An up-to-date LCUT (Last Config Updated Time) indicates the SDK has the latest changes. If LCUT lags behind, users may be seeing stale values. This can happen because a browser tab remained open or because a server integration cannot sync. {% tabs %} {% tab title="Client SDKs" %} | Time Field | Description | SDKs | | --- | --- | --- | | `LCUT` | Time of the most recent config change reflected in the SDK. | JavaScript (incl. React & RN), iOS, Dart | | `receivedAt` | Timestamp when the client obtained the current values. | JavaScript (incl. React & RN), iOS, Dart | {% /tab %} {% tab title="Server SDKs" %} | Time Field | Description | SDKs | | --- | --- | --- | | `initTime` | LCUT captured when the server SDK initialized. | All server SDKs except Rust | | `configSyncTime` | Latest LCUT received from the network or data adapter. | All server SDKs except Rust | | `serverTime` | Current server clock time when the evaluation was made. | All server SDKs except Rust | {% /tab %} {% /tabs %} ### Mocking Statsig / local mode Use the following tools to validate code paths without making network requests: * **Local mode:** Set `localMode` to `true` so the SDK skips network calls and returns default values. This is useful for tests and offline environments. * **Override APIs:** Call `overrideGate` and `overrideConfig` to force specific values for an individual user or globally. Use both techniques together to exercise each branch of your application before shipping. ### Client SDK debugger Inspect the exact values a client SDK is using by opening the Client SDK Debugger. It exposes the active user object and every gate/config tied to that client. * **JavaScript / React:** Use the [Chrome extension](https://github.com/statsig-io/statsig-sdk-debugger-chrome-extension). * **iOS:** Call `Statsig.openDebugView()` in [v1.26.0](https://github.com/statsig-io/statsig-kit/releases/tag/v1.26.0) or later. * **Android:** Call `Statsig.openDebugView()` in [v4.29.0](https://github.com/statsig-io/android-sdk/releases/tag/4.29.0) or later. {% callout type="note" %} Accounts that sign in to the Statsig console through Google SSO aren't supported by the Chrome extension. {% /callout %} | Landing | Gates List | Gate Details | Experiment Details | | --- | --- | --- | --- | | ![Client debugger landing view](/images/sdks/debugging/client-debugger-landing.png) | ![Client debugger gates list](/images/sdks/debugging/client-debugger-gates.png) | ![Client debugger gate details](/images/sdks/debugging/client-debugger-gate-details.png) | ![Client debugger experiment details](/images/sdks/debugging/client-debugger-experiment.png) | ## FAQ For SDK-specific edge cases, check each SDK’s FAQ or reach out to the Statsig team in the [Statsig Slack community](https://statsig.com/slack). ### Invalid bootstrap `InvalidBootstrap` occurs when a client SDK is bootstrapped with values generated for a different user profile. The bootstrap user must exactly match the runtime user object. ```js // Server side const userA = { userID: 'user-a' }; const bootstrapValues = Statsig.getClientInitializeResponse(userA); // Client side const bootstrapValues = await fetchStatsigValuesFromMyServers(); const userB = { userID: 'user-b' }; // <-- Different from userA await Statsig.initialize('client-key', userB, { initializeValues: bootstrapValues }); ``` Even subtle differences count as a mismatch. Adding `customIDs` or other attributes results in a distinct user object. ```js const userA = { userID: 'user-a' }; const userAExt = { userID: 'user-a', customIDs: { employeeID: 'employee-a' } }; ``` ### BootstrapStableIDMismatch `BootstrapStableIDMismatch` is similar to `InvalidBootstrap` but focuses on `stableID`. Client SDKs generate a stable ID automatically when one is not provided, so mixing empty user objects between server and client code can cause drift. ```js // Server side const userA = {}; const bootstrapValues = Statsig.getClientInitializeResponse(userA); // Client side const bootstrapValues = await fetchStatsigValuesFromMyServers(); const userB = { stableID: '12345' }; // <-- Server user lacked a stableID await Statsig.initialize('client-key', userB, { initializeValues: bootstrapValues }); ``` Even if both sides start with `{}`, the client-generated stable ID may not match the server’s stable ID, which triggers the same mismatch warning. ```js const userC = {}; // Client SDK auto-generates a stableID await Statsig.initialize('client-key', userC, { initializeValues: bootstrapValues }); ``` ### Environments SDKs inherit their environment from initialization options. If you don't provide an environment, the SDK defaults to production. To verify which environment a user evaluated under, open the diagnostics log stream and inspect the `statsigEnvironment` property on the exposure entry. ### Maximizing event throughput {% callout type="info" %} Python SDK v0.45.0+ introduces tunables that help handle high event volume without drops. {% /callout %} The SDK batches events and retries failures in the background. When throughput spikes, adjust these options to reduce dropped events: * **`eventQueueSize`** – Number of events flushed per batch. Larger batches increase throughput but use more memory. Keep the value under approximately 3000 to avoid oversized payloads. * **`retryQueueSize`** – Number of batches kept for retrying. The default is 10. Raise this value to retain more data at the cost of higher memory usage. Tune both settings to match your traffic profile. ================================================================================ # Client vs Server SDKs URL: https://docs.statsig.com/sdks/client-vs-server Updated: 2026-07-11 ================================================================================ Statsig offers client and server SDKs to enable experimentation and feature management across different parts of your application. This page outlines when to choose each. ## Overview **Client SDKs** run in code that executes on end-user devices, such as a website, mobile app, video game, or smart TV app. **Server SDKs** run on your servers (typically in the cloud), such as a web server or API server. Many customers deploy both Server and Client SDKs, which lets them gate features on both the client and server side. While this is common, you can get value from Statsig by starting with one SDK type. ## Usage Client and Server SDKs follow a similar setup pattern: 1. **Initialize:** Set up the SDK and download the latest values. 2. **Check an experiment/gate:** Reference those values to assign an experiment or flag a feature. 3. **Log custom events:** Log important app metrics to power your analyses. {% callout type="note" %} After initialization, both Client and Server SDKs evaluate experiments/gates *without a network request*, and typically in less than 1ms. Checks in the Statsig SDKs are designed to be very efficient. {% /callout %} ## Conceptual differences * Data Privacy: The Server SDK runs in a secure, multi-user environment, so Server SDKs have access to the full ruleset describing each experiment and gate. Client SDKs fetch only the value for a single user, avoiding exposure of your configuration definitions. ||Server| Client | |--|--|--| |Privacy | Your servers operate in a secure, multi-user environment, so Server SDKs have access to the definition of all configurations in your project|Statsig precomputes all evaluations on its servers and sends results to your client applications. Names are obfuscated, but a savvy user may be able to glean information from the raw response| |Evaluation Performance | The SDK evaluates in real time, without a network request. Very complex configurations can take longer to compute, but in practice, this is rarely an issue.|As Statsig precomputes all evaluation, gate and experiment checks are effectively a dictionary lookup with some computation used for creating and flushing exposure events| |Initialization Performance | The SDK makes an upfront request for configuration files, then continually polls for any changes to your configurations, updating its internal state when it detects a change| Client SDKs download configurations when you initialize, before which, the SDK may not have usable values. Values don't update mid-session unless you explicitly call updateUser. Additional options are available for performant initialization; refer to [Initializing](/client/concepts/initialize)| |Users|Server SDKs are designed to run against multiple users, and all SDK methods require a user object for evaluation/logging |Client SDKs are designed to run with one user at a time. The SDK loads all evaluations once during initialization, and every event logged uses that user object| |Infrastructure | Server SDKs require you to host your own backend services| Client SDKs run entirely on the client and use Statsig's servers| ## Usage differences ||Server| Client | |--|--|--| |Initializing| Requires only a secret key, downloads the entire ruleset and syncs it in the background | Requires a client key and a user object. Before/during initialization, the SDK attempts to fall back to cached values.| |Checking an Experiment| Requires a user object, which the SDK evaluates locally (without a network request) against a ruleset in memory | Doesn't require a user object, uses a dictionary lookup for values fetched during initialize() | |User Identifiers| Pass any and all useful user identifiers | Pass any useful identifiers, the SDK also generates a "StableID", Statsig's anonymous ID you can use to experiment on a user per-device| |Logging Events| Requires a user object | Doesn't require a user object. There is some risk of adblocking log events on client SDKs, which you can minimize by setting up a [Custom Proxy](/infrastructure/api_proxy/custom_proxy)| |Flushing Events| Batched and flushed by the SDK every 60 seconds | Batched and flushed by the SDK every 10 seconds| |Updating Configurations| Poll Statsig servers for updates every 10 seconds by default (configurable), Streaming possible with some Server SDKs and the [Statsig Forward Proxy](/server/concepts/forward_proxy) | Configuration persists until next `initialize` or `updateUser` call; call `initialize` at the start of each user session| ## Initialize and update logic **Client SDKs:** * Configuration persists until next `initialize` or `updateUser` call * Call `initialize` at the start of each user session {% figure %} ![Client SDKs](/images/client/introduction/169555378-a1ba4c22-1d13-46c5-956e-ba87abe9b549.png) {% /figure %} **Server SDKs:** * Poll Statsig servers for updates every 10 seconds by default (configurable) * Some SDKs support grpc streaming updates by connecting to the [Statsig Forward Proxy](/server/concepts/forward_proxy) {% figure %} ![Server SDKs](/images/server/introduction/169563936-121cb75e-37e5-491f-8bb1-2d853c2e04b8.png) {% /figure %} ## Available SDKs ### Client SDKs * [Javascript](/client/javascript-sdk) * [React](/client/React) * [React Native](/client/ReactNative) * [Expo](/client/Expo) * [iOS](/client/iosClientSDK) * [Android](/client/Android) * [.Net](/client/DotNet) * [Unity](/client/Unity) * [Roku](/client/Roku) * [C++](/client/CPP) * [Dart/flutter](/client/Dart) ### Server SDKs * [Node.js](/server/nodejsServerSDK) * [Java](/server/java) * [Python](/server/pythonSDK) * [Go](/server/go) * [Ruby](/server/ruby) * [.NET Core](/server-core/dotnet-core) * [C++](/server-core/cpp-core) * [PHP](/server/php) * [Rust](/server/rust) For more information on each SDK, refer to their respective documentation pages. Got questions? Join the Statsig engineering and product team on the [Statsig Slack channel](https://statsig.com/slack). ================================================================================ # Data Privacy for Mobile URL: https://docs.statsig.com/compliance/data_privacy_for_mobile Updated: 2026-07-11 ================================================================================ ## General ### What data does Statsig collect from users of my app? Statsig collects only the data that you configure to be sent to Statsig. This is typically the occurrence of feature flag evaluations (Feature Gates), experiment exposures, and custom events you log with the SDK. ### Does that data include any personally identifiable information (PII)? By default, Statsig uses randomly generated IDs as described in this article. You can also augment data sent to Statsig with additional context and metadata, including user names, email addresses, or custom attributes. This data, alone or in combination with other data, may constitute PII if it identifies an individual directly or indirectly. ### Does Statsig use the device ID to identify a user? No. Statsig on Mobile doesn't use device IDs such as `Secure.ANDROID_ID` or `advertisingIdentifier` on iOS. Instead, Statsig uses a StableID, which is randomly generated per device, per app, and per installation, and can't be used to identify a single device across installations. A new StableID is generated when an application is reinstalled. These IDs provide approximate statistical information as part of the application monitoring service, for example, for Crash Free Session and User Rates, and to indicate the impact of issues based on number of events versus affected users. ### What does Statsig do with the data it collects? Statsig processes the data you send to it to provide feature flag management, experimentation, and analytics services. ## Apple App Store ### Do I need to disclose the use of Statsig in App Store Connect on the Apple App Store? Yes, Statsig is a third-party partner whose code (SDKs) you integrate in your app that collects data from users of your app. ### What do I need to disclose to Apple? Disclose all types of data you are collecting through your app, including data you are sending to Statsig. This may include "Contact Info" or "Identifiers" if you provide those in the `StatsigUser` object. Include any other categories of data you have configured the SDKs to send to Statsig. ### How does Statsig use my data? The standard data use cases for Statsig are "Analytics" and "App Functionality". Disclose to Apple any other ways in which you or your app use the data you collect. ### Does Statsig use my data to track users? Statsig doesn't use your data to track users. However, if you or your other third-party partners are tracking users, you still need to disclose this to Apple. ### Does Statsig use the Advertising Identifier (IDFA)? No. Statsig doesn't require IDFA. ## Google Play ### Does Statsig collect any PII from children? If your app targets children and you configure Statsig to collect PII, Statsig collects the elements you have designated. You remain responsible for obtaining appropriate parental consents for the PII you collect from users and send to Statsig. You also remain responsible for declaring your app's target age group to Google Play. ### Do Statsig SDKs cause my app to contain ads? No. Statsig doesn't cause your app to contain any ads. ### What do I need to disclose to Google Play? Disclose in your app privacy policy all types of data you are collecting through your app, including data you are sending to Statsig. ## Privacy Controls ### What device and user metadata does Statsig automatically collect? The Statsig SDKs automatically collect the following metadata for targeting and analytics purposes: **iOS SDK collects:** * appIdentifier: Your app's bundle identifier * appVersion: Your app's version * deviceModel: The device model (e.g., iPhone14,3) * deviceOS: The operating system (iOS) * language: The user's preferred language * locale: The user's locale identifier * sdkType: The type of SDK (ios-client) * sdkVersion: The version of the Statsig SDK * sessionID: A randomly generated UUID for the current session * stableID: A persistent device identifier (see below) * systemVersion: The iOS version * systemName: The system name (iOS) **Android SDK collects:** * appIdentifier: Your app's package name * appVersion: Your app's version name * deviceModel: The device model (e.g., Pixel 7) * deviceOS: The operating system (Android) * locale: The user's locale * language: The user's language * sdkType: The type of SDK (android-client) * sdkVersion: The version of the Statsig SDK * sessionID: A randomly generated UUID for the current session * stableID: A persistent device identifier (see below) * systemVersion: The Android API level * systemName: The system name (Android) ### How can I limit the metadata collected by Statsig? Both iOS and Android SDKs provide the `optOutNonSdkMetadata` option to limit the collection of device-specific information: **iOS SDK:** ```swift let options = StatsigOptions() options.optOutNonSdkMetadata = true Statsig.start(sdkKey: "client-sdk-key", options: options) ``` **Android SDK:** ```kotlin val options = StatsigOptions(optOutNonSdkMetadata = true) ``` When `optOutNonSdkMetadata` is enabled, only the following core SDK metadata is included: * sdkType: The type of SDK * sdkVersion: The version of the Statsig SDK * sessionID: A randomly generated UUID for the current session * stableID: A persistent device identifier All device-specific information (appIdentifier, appVersion, deviceModel, deviceOS, locale, language, systemVersion, systemName) is excluded from logs and targeting. ### How does StableID work? The StableID is a persistent identifier that Statsig uses to provide consistent user experiences and analytics: **iOS Implementation:** * The StableID is stored in UserDefaults with the key "com.Statsig.InternalStore.stableIDKey" * When first generated, the StableID is created as a random UUID * The StableID persists across app launches but is regenerated when the app is reinstalled * The StableID can be overridden through the `StatsigOptions.overrideStableID` parameter **Android Implementation:** * The StableID is stored in SharedPreferences * When first generated, the StableID is created as a random UUID * The StableID persists across app launches but is regenerated when the app is reinstalled * The StableID can be overridden through the `StatsigOptions.overrideStableID` parameter The StableID isn't shared across different apps or websites and can't be used to track users across different applications or platforms. ### How can I prevent sending sensitive user data to Statsig? Use the `privateAttributes` field for sensitive data you want to use for targeting but not log: **iOS SDK:** ```swift let user = StatsigUser( userID: "user-123", email: nil, // Not included at top level to keep private privateAttributes: ["email": "user@example.com"] // Used for evaluation but not logged ) ``` **Android SDK:** ```kotlin val user = StatsigUser("user-123") user.privateAttributes = mapOf("email" to "user@example.com") ``` The SDK sends these attributes to Statsig servers during initialization for feature flag and experiment evaluation, but Statsig removes them before sending any event logs to Statsig servers. Use the attributes for targeting users with specific features or experiments, but they don't appear in your analytics data. For more comprehensive privacy controls, use [Client Bootstrapping](/client/concepts/initialize#bootstrapping-overview) to generate all assignments locally on your server, which eliminates the need to send any user attributes from the client device to Statsig. ================================================================================ # JavaScript Client SDK (Web) URL: https://docs.statsig.com/client/javascript-sdk Updated: 2026-07-11 ================================================================================ ## Set Up the SDK {% steps %} {% step title="Install the SDK" %} To install the Statsig Web SDK, add the package using your preferred package manager. Include optional packages if you plan to enable Session Replay or Auto Capture. {% tabs %} {% tab title="npm" %} ```bash npm install @statsig/js-client @statsig/session-replay @statsig/web-analytics ``` {% /tab %} {% tab title="yarn" %} ```bash yarn add @statsig/js-client @statsig/session-replay @statsig/web-analytics ``` {% /tab %} {% /tabs %} {% callout type="info" %} If you don't need Session Replay or Auto Capture, omit the `@statsig/session-replay` and `@statsig/web-analytics` packages. {% /callout %} After installation, configure the SDK in your app entry point before rendering your UI. {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later in a gate or experiment. ```tsx import { StatsigClient } from '@statsig/js-client'; const client = new StatsigClient( 'client-xyz', { userID: 'a-user' }, { environment: { tier: 'development' }, }, ); await client.initializeAsync(); ``` Use `initializeAsync` when you need to await the latest values. For a non-blocking approach, you can call `initializeAsync()` without awaiting and rely on cached values until the promise resolves. {% /step %} {% /steps %} ## Use the SDK ### Checking a Feature Flag/Gate Now that your SDK is initialized, check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (think `return false;`) by default. ```tsx if (client.checkGate('new_homepage_design')) { // Gate is on, show new experience } else { // Gate is off, render the default experience } ``` ### Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional advanced user targeting. To send different values (strings, numbers, and similar types) to your clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates but returns a full JSON object you can configure on the server and fetch typed parameters from. For example: ```tsx const config = client.getDynamicConfig('awesome_product_details'); const itemName = config.get('product_name', 'Some Fallback'); const price = config.value.price ?? 10.0; if (config.value.is_discount_enabled === true) { // apply discount logic } ``` ### Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Statsig offers two APIs, but recommends [layers](/experiments/layers-overview) to enable quicker iterations with parameter reuse. ```tsx // Reading values via getLayer const layer = client.getLayer('user_promo_experiments'); const promoTitle = layer.get('title', 'Welcome to Statsig!'); const discount = layer.get('discount', 0.1); ``` ```tsx // Reading values via getExperiment const titleExperiment = client.getExperiment('new_user_promo_title'); const priceExperiment = client.getExperiment('new_user_promo_price'); const experimentTitle = titleExperiment.value.title ?? 'Welcome to Statsig!'; const experimentDiscount = priceExperiment.value.discount ?? 0.1; ``` ### Logging an Event After you set up a Feature Gate or an Experiment, you can track custom events to measure how your new features or experiment groups affect those events. Call the Log Event API for the event, and optionally provide a value and/or a metadata object to log together with the event: ```tsx client.logEvent('my_simple_event'); client.logEvent({ eventName: 'add_to_cart', value: 'SKU_12345', metadata: { price: '9.99', item_name: 'diet_coke_48_pack', }, }); ``` ### Flushing Logged Events `flush()` sends queued events immediately. Use `shutdown()` when your app is exiting. ```tsx await client.flush(); ``` ### Typed Getters `Layer`, `Experiment`, and `DynamicConfig` objects support a typed `get` method. Using a fallback that matches the expected type helps avoid returning unintended values. ```tsx // config value: { "my_value": 1 } const dynamicConfig = client.getDynamicConfig('a_config'); const fallbackString = dynamicConfig.get('my_value', 'fallback'); // returns 'fallback' const fallbackNumber = dynamicConfig.get('my_value', 0); // returns 1 const rawValue = dynamicConfig.get('my_value'); // returns 1 ``` Passing a fallback of the wrong type returns that fallback. When type safety is not needed, omit the fallback to receive the raw value. ### Evaluation Details Each gate, config, experiment, and layer exposes `details` describing how the value was resolved. * `reason` explains the source (e.g., `Network:Recognized`, `Cache:Unrecognized`). * `lcut` is the last time any configuration changed in your project. * `receivedAt` marks when this response was received, useful for judging cache staleness. ```tsx const gate = client.getFeatureGate('a_gate'); console.log(gate.details); // { reason: 'Cache:Recognized', lcut: 1713837126636, receivedAt: 1713838137598 } const config = client.getDynamicConfig('a_config'); console.log(config.details); // { reason: 'Cache:Unrecognized', lcut: 1713837126636, receivedAt: 1713838137598 } ``` Go to [`/sdk/debugging`](/sdks/debugging) for the full list of `reason` values. ### Sample Projects Explore end-to-end examples in the [`js-client-monorepo` samples folder](https://github.com/statsig-io/js-client-monorepo/tree/main/samples) for React, Next.js, precomputed clients, and more. ## Parameter Stores Parameter Stores hold a set of parameters for your mobile app. These parameters can be remapped dynamically from a static value to a Statsig entity (Feature Gates, Experiments, and Layers), so you can decouple your code from the configuration in Statsig. Go to [Parameter Stores](/client/concepts/parameter-stores) to learn more. ```tsx const homepageStore = client.getParameterStore('homepage'); const title = homepageStore.get('title', 'Welcome'); const showUpsell = homepageStore.get('upsell_upgrade_now', false); ``` ## Statsig User Provide a StatsigUser object to check or get your configurations. Pass as much information as possible to take advantage of advanced gate and config conditions. The `userID` field is usually required to provide a consistent experience for a given user. (Refer to [logged-out experiments](/guides/first-device-level-experiment) to understand how to correctly run experiments for logged-out users.) Besides `userID`, the `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion` fields are also available as top-level fields on StatsigUser. You can also pass any key-value pairs in an object/dictionary to the `custom` field to create targeting based on them. After the user logs in or their attributes change, call `updateUser` with the updated `userID` and/or any other updated user attributes. ### Updating Users Call `updateUserAsync` when the signed-in user changes to fetch fresh values for that identity. ```tsx const user = { userID: 'a-user' }; await client.updateUserAsync(user); ``` For advanced flows such as bootstrapping or prefetching users, go to [Using EvaluationsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter). ### Prefetching Users Use `prefetchData` to prepare values for another user so you can switch synchronously later. ```tsx const nextUser = { userID: 'my-other-user' }; await client.dataAdapter.prefetchData(nextUser); // Optionally handle failures without blocking the UI client.dataAdapter.prefetchData(nextUser).catch((err) => { console.warn('Failed to prefetch', err); }); client.updateUserSync(nextUser); const gate = client.getFeatureGate('a_gate'); console.log(gate.value, gate.details.reason); // true, 'Prefetch:Recognized' ``` ## Statsig Options {% parameter name="loggingEnabled" type="LoggingEnabledOption" %} Controls logging behavior. * `browser-only` (default): log events from browser environments. * `disabled`: never send events. * `always`: log in every environment, including non-browser contexts. {% /parameter %} {% parameter name="disableLogging" type="boolean" %} Use `loggingEnabled: 'disabled'` instead. {% /parameter %} {% parameter name="disableStableID" type="boolean" %} Skip generating a device-level Stable ID. {% /parameter %} {% parameter name="disableEvaluationMemoization" type="boolean" %} Recompute every evaluation instead of using the memoized result. {% /parameter %} {% parameter name="initialSessionID" type="string" %} Override the generated session ID. {% /parameter %} {% parameter name="enableCookies" type="boolean" %} Persist Stable ID in cookies for cross-domain tracking. {% /parameter %} {% parameter name="disableStorage" type="boolean" %} Prevent any local storage writes (disables caching). {% /parameter %} {% parameter name="networkConfig" type="NetworkConfig" %} Override network endpoints per request type. {% /parameter %} {% accordion title="Network Config Options" %} {% parameter name="api" type="string" %} Base URL for all requests. The SDK appends endpoint paths like `/initialize` and `/rgstr`; append `/v1` when your proxy expects it. {% /parameter %} {% parameter name="initializeUrl" type="string" %} Endpoint for initialization requests only. Takes precedence over `api` for `/initialize`. {% /parameter %} {% parameter name="initializeFallbackUrls" type="string[]" %} Fallback endpoints for initialization requests only. This doesn't create a generic fallback for `api`. {% /parameter %} {% parameter name="logEventUrl" type="string" %} Endpoint for event uploads. {% /parameter %} {% parameter name="logEventFallbackUrls" type="string[]" %} Fallback endpoints for event uploads only. This doesn't create a generic fallback for `api`. {% /parameter %} {% parameter name="networkTimeoutMs" type="number" %} Request timeout in milliseconds. {% /parameter %} {% parameter name="preventAllNetworkTraffic" type="boolean" %} Disable all outbound requests; combine with `loggingEnabled: 'disabled'` to silence log warnings. {% /parameter %} {% parameter name="networkOverrideFunc" type="function" %} Provide custom transport (e.g., Axios). {% /parameter %} {% /accordion %} {% parameter name="environment" type="StatsigEnvironment" %} Set environment-wide defaults (for example `{ tier: 'staging' }`). {% /parameter %} {% parameter name="logLevel" type="LogLevel" %} Console verbosity. {% /parameter %} {% parameter name="loggingBufferMaxSize" type="number" %} Max events per log batch. {% /parameter %} {% parameter name="loggingIntervalMs" type="number" %} Interval between automatic flushes. {% /parameter %} {% parameter name="overrideAdapter" type="OverrideAdapter" %} Modify evaluations before returning them. {% /parameter %} {% parameter name="includeCurrentPageUrlWithEvents" type="boolean" %} Attach the current page URL to logged events. {% /parameter %} {% parameter name="disableStatsigEncoding" type="boolean" %} Send requests without Statsig-specific encoding. {% /parameter %} {% parameter name="logEventCompressionMode" type="LogEventCompressionMode" %} Control compression for batched events. {% /parameter %} {% parameter name="disableCompression" type="boolean" %} Use `logEventCompressionMode` instead. {% /parameter %} {% parameter name="dataAdapter" type="EvaluationsDataAdapter" %} Provide a custom data adapter to control caching/fetching. {% /parameter %} {% parameter name="customUserCacheKeyFunc" type="CustomCacheKeyGenerator" %} Override cache key generation for stored evaluations. {% /parameter %} ## Manual Exposures {% callout type="warning" %} Manual logging is error-prone and can often introduce issues like uneven exposures, which compromise experiment results. {% /callout %} You can query your gates/experiments without triggering an exposure, and manually log the exposures later: {% tabs %} {% tab title="Feature Gates" %} ```tsx const result = client.checkGate('a_gate_name', { disableExposureLog: true }); // ... client.checkGate('a_gate_name'); // later, when ready to log the exposure ``` {% /tab %} {% tab title="Dynamic Configs" %} ```tsx const config = client.getConfig('a_dynamic_config_name', { disableExposureLog: true }); client.getConfig('a_dynamic_config_name'); ``` {% /tab %} {% tab title="Experiments" %} ```tsx const experiment = client.getExperiment('an_experiment_name', { disableExposureLog: true }); client.getExperiment('an_experiment_name'); ``` {% /tab %} {% tab title="Layers" %} ```tsx const layer = client.getLayer('a_layer_name', { disableExposureLog: true }); const value = layer.get('param_name', 'fallback'); // When ready to log const exposure = client.getLayer('a_layer_name'); exposure.get('param_name', 'fallback'); ``` {% /tab %} {% /tabs %} ## Session Replay Install `@statsig/session-replay` and register the plugin to record user sessions. ```tsx import { StatsigProvider } from '@statsig/react-bindings'; import { StatsigSessionReplayPlugin } from '@statsig/session-replay'; <StatsigProvider sdkKey="client-xyz" user={{ userID: 'a-user' }} loadingComponent={<div style={{ height: 100, width: 300, padding: 16 }}>Loading...</div>} options={{ plugins: [new StatsigSessionReplayPlugin()] }} > <App /> </StatsigProvider>; ``` ## Web Analytics / Auto Capture By including the [`@statsig/web-analytics`](https://www.npmjs.com/package/@statsig/web-analytics) package in your project, you can automatically capture common web events like clicks and page views. For more information on filtering events, enabling console log capture, and other configuration options available in web analytics, refer to the [Web Analytics Configuration](/webanalytics/overview#event-filtering-and-console-configuration) documentation. ```tsx import { StatsigProvider } from '@statsig/react-bindings'; import { StatsigAutoCapturePlugin } from '@statsig/web-analytics'; <StatsigProvider sdkKey="client-xyz" user={{ userID: 'a-user' }} loadingComponent={<div style={{ height: 100, width: 300, padding: 16 }}>Loading...</div>} options={{ plugins: [new StatsigAutoCapturePlugin()] }} > <App /> </StatsigProvider>; ``` ## Content Security Policy Add Statsig endpoints to your CSP `connect-src` directive when running the web SDK. ```js const cspConfig = { directives: { 'connect-src': [ 'api.statsig.com', 'featuregates.org', 'statsigapi.net', 'events.statsigapi.net', 'api.statsigcdn.com', 'featureassets.org', 'assetsconfigcdn.org', 'prodregistryv2.org', 'cloudflare-dns.com', 'beyondwickedmapping.org', ], }, }; ``` {% callout type="info" %} Statsig occasionally updates its network domains. Verify the latest list in [Statsig Domains](/infrastructure/statsig_domains). {% /callout %} ## Lifecycle & Advanced Usage ## Shutting Statsig Down To save users' data and battery usage and prevent logged events from being dropped, the SDK keeps event logs in client cache and flushes them periodically. Because of this, some events may not have been sent when your app shuts down. To ensure all logged events are flushed or saved locally, call shutdown when your app is closing. ```tsx await client.shutdown(); ``` ## Stable ID Stable ID provides a consistent device identifier. It lets you run [logged-out experiments](/guides/first-device-level-experiment) and target gates at the device level. ### How Stable ID Works * On first initialization the SDK generates a Stable ID and stores it in `localStorage` under `statsig.stable_id.<SDK_KEY_HASH>`. * Subsequent sessions reuse the stored value. Each client SDK key has its own Stable ID entry. * Local storage is scoped per domain, so cross-domain usage requires sharing the value manually (see below). ### Reading the Stable ID {% tabs %} {% tab title="JavaScript" %} ```tsx const context = client.getContext(); console.log('Statsig StableID:', context.stableID); ``` {% /tab %} {% tab title="React" %} ```tsx import { useStatsigClient } from '@statsig/react-bindings'; function MyComponent() { const { client } = useStatsigClient(); const context = client.getContext(); return <div>{context.stableID}</div>; } ``` {% /tab %} {% /tabs %} ### Overriding the Stable ID Provide a custom Stable ID through `StatsigUser.customIDs.stableID` if you already manage a durable device identifier. {% tabs %} {% tab title="JavaScript" %} ```tsx import { StatsigClient, StatsigUser } from '@statsig/js-client'; const userWithStableID: StatsigUser = { customIDs: { stableID: 'my-custom-stable-id', }, }; const client = new StatsigClient('client-xyz', userWithStableID); await client.updateUserAsync(userWithStableID); ``` {% /tab %} {% tab title="React" %} ```tsx import { StatsigProvider, useStatsigClient } from '@statsig/react-bindings'; function App() { return ( <StatsigProvider sdkKey="client-xyz" user={{ customIDs: { stableID: 'my-custom-stable-id' }, }} > <div>Your App</div> </StatsigProvider> ); } function MyComponent() { const { client } = useStatsigClient(); useEffect(() => { client.updateUserAsync({ customIDs: { stableID: 'my-custom-stable-id' }, }); }, [client]); } ``` {% /tab %} {% /tabs %} {% callout type="note" %} When you override the Stable ID, Statsig persists it to local storage, so subsequent sessions reuse your custom value. {% /callout %} ### Sharing Stable ID Across Subdomains Add this helper script before initializing the SDK and then copy the stored value onto your user object. ```html <!-- cross domain id script --> <script>!function(){let t="STATSIG_LOCAL_STORAGE_STABLE_ID";function e(){if(crypto&&crypto.randomUUID)return crypto.randomUUID();let t=()=>Math.floor(65536*Math.random()).toString(16).padStart(4,"0");return`$\{t()\}${t()}-$\{t()\}-4${t().substring(1)}-$\{t()\}-${t()}$\{t()\}${t()}`}let i=null,n=localStorage.getItem(t)||null;if(document.cookie.match(/statsiguuid=([\w-]+);?/)&&([,i]=document.cookie.match(/statsiguuid=([\w-]+);?/)),i&&n&&i===n);else if(i&&n&&i!==n)localStorage.setItem(t,i);else if(i&&!n)localStorage.setItem(t,i);else{let o=e();localStorage.setItem(t,o),function t(i){let n=new Date;n.setMonth(n.getMonth()+12);let o=window.location.host.split(".");o.length>2&&o.shift();let s=`.$\{o.join(".")\}`;document.cookie=`statsiguuid=${i||e()};Expires=$\{n\};Domain=${s};Path=/`}(o)}}();</script> <!-- Manually attach stableID to user object --> <script> const userObj = {}; if (localStorage.getItem('STATSIG_LOCAL_STORAGE_STABLE_ID')) { userObj.customIDs = { stableID: localStorage.getItem('STATSIG_LOCAL_STORAGE_STABLE_ID'), }; } const client = new Statsig.StatsigClient('<client-sdk-key>', userObj); </script> ``` _Use this script at your discretion and test thoroughly._ ### Aligning Stable ID Between Client and Server To share Stable ID with a backend Statsig SDK, send the value with requests and persist it server-side when missing. The server can bootstrap the client with the same Stable ID. ```tsx // Server: ensure Stable ID exists, then return initialize response for the client const values = Statsig.getClientInitializeResponse(user, YOUR_CLIENT_KEY, { hash: 'djb2', }); // Client: apply the server-provided values and initialize synchronously const { values, user: verifiedUser } = await fetch('/init-statsig-client', { method: 'POST', body: loadUserData(), }).then((res) => res.json()); const myClient = new StatsigClient(YOUR_CLIENT_KEY, verifiedUser); myClient.dataAdapter.setData(values); myClient.initializeSync(); ``` ## Using multiple instances of the SDK The examples above use the SDK's singleton. Statsig also supports creating multiple instances of the SDK. The `Statsig` singleton wraps a single instance of the SDK (typically called a `StatsigClient`) that you can instantiate directly. {% callout type="note" %} Use a different SDK key for each SDK instance. Various functionality of the Statsig client is keyed on the SDK key being used. Using the same key causes collisions. {% /callout %} All top-level static methods from the singleton carry over as instance methods. To create an instance of the Statsig SDK: ```tsx import { StatsigClient } from '@statsig/js-client'; const mainClient = new StatsigClient('client-xyz', { userID: 'a-user' }); const secondaryClient = new StatsigClient('client-abc', { userID: 'another-user' }); await Promise.all([ mainClient.initializeAsync(), secondaryClient.initializeAsync(), ]); if (mainClient.checkGate('a_gate')) { // ... } if (secondaryClient.checkGate('some_other_gate')) { // ... } ``` ## Override Adapter Use the `LocalOverrideAdapter` to define local overrides for gates, configs, experiments, or layers. ```tsx import { LocalOverrideAdapter } from '@statsig/js-local-overrides'; import { StatsigClient, LogLevel } from '@statsig/js-client'; const overrideAdapter = new LocalOverrideAdapter(); overrideAdapter.overrideGate('gate_a', false); overrideAdapter.overrideGate('gate_b', true); const client = new StatsigClient('client-xyz', { userID: 'a-user' }, { logLevel: LogLevel.Debug, overrideAdapter, }); ``` ### Persisting Overrides Pass your client SDK key to the adapter to persist overrides between sessions when using multi-instance setups. ```tsx const overrideAdapter = new LocalOverrideAdapter('client-xyz'); ``` ## Using Persistent Evaluations Persist experiment assignments so users keep the same variant even if targeting rules change. ```tsx import { StatsigClient } from '@statsig/js-client'; import { UserPersistentOverrideAdapter } from '@statsig/js-user-persisted-storage'; class LocalStorageUserPersistedStorage { load(key: string) { return JSON.parse(localStorage.getItem(key) ?? '{}'); } save(key: string, experiment: string, data: string) { const values = JSON.parse(localStorage.getItem(key) ?? '{}'); values[experiment] = JSON.parse(data); localStorage.setItem(key, JSON.stringify(values)); } delete(key: string, experiment: string) { const data = JSON.parse(localStorage.getItem(key) ?? '{}'); delete data[experiment]; localStorage.setItem(key, JSON.stringify(data)); } } const storage = new LocalStorageUserPersistedStorage(); const adapter = new UserPersistentOverrideAdapter(storage); const client = new StatsigClient('client-xyz', { overrideAdapter: adapter }); await client.initializeAsync({ userID: '123' }); const userPersistedValues = adapter.loadUserPersistedValues({ userID: '123' }, 'userID'); const experiment = client.getExperiment('active_experiment', { userPersistedValues }); ``` See [Client Persistent Assignment](/client/concepts/persistent_assignment) for additional patterns and storage options. ## Common Targeting Use Cases Capture cookies or URL parameters and pass them through `StatsigUser.custom` for targeting rules. ```tsx const user = { custom: { isLoggedIn: cookieLib.get('isLoggedIn'), utm: new URL(window.location.href).searchParams.get('utm'), }, }; const client = new StatsigClient('client-xyz', user, options); ``` {% figure %} ![Targeting in Console](/images/client/js-common-targeting.png) {% /figure %} ## Async Timeouts Limit how long `initializeAsync` and `updateUserAsync` wait for network responses before falling back to cached values. ```tsx await client.initializeAsync({ timeoutMs: 1000 }); await client.updateUserAsync( { userID: 'a-user' }, { timeoutMs: 1000 }, ); ``` ### Data Adapter `StatsigClient` uses an `EvaluationsDataAdapter` to manage caching and network fetches. The default implementation (`StatsigEvaluationsDataAdapter`) reads from local storage synchronously and refreshes values from Statsig asynchronously. Go to [Using EvaluationsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter) for full examples, including bootstrapping, prefetching, and custom adapters. ### Partial User Matching Use `customUserCacheKeyFunc` with `updateUserSync` when you need to enrich a user locally without triggering a full network refresh. ```tsx const originalUser = { customIDs: { analyticsID: 'analytics-123', }, }; const customKey = (sdkKey: string, user: StatsigUser) => { const analyticsID = user.customIDs?.analyticsID ?? 'anonymous'; return `sdkKey:$\{sdkKey\}:analyticsID:${analyticsID}`; }; const client = new StatsigClient('client-xyz', originalUser, { customUserCacheKeyFunc: customKey, }); await client.initializeAsync(); someAsyncFunction().then((newData) => { const enrichedUser = { ...originalUser, userID: newData.userID, email: newData.email, }; client.updateUserSync(enrichedUser); }); ``` {% callout type="warning" %} Custom cache keys can produce stale or incorrect evaluations if multiple users map to the same key. Await `updateUserAsync` when you need guaranteed fresh values per user. {% /callout %} ### Client Event Emitter Subscribe to Statsig client lifecycle events to respond to initialization, logging, or evaluation changes. ```tsx import type { AnyStatsigClientEvent, StatsigClientEvent, } from '@statsig/client-core'; const onAnyEvent = (event: AnyStatsigClientEvent) => { console.log('Statsig event', event); }; const onLogsFlushed = (event: StatsigClientEvent<'logs_flushed'>) => { console.log('Logs', event.events); }; client.on('logs_flushed', onLogsFlushed); client.on('*', onAnyEvent); client.off('logs_flushed', onLogsFlushed); client.off('*', onAnyEvent); ``` | Event | Payload | Description | | --- | --- | --- | | `values_updated` | `{ status, values }` | Fired when initialize/update refreshes cached values. | | `session_expired` | `{}` | Fired when the current session expires. | | `error` | `{ error, tag }` | Unexpected client errors. | | `pre_logs_flushed` | `{ events }` | Before a batch of events is sent. | | `logs_flushed` | `{ events }` | After events are sent. | | `pre_shutdown` | `{}` | Before the SDK shuts down. | | `initialization_failure` | `{}` | Initialization failed. | | `gate_evaluation` | `{ gate }` | When a gate is evaluated. | | `dynamic_config_evaluation` | `{ dynamicConfig }` | When a config is evaluated. | | `experiment_evaluation` | `{ experiment }` | When an experiment is evaluated. | | `layer_evaluation` | `{ layer }` | When a layer is evaluated. | | `log_event_called` | `{ event }` | When `logEvent` is called. | ## Quality & Troubleshooting ## Testing Mock Statsig APIs in Jest to isolate business logic. ```tsx import { StatsigClient } from '@statsig/js-client'; export async function transform(input: string): Promise<string> { const client = new StatsigClient('client-xyz', { userID: 'a-user' }, { networkConfig: { preventAllNetworkTraffic: typeof process !== 'undefined' && process.env['NODE_ENV'] === 'test', }, }); await client.initializeAsync(); if (client.checkGate('a_gate')) { input = 'transformed'; } const experiment = client.getExperiment('an_experiment'); input += '-' + experiment.get('my_param', 'fallback'); await client.shutdown(); return input; } ``` ```tsx import { StatsigClient } from '@statsig/js-client'; jest.mock('@statsig/js-client'); test('string transformations', async () => { jest .spyOn(StatsigClient.prototype, 'checkGate') .mockImplementation(() => true); jest .spyOn(StatsigClient.prototype, 'getExperiment') .mockImplementation(() => ({ get: () => 'my-value' } as any)); const result = await transform('original'); expect(result).toBe('transformed-my-value'); }); ``` ## Debugging When results look unexpected, use these tools to inspect what the SDK is doing. ### Enable Verbose Logging ```ts import { LogLevel, StatsigClient } from '@statsig/js-client'; const client = new StatsigClient('client-xyz', { userID: 'a-user' }, { logLevel: LogLevel.Debug, }); ``` ### Inspect the `__STATSIG__` Global Open your browser console and run `__STATSIG__` to inspect the current client instance. Useful properties include `_logger._queue` for pending events. {% figure %} ![Statsig Global](/images/client/statsig-global.png) {% /figure %} ### Review Network Traffic Filter network requests by `client-` to see initialization and logging calls. {% figure %} ![Network Logs](/images/client/network-logs.png) {% /figure %} ### Check Evaluation Reasons ```ts const gate = client.getFeatureGate('a_gate'); console.log(gate.details.reason); ``` Common reasons: * `Network` | `NetworkNotModified`: latest values from the API. * `Cache`: loaded from local storage. * `NoValues`: no cached values and network failed. * `Bootstrap`: values provided through `dataAdapter.setData`. * `Prefetch`: values from `dataAdapter.prefetchData`. Go to [`/sdk/debugging`](/sdks/debugging#reasons) for full details. ## FAQs #### Does the SDK use local storage or cookies? Statsig's web SDK doesn't set cookies. It stores gate/config values and unsent events in `localStorage` so features keep working when offline. #### Can I access the SDK instance globally? ```tsx window.Statsig.instance().logEvent('test_event'); ``` ```ts import { StatsigClient } from '@statsig/js-client'; StatsigClient.instance().logEvent('test_event'); ``` {% callout type="info" %} With multiple instances, pass the SDK key: `Statsig.instance('client-YOUR_KEY')`. {% /callout %} #### How do I handle consent or GDPR flows? Start with logging disabled and storage blocked, then enable them after consent. ```tsx const client = new StatsigClient('client-xyz', {}, { loggingEnabled: 'disabled', disableStorage: true, }); await client.initializeAsync(); client.updateRuntimeOptions({ loggingEnabled: 'browser-only', disableStorage: false, }); ``` The SDK buffers up to 500 events in memory and flushes them once logging is re-enabled. ## Additional Resources * [Client Persistent Assignment](/client/concepts/persistent_assignment) * [Using EvaluationsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter) * [Debugging SDK Evaluations](/sdks/debugging) ================================================================================ # HTML Snippet URL: https://docs.statsig.com/client/html-snippet Updated: 2026-07-11 ================================================================================ ## Set Up the SDK {% steps %} {% step title="Install the SDK" %} Install the Statsig SDK by adding a script tag to the head of your HTML file: ```js <script src="https://cdn.jsdelivr.net/npm/@statsig/js-client@3/build/statsig-js-client+session-replay+web-analytics.min.js?apikey=YOUR_CLIENT_API_KEY" crossorigin="anonymous" > </script> ``` Replace `YOUR_CLIENT_API_KEY` with your actual client API key from [Project Settings > API Keys](https://console.statsig.com/api_keys). {% /step %} {% step title="Initialize the SDK" %} The HTML snippet wraps an instance of the Statsig JavaScript SDK. Providing your client API key in the URL auto-initializes the SDK, as shown in the installation step. Move to manual initialization if you need any of the following: * custom user properties * custom initialization options * check gates, configs, experiments, or log events To manually initialize an instance of the SDK, remove the key parameter from the script tag and do the following: ```js const { StatsigClient, runStatsigAutoCapture, runStatsigSessionReplay } = window.Statsig; const client = new StatsigClient( '<CLIENT-SDK-KEY>', { userID: 'a-user' } ); runStatsigSessionReplay(client); runStatsigAutoCapture(client); await client.initializeAsync(); // check gates, configs, experiments, or log events ``` The `StatsigClient` instance you create is also available via `window.Statsig`, so you can reference it globally. Your `StatsigClient` instance provides access to all methods in the [JavaScript SDK](/client/javascript-sdk). Go to that documentation for initialization details and core methods. {% /step %} {% /steps %} #### Can I customize the initialization logic? Yes. Remove the client API key from the URL. The HTML snippet is the JavaScript SDK. Providing an API key in the URL auto-initializes an instance. To skip auto-initialization, omit the key and create your own instance. Go to the [JavaScript SDK Getting Started Guide](/client/javascript-sdk#getting-started) for details. Creating an instance using the HTML snippet differs from installing through npm: ```js const { StatsigClient, runStatsigAutoCapture, runStatsigSessionReplay } = window.Statsig; const client = new StatsigClient( '<CLIENT-SDK-KEY>', { userID: 'a-user' } ); runStatsigSessionReplay(client); runStatsigAutoCapture(client); await client.initializeAsync(); // check gates, configs, experiments, or log events ``` ================================================================================ # React Client SDK URL: https://docs.statsig.com/client/React Updated: 2026-07-11 ================================================================================ ## Set Up the SDK {% steps %} {% step title="Install the SDK" %} {% callout type="info" %} If you need a starter project, follow the official [React quickstart](https://react.dev/learn/build-a-react-app-from-scratch). Looking for Next.js instead? Go to the [Next.js SDK](/client/Next) docs. {% /callout %} ### AI-powered Setup Setup Statsig in 90 seconds by copying this AI prompt into your IDE: ```text expandable You are a frontend engineer integrating the Statsig SDK into a React app. Follow these instructions carefully: 1. Install the required Statsig packages: npm install @statsig/react-bindings @statsig/session-replay @statsig/web-analytics 2. In the main component file (`App.jsx` or `App.tsx`): - Import `StatsigProvider` and `useClientAsyncInit` from `@statsig/react-bindings` - Import `StatsigAutoCapturePlugin` from `@statsig/web-analytics` and `StatsigSessionReplayPlugin` from `@statsig/session-replay` - Initialize the SDK using your client key: 'YOUR-CLIENT-API-KEY' - Use `userID` from an existing variable if it's already declared in the file; otherwise, default to `'a-user'` - Wrap the existing app content inside `<StatsigProvider>`, using `<div>Loading...</div>` as the `loadingComponent` 3. DO NOT remove any existing JSX content from the component. Just wrap it. 4. Here is what the final file structure should look like: import { StatsigProvider, useClientAsyncInit } from '@statsig/react-bindings'; import { StatsigAutoCapturePlugin } from '@statsig/web-analytics'; import { StatsigSessionReplayPlugin } from '@statsig/session-replay'; import YourApp from './YourApp'; function App() { const id = typeof userID !== 'undefined' ? userID : 'a-user'; const { client } = useClientAsyncInit( 'YOUR-CLIENT-API-KEY', { userID: id }, { plugins: [new StatsigAutoCapturePlugin(), new StatsigSessionReplayPlugin()] } ); return ( <StatsigProvider client={client} loadingComponent={<div>Loading...</div>}> <YourApp /> </StatsigProvider> ); } 5. Ask the user to provide their CLIENT-API-KEY and insert it where prompted above. ``` ### Install Packages {% tabs %} {% tab name="npm" %} ```bash npm install @statsig/react-bindings ``` {% /tab %} {% tab name="yarn" %} ```bash yarn add @statsig/react-bindings ``` {% /tab %} {% /tabs %} {% callout type="tip" %} Add `@statsig/session-replay` and `@statsig/web-analytics` if you plan to enable Session Replay or Auto Capture. {% /callout %} {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later on in a gate or experiment. ### Wrap Your App With `StatsigProvider` Provide your client SDK key and initial user when you render the provider. ```tsx import { StatsigProvider } from '@statsig/react-bindings'; function App() { return ( <StatsigProvider sdkKey="client-KEY" user={{ userID: '1234', email: 'example@statsig.com' }}> <div>Hello world</div> </StatsigProvider> ); } ``` ### Typical Project Structure Most projects render a root component inside the provider. ```tsx // App.tsx import RootPage from './RootPage'; import { StatsigProvider } from '@statsig/react-bindings'; export default function App() { return ( <StatsigProvider sdkKey="client-KEY" user={{ userID: '1234' }}> <RootPage /> </StatsigProvider> ); } ``` ```tsx // RootPage.tsx export default function RootPage() { return <div>Hello World</div>; } ``` {% callout type="info" %} Need to balance startup speed with freshness? Review [Initialization Strategies](/client/concepts/initialize) for bootstrap and async options. {% /callout %} {% /step %} {% /steps %} ## Use the SDK Use `useStatsigClient` inside components to retrieve the client when you need to evaluate something. ```tsx import { useStatsigClient } from '@statsig/react-bindings'; const { client } = useStatsigClient(); ``` ### Checking a Feature Flag/Gate Now that your SDK is initialized, let's check a [**Feature Gate**](/feature-flags/overview). Feature Gates can be used to create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (think `return false;`) by default. ```tsx import { useFeatureGate, useGateValue, useStatsigClient, } from '@statsig/react-bindings'; const { checkGate } = useStatsigClient(); const gateValue = useGateValue('my_gate'); const gate = useFeatureGate('my_gate'); return ( <div> {checkGate('my_gate') && <p>Passing</p>} {gateValue && <p>Passing</p>} {gate.value && <p>Passing ({gate.details.reason})</p>} </div> ); ``` ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional advanced user targeting. To send a different set of values (strings, numbers, etc.) to clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates, but you get a complete JSON object you can configure on the server and fetch typed parameters from it. For example: ```tsx import { useDynamicConfig, useStatsigClient } from '@statsig/react-bindings'; const config = useDynamicConfig('my_dynamic_config'); const { getDynamicConfig } = useStatsigClient(); return ( <div> <p>Reason: {config.details.reason}</p> <p>Value: {config.get('a_value', 'fallback_value')}</p> <p>Another Value: {getDynamicConfig('my_dynamic_config').get('a_bool', false)}</p> </div> ); ``` ### Getting a Layer/Experiment **Layers/Experiments** let you run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for quicker iterations with parameter reuse. ```tsx import { useExperiment, useStatsigClient } from '@statsig/react-bindings'; const experiment = useExperiment('my_experiment'); const { getExperiment } = useStatsigClient(); return ( <div> <p>Group: {getExperiment('my_experiment').groupName}</p> <p>Value: {experiment.get('a_value', 'fallback_value')}</p> </div> ); ``` ```tsx import { useLayer, useStatsigClient } from '@statsig/react-bindings'; const layer = useLayer('my_layer'); const { getLayer } = useStatsigClient(); return ( <div> <p>Group: {getLayer('my_layer').groupName}</p> <p>Value: {layer.get('a_value', 'fallback_value')}</p> </div> ); ``` ### Logging an Event After setting up a Feature Gate or Experiment, you may want to track custom events to see how new features or experiment groups affect those events. Call the Log Event API for the event. You can also provide a value and metadata object to be logged with the event: ```tsx import { useStatsigClient } from '@statsig/react-bindings'; const { logEvent } = useStatsigClient(); return <button onClick={() => logEvent('my_event')}>Click Me</button>; ``` ### Flushing Logged Events `flush()` sends queued events immediately. Use `shutdown()` when your app is exiting. ```tsx import { useStatsigClient } from '@statsig/react-bindings'; const { client } = useStatsigClient(); return ( <button onClick={async () => { await client.flush(); }} > Flush Events </button> ); ``` ## Parameter Stores Parameter Stores hold a set of parameters for your app. These parameters can be remapped dynamically from a static value to a Statsig entity (Feature Gates, Experiments, and Layers), so you can decouple your code from the configuration in Statsig. Refer to [Parameter Stores](/client/concepts/parameter-stores) for details. ## Manage Users ### Updating User Properties Call `updateUserAsync` when a user logs in or when you collect richer attributes. ```tsx import { useGateValue, useStatsigUser } from '@statsig/react-bindings'; export default function AccountBanner() { const gateValue = useGateValue('check_user'); const { updateUserAsync } = useStatsigUser(); return ( <div> <div>Gate is {gateValue ? 'passing' : 'failing'}.</div> <button onClick={() => updateUserAsync({ userID: '2' })}>Login</button> </div> ); } ``` ## Loading State To wait for the latest values during initialization, use either the provider or the async hook. {% tabs %} {% tab name="StatsigProvider" %} ```tsx import { StatsigProvider } from '@statsig/react-bindings'; export function App() { return ( <StatsigProvider sdkKey="client-KEY" user={{ userID: 'a-user' }} loadingComponent={<div>Loading...</div>} > <YourComponent /> </StatsigProvider> ); } ``` {% /tab %} {% tab name="useClientAsyncInit" %} ```tsx import { StatsigProvider, useClientAsyncInit } from '@statsig/react-bindings'; export function App() { const { client, isLoading } = useClientAsyncInit( 'client-KEY', { userID: 'a-user' }, ); if (isLoading) { return <div>Loading...</div>; } return ( <StatsigProvider client={client}> <YourComponent /> </StatsigProvider> ); } ``` {% /tab %} {% /tabs %} ## React Hooks {% callout type="warning" %} Hooks that read gates, configs, experiments, or layers will log exposures on render. Use `useStatsigClient` to defer checks until you actually change the UI. {% /callout %} ### Feature Gate Hooks * Recommended: `useStatsigClient().checkGate` logs when invoked. * `useGateValue` returns the boolean value and logs immediately. * `useFeatureGate` returns the full gate object with details. ```tsx import { useFeatureGate, useGateValue, useStatsigClient, } from '@statsig/react-bindings'; const { checkGate } = useStatsigClient(); const gateValue = useGateValue('my_gate'); const gate = useFeatureGate('my_gate'); return ( <div> {checkGate('my_gate') && <p>Passing</p>} {gateValue && <p>Passing</p>} {gate.value && <p>Passing ({gate.details.reason})</p>} </div> ); ``` ### Dynamic Config Hooks * Recommended: `useStatsigClient().getDynamicConfig` defers exposure until called. * `useDynamicConfig` logs on render. ```tsx import { useDynamicConfig, useStatsigClient } from '@statsig/react-bindings'; const config = useDynamicConfig('my_dynamic_config'); const { getDynamicConfig } = useStatsigClient(); return ( <div> <p>Reason: {config.details.reason}</p> <p>Value: {config.get('a_value', 'fallback_value')}</p> <p>Another Value: {getDynamicConfig('my_dynamic_config').get('a_bool', false)}</p> </div> ); ``` ### Experiment Hooks * Recommended: `useStatsigClient().getExperiment` to control exposures. * `useExperiment` logs on render. ```tsx import { useExperiment, useStatsigClient } from '@statsig/react-bindings'; const experiment = useExperiment('my_experiment'); const { getExperiment } = useStatsigClient(); return ( <div> <p>Group: {getExperiment('my_experiment').groupName}</p> <p>Value: {experiment.get('a_value', 'fallback_value')}</p> </div> ); ``` ### Layer Hooks Layers only log exposures when you call `.get()`. ```tsx import { useLayer, useStatsigClient } from '@statsig/react-bindings'; const layer = useLayer('my_layer'); const { getLayer } = useStatsigClient(); return ( <div> <p>Group: {getLayer('my_layer').groupName}</p> <p>Value: {layer.get('a_value', 'fallback_value')}</p> </div> ); ``` ### Parameter Store Hooks ```tsx import { useParameterStore } from '@statsig/react-bindings'; function MyComponent() { const store = useParameterStore('my_parameter_store'); const title = store.get('page_title', 'Default Title'); const maxItems = store.get('max_items', 10); const isEnabled = store.get('feature_enabled', false); const storeNoExposure = useParameterStore('my_parameter_store', { disableExposureLog: true, }); return <div>{title}</div>; } ``` ### Log Events From Hooks ```tsx import { useStatsigClient } from '@statsig/react-bindings'; const { logEvent } = useStatsigClient(); return <button onClick={() => logEvent('my_event')}>Click Me</button>; ``` ### StatsigUser Hook ```tsx import { useStatsigUser } from '@statsig/react-bindings'; const { user, updateUserSync } = useStatsigUser(); return ( <div> <p>Current User: {user.userID}</p> <button onClick={() => updateUserSync({ userID: 'some-other-user' })}> Update User </button> </div> ); ``` ### Direct Access to the Client ```tsx import { useStatsigClient } from '@statsig/react-bindings'; const { client } = useStatsigClient(); console.log('stableID', client.getContext().stableID); ``` ### Client Initialization Hooks * `useClientAsyncInit`: fetches the latest values before rendering. * `useClientBootstrapInit`: bootstrap from server-provided values. {% callout type="info" %} You can also initialize your own client instance manually. Refer to [Initialization Strategies](/client/concepts/initialize) for alternatives. {% /callout %} ## Statsig Options {% parameter name="loggingEnabled" type="LoggingEnabledOption" default="browser-only" %} Controls logging behavior. * `browser-only` (default): log events from browser environments. * `disabled`: never send events. * `always`: log in every environment, including non-browser contexts. {% /parameter %} {% parameter name="disableLogging" type="boolean" post="["deprecated"]" %} Use `loggingEnabled: 'disabled'` instead. {% /parameter %} {% parameter name="disableStableID" type="boolean" default="false" %} Skip generating a device-level Stable ID. {% /parameter %} {% parameter name="disableEvaluationMemoization" type="boolean" default="false" %} Recompute every evaluation instead of using the memoized result. {% /parameter %} {% parameter name="initialSessionID" type="string" %} Override the generated session ID. {% /parameter %} {% parameter name="enableCookies" type="boolean" default="false" %} Persist Stable ID in cookies for cross-domain tracking. {% /parameter %} {% parameter name="disableStorage" type="boolean" %} Prevent any local storage writes (disables caching). {% /parameter %} {% parameter name="networkConfig" type="NetworkConfig" %} Override network endpoints per request type. {% /parameter %} {% accordion title="Network Config Options" %} {% parameter name="api" type="string" default="https://api.statsig.com" %} Base URL for all requests. The SDK appends endpoint paths like `/initialize` and `/rgstr`; append `/v1` when your proxy expects it. {% /parameter %} {% parameter name="initializeUrl" type="string" default="https://featureassets.org/v1/initialize" %} Endpoint for initialization requests only. Takes precedence over `api` for `/initialize`. {% /parameter %} {% parameter name="initializeFallbackUrls" type="string[]" %} Fallback endpoints for initialization requests only. This doesn't create a generic fallback for `api`. {% /parameter %} {% parameter name="logEventUrl" type="string" default="https://prodregistryv2.org/v1/rgstr" %} Endpoint for event uploads. {% /parameter %} {% parameter name="logEventFallbackUrls" type="string[]" %} Fallback endpoints for event uploads only. This doesn't create a generic fallback for `api`. {% /parameter %} {% parameter name="networkTimeoutMs" type="number" default="10000" %} Request timeout in milliseconds. {% /parameter %} {% parameter name="preventAllNetworkTraffic" type="boolean" %} Disable all outbound requests; combine with `loggingEnabled: 'disabled'` to silence log warnings. {% /parameter %} {% parameter name="networkOverrideFunc" type="function" %} Provide custom transport (e.g., Axios). {% /parameter %} {% /accordion %} {% parameter name="environment" type="StatsigEnvironment" %} Set environment-wide defaults (for example `{ tier: 'staging' }`). {% /parameter %} {% parameter name="logLevel" type="LogLevel" default="Warn" %} Console verbosity. {% /parameter %} {% parameter name="loggingBufferMaxSize" type="number" default="50" %} Max events per log batch. {% /parameter %} {% parameter name="loggingIntervalMs" type="number" default="10_000" %} Interval between automatic flushes. {% /parameter %} {% parameter name="overrideAdapter" type="OverrideAdapter" %} Modify evaluations before returning them. {% /parameter %} {% parameter name="includeCurrentPageUrlWithEvents" type="boolean" default="true" %} Attach the current page URL to logged events. {% /parameter %} {% parameter name="disableStatsigEncoding" type="boolean" default="false" %} Send requests without Statsig-specific encoding. {% /parameter %} {% parameter name="logEventCompressionMode" type="LogEventCompressionMode" default="Enabled" %} Control compression for batched events. {% /parameter %} {% parameter name="disableCompression" type="boolean" post="["deprecated"]" %} Use `logEventCompressionMode` instead. {% /parameter %} {% parameter name="dataAdapter" type="EvaluationsDataAdapter" %} Provide a custom data adapter to control caching/fetching. {% /parameter %} {% parameter name="customUserCacheKeyFunc" type="CustomCacheKeyGenerator" %} Override cache key generation for stored evaluations. {% /parameter %} ## Testing Mock Statsig hooks in Jest to isolate component logic. ```tsx import { StatsigProvider, useFeatureGate, useExperiment } from '@statsig/react-bindings'; function Content() { const gate = useFeatureGate('a_gate'); const experiment = useExperiment('an_experiment'); return ( <div> <div data-testid="gate_test">a_gate: {gate.value ? 'Pass' : 'Fail'}</div> <div data-testid="exp_test"> an_experiment: {experiment.get('my_param', 'fallback')} </div> </div> ); } function App() { return ( <StatsigProvider sdkKey={YOUR_CLIENT_KEY} user={{ userID: 'a-user' }} options={{ networkConfig: { // Optional – disable network requests in tests preventAllNetworkTraffic: typeof process !== 'undefined' && process.env['NODE_ENV'] === 'test', }, }} > <Content /> </StatsigProvider> ); } ``` ```tsx import { render, screen } from '@testing-library/react'; import * as ReactBindings from '@statsig/react-bindings'; jest.mock('@statsig/react-bindings', () => ({ ...jest.requireActual('@statsig/react-bindings'), useFeatureGate: () => ({ value: true }), useExperiment: () => ({ get: () => 'my_value' }), })); test('renders gate pass', async () => { render(<App />); const elem = await screen.findByTestId('gate_test'); expect(elem.textContent).toContain('Pass'); }); test('renders experiment value', async () => { render(<App />); const elem = await screen.findByTestId('exp_test'); expect(elem.textContent).toContain('my_value'); }); ``` ## Lifecycle & Advanced Usage ## Shutting Statsig Down The SDK keeps event logs in the client cache and flushes them periodically to save data and battery usage. Because of this, the SDK may not have flushed some events when your app shuts down. To ensure all logged events are flushed or saved locally, shut down Statsig when your app is closing: ```tsx import { useEffect } from 'react'; import { useStatsigClient } from '@statsig/react-bindings'; const { client } = useStatsigClient(); useEffect(() => { return () => { void client.shutdown(); }; }, [client]); ``` ## Session Replay Install `@statsig/session-replay` and register the plugin to record user sessions. ```tsx import { StatsigProvider, useClientAsyncInit } from '@statsig/react-bindings'; import { StatsigSessionReplayPlugin } from '@statsig/session-replay'; function App() { const { client } = useClientAsyncInit( 'client-KEY', { userID: 'a-user' }, { plugins: [new StatsigSessionReplayPlugin()] }, ); return ( <StatsigProvider client={client} loadingComponent={<div>Loading...</div>}> <div>Hello World</div> </StatsigProvider> ); } ``` ## Web Analytics / Auto Capture By including the [`@statsig/web-analytics`](https://www.npmjs.com/package/@statsig/web-analytics) package in your project, you can automatically capture common web events like clicks and page views. For more information on filtering events, enabling console log capture, and other configuration options available in web analytics, refer to the [Web Analytics Configuration](/webanalytics/overview#event-filtering-and-console-configuration) documentation. ```tsx import { StatsigProvider, useClientAsyncInit } from '@statsig/react-bindings'; import { StatsigAutoCapturePlugin } from '@statsig/web-analytics'; function App() { const { client } = useClientAsyncInit( 'client-KEY', { userID: 'a-user' }, { plugins: [new StatsigAutoCapturePlugin()] }, ); return ( <StatsigProvider client={client} loadingComponent={<div>Loading...</div>}> <div>Hello World</div> </StatsigProvider> ); } ``` ## Using Persistent Evaluations To keep experiment variants stable across rerenders or user transitions, use persistent storage. The React integration mirrors the [JavaScript workflow](/client/javascript-sdk#using-persistent-evaluations) and you can adapt the [Next.js sample](https://github.com/statsig-io/js-client-monorepo/tree/main/samples/next-js/src/app/persisted-user-storage-example) to your setup. Read more in [Client Persistent Assignment](/client/concepts/persistent_assignment). ## Additional Resources * [Initialization Concepts](/client/concepts/initialize) * [JavaScript Client SDK](/client/javascript-sdk) * [Persistent Assignment](/client/concepts/persistent_assignment) ================================================================================ # Next.js Client SDK URL: https://docs.statsig.com/client/Next Updated: 2026-07-11 ================================================================================ ## Set Up the SDK ### AI-powered Setup Setup Statsig in 90 seconds by copying this AI prompt into your IDE: ```text expandable Prompt # Statsig SDK Integration for Next.js You are a frontend engineer integrating the Statsig SDK into a **Next.js application**. Follow all steps below one by one: --- ## Full Integration Instructions 1. **Detect the package manager** by checking for: - `package-lock.json` → use `npm` - `yarn.lock` → use `yarn` - `pnpm-lock.yaml` → use `pnpm` 2. **Detect the Next.js router type** by checking for: - `app/` directory → **App Router** - `pages/` directory → **Pages Router** 3. **Install the Statsig package** using the correct package manager: # For npm npm install @statsig/react-bindings @statsig/session-replay @statsig/web-analytics # For yarn yarn add @statsig/react-bindings @statsig/session-replay @statsig/web-analytics # For pnpm pnpm add @statsig/react-bindings @statsig/session-replay @statsig/web-analytics 4. Add your Statsig client key to .env.local: NEXT_PUBLIC_STATSIG_CLIENT_KEY=ask the user for their CLIENT KEY and use that input 5. Integrate Statsig into the app (auto-detect router type): ### If the project uses the App Router (has an app/ directory): // Create app/my-statsig.tsx "use client"; import React from "react"; import { LogLevel, StatsigProvider } from "@statsig/react-bindings"; export default function MyStatsig({ children }: { children: React.ReactNode }) { const id = typeof userID !== "undefined" ? userID : "a-user"; const user = { userID: id, // Optional additional fields: // email: 'user@example.com', // customIDs: { internalID: 'internal-123' }, // custom: { plan: 'premium' } }; return ( <StatsigProvider sdkKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!} user={user} options={{ logLevel: LogLevel.Debug }} > {children} </StatsigProvider> ); } // Update app/layout.tsx to wrap children with MyStatsig import MyStatsig from "./my-statsig"; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode }>) { return ( <html lang="en"> <body> <MyStatsig> {children} {/* Preserve all existing layout content */} </MyStatsig> </body> </html> ); } ### If the project uses the Pages Router (has a pages/ directory): // Update pages/_app.tsx import type { AppProps } from "next/app"; import { LogLevel, StatsigProvider } from "@statsig/react-bindings"; export default function App({ Component, pageProps }: AppProps) { const id = typeof userID !== "undefined" ? userID : "a-user"; const user = { userID: id, // Optional additional fields: // email: 'user@example.com', // customIDs: { internalID: 'internal-123' }, // custom: { plan: 'premium' } }; return ( <StatsigProvider sdkKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!} user={user} options={{ logLevel: LogLevel.Debug }} > <Component {...pageProps} /> {/* Preserve all existing pages */} </StatsigProvider> ); } ### Final Notes - The system must **detect the router type** and **apply the correct integration automatically**. - **Do not remove or change any existing JSX or layout structure**: only wrap the app with `StatsigProvider`. - **Preserve the file's language**: if it's TypeScript (`.tsx`), keep it as TypeScript; if it's JavaScript (`.jsx`), keep it as JavaScript. - After these steps, Statsig will be integrated across the entire app, with **feature gates, configs, and experiments** available everywhere. ``` ### Manual Setup Statsig supports both [Page Router](https://nextjs.org/docs/pages) & [App Router](https://nextjs.org/docs/app), with some differences in integration patterns. {% tabs %} {% tab title="App Router" %} {% steps %} {% step title="Set environment variables" %} Add the keys to your .env.local file: ```bash .env.local # the NEXT_PUBLIC_ prefix is required for this to be available on the client side NEXT_PUBLIC_STATSIG_CLIENT_KEY=client-<REPLACE_WITH_YOUR_CLIENT_KEY> STATSIG_SERVER_KEY=secret-<REPLACE_WITH_YOUR_SERVER_KEY> ``` {% /step %} {% step title="Install packages" %} For App Router, install the @statsig/next package: {% codetabs %} ```bash NPM npm i @statsig/next ``` ```bash Yarn yarn add @statsig/next ``` ```bash PNPM pnpm add @statsig/next ``` {% /codetabs %} {% /step %} {% step title="Add the StatsigBootstrapProvider" %} The \<StatsigBootstrapProvider> creates both a Statsig Client and Server instance internally, and ["bootstraps"](/client/concepts/initialize#2-bootstrap-initialization) the client so it can render each page without a blocking network request. This keeps your app fast and is recommended for most users. If you need more control over your setup, refer to [Bootstrapping](#client-bootstrapping-recommended) and [React](/client/React) for more guidance. Add this component around the content in your root `layout.tsx` file: ```tsx app/layout.tsx import { StatsigBootstrapProvider } from "@statsig/next" export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { const user = { userID: "user-123", // add additional parameters as needed }; return ( <html lang="en"> <body> <StatsigBootstrapProvider user={user} clientKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY} serverKey={process.env.STATSIG_SERVER_KEY} > {children} </StatsigBootstrapProvider> </body> </html> ); } ``` {% /step %} {% /steps %} {% /tab %} {% tab title="Page Router" %} {% steps %} {% step title="Set environment variables" %} Add the keys to your .env.local file: ```bash .env.local # the NEXT_PUBLIC_ prefix is required for this to be available on the client side NEXT_PUBLIC_STATSIG_CLIENT_KEY=client-<REPLACE_WITH_YOUR_CLIENT_KEY> ``` {% /step %} {% step title="Install packages" %} Install the @statsig/react-bindings package: {% codetabs %} ```bash NPM npm i @statsig/react-bindings @statsig/web-analytics ``` ```bash Yarn yarn add @statsig/react-bindings @statsig/web-analytics ``` ```bash PNPM pnpm add @statsig/react-bindings @statsig/web-analytics ``` {% /codetabs %} {% /step %} {% step title="Add StatsigProvider to _app.tsx" %} To integrate Statsig into your Page Router app you can add the `StatsigProvider` to your `_app.tsx` file. There is a [full example](https://github.com/statsig-io/js-client-monorepo/tree/main/samples/next-js-pages-router-sample) in the samples directory of the javascript sdk. ```tsx pages/_app.tsx import type { AppProps } from "next/app"; import { LogLevel, StatsigProvider, } from "@statsig/react-bindings"; import { StatsigAutoCapturePlugin } from '@statsig/web-analytics'; export default function App({ Component, pageProps }: AppProps) { return ( <StatsigProvider sdkKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!} user={{ userID: "a-user" }} options={{ plugins: [ new StatsigAutoCapturePlugin() ], }}> <Component {...pageProps} /> </StatsigProvider> ); } ``` {% /step %} {% /steps %} {% /tab %} {% /tabs %} Refer to the [User (StatsigUser)](/sdks/user) documentation for more information on the user property. After completing setup, you can check gates, experiments, and log events in any sub-file of layout.tsx. ## Use the SDK ### Checking a Feature Flag/Gate Now that your SDK is initialized, let's check a [**Feature Gate**](/feature-flags/overview). Feature Gates can be used to create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (think `return false;`) by default. {% tabs %} {% tab title="App Router" %} ```tsx 'use client'; import { useGateValue } from "@statsig/react-bindings"; export default function Home() { const gate = useGateValue("my_gate"); return ( <div> Gate Value: {gate ? 'PASSED' : 'FAILED'} </div> ); } ``` {% callout type="note" %} In an App Router app, you need to use the [`use client` directive](https://nextjs.org/docs/app/building-your-application/rendering/client-components) to ensure your logic runs on the frontend. {% /callout %} {% /tab %} {% tab title="Page Router" %} ```tsx import { useGateValue } from "@statsig/react-bindings"; export default function Home() { const gate = useGateValue("my_gate"); return ( <div> Gate Value: {gate ? 'PASSED' : 'FAILED'} </div> ); } ``` {% /tab %} {% /tabs %} ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional advanced user targeting. To send a different set of values (strings, numbers, etc.) to clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates, but you get a complete JSON object you can configure on the server and fetch typed parameters from it. For example: {% tabs %} {% tab title="App Router" %} ```tsx 'use client'; import { useDynamicConfig } from "@statsig/react-bindings"; export default function Home() { const config = useDynamicConfig("my_dynamic_config"); return ( <div> Title: {config.get('title', 'Fallback Title')} </div> ); } ``` {% callout type="note" %} In an App Router app, you need to use the [`use client` directive](https://nextjs.org/docs/app/building-your-application/rendering/client-components) to ensure your logic runs on the frontend. {% /callout %} {% /tab %} {% tab title="Page Router" %} ```tsx import { useDynamicConfig } from "@statsig/react-bindings"; export default function Home() { const config = useDynamicConfig("my_dynamic_config"); return ( <div> Title: {config.get('title', 'Fallback Title')} </div> ); } ``` {% /tab %} {% /tabs %} ### Getting a Layer/Experiment **Layers/Experiments** let you run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for quicker iterations with parameter reuse. {% tabs %} {% tab title="App Router" %} ```tsx 'use client'; import { useExperiment, useLayer} from "@statsig/react-bindings"; export default function Home() { const layer = useLayer("my_experiment_layer"); // or const experiment = useExperiment("my_experiment"); return ( <div> Title: {layer.get('title', 'Fallback Title')} {/* or */} Title: {experiment.get('title', 'Fallback Title')} </div> ); } ``` {% callout type="note" %} In an App Router app, you need to use the [`use client` directive](https://nextjs.org/docs/app/building-your-application/rendering/client-components) to ensure your logic runs on the frontend. {% /callout %} {% /tab %} {% tab title="Page Router" %} ```tsx import { useExperiment, useLayer} from "@statsig/react-bindings"; export default function Home() { const layer = useLayer("my_experiment_layer"); // or const experiment = useExperiment("my_experiment"); return ( <div> Title: {layer.get('title', 'Fallback Title')} {/* or */} Title: {experiment.get('title', 'Fallback Title')} </div> ); } ``` {% /tab %} {% /tabs %} ## Parameter Stores Parameter Stores hold a set of parameters for your app. These parameters can be remapped dynamically from a static value to a Statsig entity (Feature Gates, Experiments, and Layers), so you can decouple your code from the configuration in Statsig. Refer to [Parameter Stores](/client/concepts/parameter-stores) for details. {% tabs %} {% tab title="App Router" %} ```tsx 'use client'; import { useParameterStore} from "@statsig/react-bindings"; export default function Home() { const store = useParameterStore("my_param_store"); return ( <div> Title: {store.get('title', 'Fallback Title')} </div> ); } ``` {% callout type="note" %} In an App Router app, you need to use the [`use client` directive](https://nextjs.org/docs/app/building-your-application/rendering/client-components) to ensure your logic runs on the frontend. {% /callout %} {% /tab %} {% tab title="Page Router" %} ```tsx import { useParameterStore} from "@statsig/react-bindings"; export default function Home() { const store = useParameterStore("my_param_store"); return ( <div> Title: {store.get('title', 'Fallback Title')} </div> ); } ``` {% /tab %} {% /tabs %} ### Logging an Event After setting up a Feature Gate or Experiment, you may want to track custom events to see how new features or experiment groups affect those events. Call the Log Event API for the event. You can also provide a value and metadata object to be logged with the event: {% tabs %} {% tab title="App Router" %} ```tsx 'use client'; import { useStatsigClient } from "@statsig/react-bindings"; export default function Home() { const { client } = useStatsigClient(); return ( <div> <button onClick={() => client.logEvent("my_custom_event")}> Click Me </button> </div> ); } ``` {% callout type="note" %} In an App Router app, you need to use the [`use client` directive](https://nextjs.org/docs/app/building-your-application/rendering/client-components) to ensure your logic runs on the frontend. {% /callout %} {% /tab %} {% tab title="Page Router" %} ```tsx import { useStatsigClient } from "@statsig/react-bindings"; export default function Home() { const { client } = useStatsigClient(); return ( <div> <button onClick={() => client.logEvent("my_custom_event")}> Click Me </button> </div> ); } ``` {% /tab %} {% /tabs %} ### Flushing Logged Events `flush()` sends queued events immediately. Use `shutdown()` when your app is exiting. {% tabs %} {% tab title="App Router" %} ```tsx 'use client'; import { useStatsigClient } from "@statsig/react-bindings"; export default function Home() { const { client } = useStatsigClient(); return ( <div> <button onClick={async () => { await client.flush(); }} > Flush Events </button> </div> ); } ``` {% callout type="note" %} In an App Router app, you need to use the [`use client` directive](https://nextjs.org/docs/app/building-your-application/rendering/client-components) to ensure your logic runs on the frontend. {% /callout %} {% /tab %} {% tab title="Page Router" %} ```tsx import { useStatsigClient } from "@statsig/react-bindings"; export default function Home() { const { client } = useStatsigClient(); return ( <div> <button onClick={async () => { await client.flush(); }} > Flush Events </button> </div> ); } ``` {% /tab %} {% /tabs %} ## Session Replay {% tabs %} {% tab title="App Router" %} ```tsx 'use client'; import { StatsigProvider } from '@statsig/react-bindings'; import { StatsigSessionReplayPlugin } from '@statsig/session-replay'; export default function App({ children }: { children: React.ReactNode }) { return ( <StatsigProvider sdkKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!} user={{ userID: 'a-user' }} options={{ plugins: [new StatsigSessionReplayPlugin()] }} > {children} </StatsigProvider> ); } ``` {% /tab %} {% tab title="Page Router" %} ```tsx import { StatsigProvider } from '@statsig/react-bindings'; import { StatsigSessionReplayPlugin } from '@statsig/session-replay'; export default function App({ Component, pageProps }) { return ( <StatsigProvider sdkKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!} user={{ userID: 'a-user' }} options={{ plugins: [new StatsigSessionReplayPlugin()] }} > <Component {...pageProps} /> </StatsigProvider> ); } ``` {% /tab %} {% /tabs %} ## Web Analytics / Auto Capture By including the [`@statsig/web-analytics`](https://www.npmjs.com/package/@statsig/web-analytics) package in your project, you can automatically capture common web events like clicks and page views. For more information on filtering events, enabling console log capture, and other configuration options available in web analytics, refer to the [Web Analytics Configuration](/webanalytics/overview#event-filtering-and-console-configuration) documentation. {% tabs %} {% tab title="App Router" %} ```tsx 'use client'; import { StatsigProvider } from '@statsig/react-bindings'; import { StatsigAutoCapturePlugin } from '@statsig/web-analytics'; export default function App({ children }: { children: React.ReactNode }) { return ( <StatsigProvider sdkKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!} user={{ userID: 'a-user' }} options={{ plugins: [new StatsigAutoCapturePlugin()] }} > {children} </StatsigProvider> ); } ``` {% /tab %} {% tab title="Page Router" %} ```tsx import { StatsigProvider } from '@statsig/react-bindings'; import { StatsigAutoCapturePlugin } from '@statsig/web-analytics'; export default function App({ Component, pageProps }) { return ( <StatsigProvider sdkKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!} user={{ userID: 'a-user' }} options={{ plugins: [new StatsigAutoCapturePlugin()] }} > <Component {...pageProps} /> </StatsigProvider> ); } ``` {% /tab %} {% /tabs %} ## Stable ID Stable ID provides a consistent device identifier. It lets you run [logged-out experiments](/guides/first-device-level-experiment) and target gates at the device level. ### How Stable ID Works * On first initialization, the SDK generates a Stable ID and stores it in `localStorage` under `statsig.stable_id.<SDK_KEY_HASH>`. * Subsequent sessions reuse the stored value. Each client SDK key has its own Stable ID entry. * Local storage is scoped per domain, so cross-domain usage requires sharing the value manually (see below). ### Reading the Stable ID {% tabs %} {% tab title="JavaScript" %} ```tsx const context = client.getContext(); console.log('Statsig StableID:', context.stableID); ``` {% /tab %} {% tab title="React" %} ```tsx import { useStatsigClient } from '@statsig/react-bindings'; function MyComponent() { const { client } = useStatsigClient(); const context = client.getContext(); return <div>{context.stableID}</div>; } ``` {% /tab %} {% /tabs %} ### Overriding the Stable ID Provide a custom Stable ID through `StatsigUser.customIDs.stableID` if you already manage a durable device identifier. {% tabs %} {% tab title="JavaScript" %} ```tsx import { StatsigClient, StatsigUser } from '@statsig/js-client'; const userWithStableID: StatsigUser = { customIDs: { stableID: 'my-custom-stable-id', }, }; const client = new StatsigClient('client-xyz', userWithStableID); await client.updateUserAsync(userWithStableID); ``` {% /tab %} {% tab title="React" %} ```tsx import { StatsigProvider, useStatsigClient } from '@statsig/react-bindings'; function App() { return ( <StatsigProvider sdkKey="client-xyz" user={{ customIDs: { stableID: 'my-custom-stable-id' }, }} > <div>Your App</div> </StatsigProvider> ); } function MyComponent() { const { client } = useStatsigClient(); useEffect(() => { client.updateUserAsync({ customIDs: { stableID: 'my-custom-stable-id' }, }); }, [client]); } ``` {% /tab %} {% /tabs %} {% callout type="note" %} When you override the Stable ID, it is persisted to local storage, so subsequent sessions reuse your custom value. {% /callout %} ### Sharing Stable ID Across Subdomains Add this helper script before initializing the SDK and then copy the stored value onto your user object. ```html <!-- cross domain id script --> <script>!function(){let t="STATSIG_LOCAL_STORAGE_STABLE_ID";function e(){if(crypto&&crypto.randomUUID)return crypto.randomUUID();let t=()=>Math.floor(65536*Math.random()).toString(16).padStart(4,"0");return`$\{t()\}${t()}-$\{t()\}-4${t().substring(1)}-$\{t()\}-${t()}$\{t()\}${t()}`}let i=null,n=localStorage.getItem(t)||null;if(document.cookie.match(/statsiguuid=([\w-]+);?/)&&([,i]=document.cookie.match(/statsiguuid=([\w-]+);?/)),i&&n&&i===n);else if(i&&n&&i!==n)localStorage.setItem(t,i);else if(i&&!n)localStorage.setItem(t,i);else{let o=e();localStorage.setItem(t,o),function t(i){let n=new Date;n.setMonth(n.getMonth()+12);let o=window.location.host.split(".");o.length>2&&o.shift();let s=`.$\{o.join(".")\}`;document.cookie=`statsiguuid=${i||e()};Expires=$\{n\};Domain=${s};Path=/`}(o)}}();</script> <!-- Manually attach stableID to user object --> <script> const userObj = {}; if (localStorage.getItem('STATSIG_LOCAL_STORAGE_STABLE_ID')) { userObj.customIDs = { stableID: localStorage.getItem('STATSIG_LOCAL_STORAGE_STABLE_ID'), }; } const client = new Statsig.StatsigClient('<client-sdk-key>', userObj); </script> ``` _Use this script at your discretion and test thoroughly._ ### Aligning Stable ID Between Client and Server To share Stable ID with a backend Statsig SDK, send the value with requests and persist it server-side when missing. The server can bootstrap the client with the same Stable ID. ```tsx // Server: ensure Stable ID exists, then return initialize response for the client const values = Statsig.getClientInitializeResponse(user, YOUR_CLIENT_KEY, { hash: 'djb2', }); // Client: apply the server-provided values and initialize synchronously const { values, user: verifiedUser } = await fetch('/init-statsig-client', { method: 'POST', body: loadUserData(), }).then((res) => res.json()); const myClient = new StatsigClient(YOUR_CLIENT_KEY, verifiedUser); myClient.dataAdapter.setData(values); myClient.initializeSync(); ``` ## Lifecycle & Advanced Usage ## Shutting Statsig Down The SDK keeps event logs in the client cache and flushes them periodically to save data and battery usage. Because of this, the SDK may not have flushed some events when your app shuts down. To ensure all logged events are flushed or saved locally, shut down Statsig when your app is closing: {% tabs %} {% tab title="App Router" %} ```tsx 'use client'; import { useEffect } from "react"; import { useStatsigClient } from "@statsig/react-bindings"; export default function Home() { const { client } = useStatsigClient(); useEffect(() => { return () => { void client.shutdown(); }; }, [client]); return null; } ``` {% callout type="note" %} In an App Router app, you need to use the [`use client` directive](https://nextjs.org/docs/app/building-your-application/rendering/client-components) to ensure your logic runs on the frontend. {% /callout %} {% /tab %} {% tab title="Page Router" %} ```tsx import { useEffect } from "react"; import { useStatsigClient } from "@statsig/react-bindings"; export default function Home() { const { client } = useStatsigClient(); useEffect(() => { return () => { void client.shutdown(); }; }, [client]); return null; } ``` {% /tab %} {% /tabs %} ## Advanced Setup ### Client Bootstrapping (Recommended) {% tabs %} {% tab title="App Router" %} ```ts app/api/statsig-bootstrap/route.ts import { Statsig, StatsigUser } from '@statsig/statsig-node-core'; export async function POST(request: Request): Promise<Response> { const body = await request.json(); const user = new StatsigUser(body?.user ?? {}); // Ensure server SDK is initialized at startup // await Statsig.initialize(process.env.STATSIG_SERVER_KEY!); const values = Statsig.getClientInitializeResponse(user, { hashAlgorithm: 'djb2', }); return new Response(JSON.stringify(values), { status: 200 }); } ``` ```tsx app/layout.tsx import { StatsigBootstrapProvider } from '@statsig/next'; export default function RootLayout({ children }: { children: React.ReactNode }) { const user = { userID: 'user-123' }; return ( <html lang="en"> <body> <StatsigBootstrapProvider user={user} clientKey={process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY} serverKey={process.env.STATSIG_SERVER_KEY} > {children} </StatsigBootstrapProvider> </body> </html> ); } ``` {% /tab %} {% tab title="Page Router" %} ```ts pages/api/statsig-bootstrap.ts import type { NextApiRequest, NextApiResponse } from 'next'; import { Statsig, StatsigUser } from 'statsig-node'; // legacy Node SDK for pages router export default async function handler( req: NextApiRequest, res: NextApiResponse<string>, ) { if (req.method !== 'POST') { res.status(400).send('/statsig-bootstrap only supports POST'); return; } // Ensure server SDK is initialized at startup // await Statsig.initialize(process.env.STATSIG_SERVER_KEY!); const { user } = JSON.parse(req.body) as { user: StatsigUser }; const values = Statsig.getClientInitializeResponse(user, { hash: 'djb2' }); res.status(200).send(JSON.stringify(values)); } ``` ```tsx pages/_app.tsx import type { AppProps } from 'next/app'; import { StatsigProvider } from '@statsig/react-bindings'; import { StatsigClient } from '@statsig/js-client'; import { useEffect, useMemo, useState } from 'react'; export default function App({ Component, pageProps }: AppProps) { const user = useMemo(() => ({ userID: 'a-user' }), []); const [client, setClient] = useState<StatsigClient | null>(null); useEffect(() => { (async () => { const res = await fetch('/api/statsig-bootstrap', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ user }), }); const initializeValues = await res.json(); const inst = new StatsigClient( process.env.NEXT_PUBLIC_STATSIG_CLIENT_KEY!, user, { initializeValues }, ); await inst.initializeAsync(); setClient(inst); })(); }, [user]); if (!client) { return null; } return ( <StatsigProvider client={client}> <Component {...pageProps} /> </StatsigProvider> ); } ``` {% /tab %} {% /tabs %} ### Proxying Network Traffic (Optional) {% tabs %} {% tab title="App Router" %} ```ts app/proxy/initialize/route.ts // Note: Using generic path names like "proxy" instead of "statsig-proxy" // to prevent ad blockers from blocking these requests import { generateBootstrapValues } from './statsig-backend'; export async function POST(request: Request): Promise<Response> { const json = await request.json(); if (!json || typeof json !== 'object') { return new Response(null, { status: 400 }); } const data = await generateBootstrapValues(); return new Response(data); } ``` ```ts app/proxy/search.ts // Note: Using generic path names like "search" instead of "log_event" or "events" // to prevent ad blockers from blocking these requests type ExtendedRequestInit = RequestInit & { duplex?: 'half' | 'full' }; export async function POST(request: Request): Promise<Response> { const tail = request.url.split('?').pop(); const logEventUrl = `https://events.statsigapi.net/v1/log_event?${tail}`; const fetchOptions: ExtendedRequestInit = { method: 'POST', body: request.body, headers: request.headers, duplex: 'half', }; return fetch(logEventUrl, fetchOptions); } ``` {% /tab %} {% tab title="Page Router" %} ```ts pages/api/proxy/initialize.ts // Note: Using generic path names like "proxy" instead of "statsig-proxy" // to prevent ad blockers from blocking these requests import type { NextApiRequest, NextApiResponse } from 'next'; import { StatsigUser } from 'statsig-node'; import { getStatsigValues } from '@/pages/statsig-backend'; export default async function handler( req: NextApiRequest, res: NextApiResponse<string>, ) { if (req.method !== 'POST') { res.status(400).send('/initialize only supports POST'); return; } const { user } = JSON.parse(req.body) as { user: StatsigUser }; const values = await getStatsigValues(user); res.status(200).send(values); } ``` ```ts pages/api/proxy/search.ts // Note: Using generic path names like "search" instead of "log_event" or "events" // to prevent ad blockers from blocking these requests import type { NextApiRequest, NextApiResponse } from 'next'; type ExtendedRequestInit = RequestInit & { duplex?: 'half' | 'full' }; export default async function handler( req: NextApiRequest, res: NextApiResponse<string>, ): Promise<void> { if (req.method !== 'POST') { res.status(400).send('/search only supports POST'); return; } let logEventUrl = `https://events.statsigapi.net/v1/log_event`; const queryParams = [] as string[]; for (const [key, value] of Object.entries(req.query)) { queryParams.push(`$\{key\}=${value}`); } if (queryParams.length > 0) { logEventUrl += '?' + queryParams.join('&'); } const fetchOptions: ExtendedRequestInit = { method: 'POST', body: req.body as BodyInit, headers: req.headers as HeadersInit, duplex: 'half', }; try { const response = await fetch(logEventUrl, fetchOptions); if (!response.ok) { res.status(500).send('Failed to log event'); return; } const body = await response.text(); res.status(response.status).send(body); } catch (err) { res.status(500).send('Failed to log event: ' + err); } } ``` {% /tab %} {% /tabs %} ```ts // Assign URLs when creating the client const inst = new StatsigClient(clientSdkKey, user, { networkConfig: { logEventUrl: '/api/proxy/search', initializeUrl: '/api/proxy/initialize', logEventCompressionMode: 'Forced', }, disableCompression: true, disableStatsigEncoding: true, }); ``` ## Static Site Generation (SSG) Static Site Generation renders HTML at build time. Because static HTML can't respond to per-user values, experimenting on SSG content requires one of these patterns: * Use Edge Middleware with Statsig's Edge Config Adapter for zero-latency redirects. * Isolate Statsig usage to hydrated client components only. ```tsx // Create a single client and share it across multiple StatsigProviders const myStatsigClient = new StatsigClient(YOUR_SDK_KEY, user, options); await myStatsigClient.initializeAsync(); <StatsigProvider client={myStatsigClient}> <YourComponent /> </StatsigProvider> <StatsigProvider client={myStatsigClient}> <AnotherComponent /> </StatsigProvider> ``` ## Statsig Options {% parameter name="loggingEnabled" type="LoggingEnabledOption" %} Controls logging behavior. * `browser-only` (default): log events from browser environments. * `disabled`: never send events. * `always`: log in every environment, including non-browser contexts. {% /parameter %} {% parameter name="disableLogging" type="boolean" %} Use `loggingEnabled: 'disabled'` instead. {% /parameter %} {% parameter name="disableStableID" type="boolean" %} Skip generating a device-level Stable ID. {% /parameter %} {% parameter name="disableEvaluationMemoization" type="boolean" %} Recompute every evaluation instead of using the memoized result. {% /parameter %} {% parameter name="initialSessionID" type="string" %} Override the generated session ID. {% /parameter %} {% parameter name="enableCookies" type="boolean" %} Persist Stable ID in cookies for cross-domain tracking. {% /parameter %} {% parameter name="disableStorage" type="boolean" %} Prevent any local storage writes (disables caching). {% /parameter %} {% parameter name="networkConfig" type="NetworkConfig" %} Override network endpoints per request type. {% /parameter %} {% accordion title="Network Config Options" %} {% parameter name="api" type="string" %} Base URL for all requests. The SDK appends endpoint paths like `/initialize` and `/rgstr`; append `/v1` when your proxy expects it. {% /parameter %} {% parameter name="initializeUrl" type="string" %} Endpoint for initialization requests only. Takes precedence over `api` for `/initialize`. {% /parameter %} {% parameter name="initializeFallbackUrls" type="string[]" %} Fallback endpoints for initialization requests only. This doesn't create a generic fallback for `api`. {% /parameter %} {% parameter name="logEventUrl" type="string" %} Endpoint for event uploads. {% /parameter %} {% parameter name="logEventFallbackUrls" type="string[]" %} Fallback endpoints for event uploads only. This doesn't create a generic fallback for `api`. {% /parameter %} {% parameter name="networkTimeoutMs" type="number" %} Request timeout in milliseconds. {% /parameter %} {% parameter name="preventAllNetworkTraffic" type="boolean" %} Disable all outbound requests; combine with `loggingEnabled: 'disabled'` to silence log warnings. {% /parameter %} {% parameter name="networkOverrideFunc" type="function" %} Provide custom transport (e.g., Axios). {% /parameter %} {% /accordion %} {% parameter name="environment" type="StatsigEnvironment" %} Set environment-wide defaults (for example `{ tier: 'staging' }`). {% /parameter %} {% parameter name="logLevel" type="LogLevel" %} Console verbosity. {% /parameter %} {% parameter name="loggingBufferMaxSize" type="number" %} Max events per log batch. {% /parameter %} {% parameter name="loggingIntervalMs" type="number" %} Interval between automatic flushes. {% /parameter %} {% parameter name="overrideAdapter" type="OverrideAdapter" %} Modify evaluations before returning them. {% /parameter %} {% parameter name="includeCurrentPageUrlWithEvents" type="boolean" %} Attach the current page URL to logged events. {% /parameter %} {% parameter name="disableStatsigEncoding" type="boolean" %} Send requests without Statsig-specific encoding. {% /parameter %} {% parameter name="logEventCompressionMode" type="LogEventCompressionMode" %} Control compression for batched events. {% /parameter %} {% parameter name="disableCompression" type="boolean" %} Use `logEventCompressionMode` instead. {% /parameter %} {% parameter name="dataAdapter" type="EvaluationsDataAdapter" %} Provide a custom data adapter to control caching/fetching. {% /parameter %} {% parameter name="customUserCacheKeyFunc" type="CustomCacheKeyGenerator" %} Override cache key generation for stored evaluations. {% /parameter %} ## Additional Resources * [JavaScript Client SDK](/client/javascript-sdk) * [React Client SDK](/client/React) * [Initialization Concepts](/client/concepts/initialize) ================================================================================ # Android Client SDK URL: https://docs.statsig.com/client/Android Updated: 2026-07-11 ================================================================================ ## Set up the SDK {% steps %} {% step title="Install the SDK" %} v4.37.1 and higher are published to only [Maven Central](https://central.sonatype.com/artifact/com.statsig/android-sdk). To install the SDK, set the Maven Central repository in your build.gradle. ```java dependencies { implementation "com.statsig:android-sdk:4.37.1" } ``` You can install legacy versions (\<=V4.37.0) with [Jitpack](https://jitpack.io/#statsig-io/android-sdk). {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later on in a gate or experiment. {% codetabs %} ```java MainActivity.java import com.statsig.androidsdk.*; ... public class MainActivity extends AppCompatActivity implements IStatsigCallback { ... StatsigOptions options = new StatsigOptions(); options.setTier(Tier.PRODUCTION); StatsigUser user = new StatsigUser("UUID"); Statsig.initializeAsync(app, "client-key", user, this, options); ... // SDK is usable, but values will be from the cache or defaults (false for gates, {} for configs) // Once onStatsigInitialize fires, then @Override public void onStatsigInitialize() { // SDK is initialized and has the most up to date values } @Override public void onStatsigUpdateUser() { // User has been updated and values have been refetched for the new user } } ``` ```kotlin MainActivity.kt import com.statsig.androidsdk.* ... async { Statsig.initialize( this.application, "my_client_sdk_key", StatsigUser("user_id"), ) }.await() ``` {% /codetabs %} {% /step %} {% /steps %} ## Use the SDK ### Checking a Feature Flag/Gate After the SDK is initialized, check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (equivalent to `return false;`) by default. {% codetabs %} ```java Java DynamicConfig config = Statsig.getConfig("awesome_product_details"); // The 2nd parameter is the default value to be used in case the given parameter name does not exist on // the Dynamic Config object. This can happen when there is a typo, or when the user is offline and the // value has not been cached on the client. String itemName = config.getString("product_name", "Awesome Product v1"); Double price = config.getDouble("price", 10.0); Boolean shouldDiscount = config.getBoolean("discount", false); ``` ```kotlin Kotlin val config = Statsig.getConfig("awesome_product_details") // The 2nd parameter is the default value to be used in case the given parameter name does not exist on // the Dynamic Config object. This can happen when there is a typo, or when the user is offline and the // value has not been cached on the client. val itemName = config.getString("product_name", "Awesome Product v1") val price = config.getDouble("price", 10.0) val shouldDiscount = config.getBoolean("discount", false) ``` {% /codetabs %} ### Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional advanced user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates, but returns an entire JSON object you configure on the server, from which you can fetch typed parameters. For example: {% codetabs %} ```java Java if (Statsig.checkGate("new_homepage_design")) { // Gate is on, show new home page } else { // Gate is off, show old home page } ``` ```kotlin Kotlin if (Statsig.checkGate("new_homepage_design")) { // Gate is on, show new home page } else { // Gate is off, show old home page } ``` {% /codetabs %} ### Getting a Layer/Experiment **Layers/Experiments** support running A/B/n experiments. Two APIs are available, but [layers](/experiments/layers-overview) are recommended to enable quicker iterations with parameter reuse. {% codetabs %} ```java Java // Values via getLayer Layer layer = Statsig.getLayer("user_promo_experiments") String promoTitle = layer.getString("title", "Welcome to Statsig!"); Double discount = layer.getDouble("discount", 0.1); // or, via getExperiment DynamicConfig titleExperiment = Statsig.getExperiment("new_user_promo_title"); DynamicConfig priceExperiment = Statsig.getExperiment("new_user_promo_price"); String promoTitle = titleExperiment.getString("title", "Welcome to Statsig!"); Double discount = priceExperiment.getDouble("discount", 0.1); ... Double price = msrp * (1 - discount); ``` ```kotlin Kotlin // Values via getLayer val layer = Statsig.getLayer("user_promo_experiments") val promoTitle = layer.getString("title", "Welcome to Statsig!") val discount = layer.getDouble("discount", 0.1) // or, via getExperiment val titleExperiment = Statsig.getExperiment("new_user_promo_title") val priceExperiment = Statsig.getExperiment("new_user_promo_price") val promoTitle = titleExperiment.getString("title", "Welcome to Statsig!") val discount = priceExperiment.getDouble("discount", 0.1) ... val price = msrp * (1 - discount); ``` {% /codetabs %} ### Logging an Event After setting up a Feature Gate or an Experiment, you can track custom events to see how your new features or different experiment groups affect those events. Call the Log Event API for the event, and optionally provide a value and metadata object to be logged with the event: {% codetabs %} ```java Java Statsig.logEvent("purchase", 2.99, Map.of("item_name", "remove_ads")); ``` ```kotlin Kotlin Statsig.logEvent("purchase", 2.99, Map.of("item_name" to "remove_ads")) ``` {% /codetabs %} ## Parameter Stores Parameter Stores hold a set of parameters for your mobile app. These parameters can be remapped dynamically from a static value to a Statsig entity (Feature Gates, Experiments, and Layers), so you can decouple your code from the configuration in Statsig. Refer to [Param Stores](/client/concepts/parameter-stores) for more information. ### Getting a parameter store To fetch a set of parameters, use the following API: {% codetabs %} ```java Java ParameterStore homepageStore = Statsig.getParameterStore("homepage"); ``` ```kotlin Kotlin val homepageStore = Statsig.getParameterStore("homepage") ``` {% /codetabs %} ### Getting a parameter You can then access parameters like this: {% codetabs %} ```java Java String title = homepageStore.getString( "title", //parameter name "Welcome" // default value ); boolean shouldShowUpsell = homePageStore.getBoolean("upsell_upgrade_now", false); ``` ```kotlin Kotlin val title = homepageStore.getString( "title", // parameter name "Welcome", // default value ) val shouldShowUpsell = homepageStore.getBoolean("upsell_upgrade_now", false) ``` {% /codetabs %} ## Statsig User You need to provide a StatsigUser object to check or get your configurations. Pass as much information as possible to take advantage of advanced gate and config conditions. The `userID` field is required in most cases to provide a consistent experience for a given user (refer to [logged-out experiments](/guides/first-device-level-experiment) for how to run experiments for logged-out users). In addition to `userID`, the following top-level fields are available on StatsigUser: `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion`. You can also pass any key-value pairs in an object or dictionary to the `custom` field to create targeting based on them. After the user logs in or their attributes change, call `updateUser` with the updated `userID` and any other updated user attributes: {% codetabs %} ```java Java StatsigUser newUser = new StatsigUser("new_user_id"); Statsig.updateUserAsync(newUser, this); // this must implement IStatsigCallback ... @Override public void onStatsigUpdateUser() { // User has been updated and values have been refetched for the new user } ``` ```kotlin Kotlin Statsig.updateUser(StatsigUser("new_user_id")) ``` {% /codetabs %} ### Private attributes To prevent sensitive user PII from being logged, use the `privateAttributes` field on the StatsigUser object. Statsig uses any attribute set in `privateAttributes` only for evaluation and targeting, and removes it from logs before sending them to the server. For example, if you have feature gates that should only pass for users with emails ending in "@statsig.com" but don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ## Statsig Options Pass an optional `options` parameter in addition to `sdkKey` and `user` during initialization to customize the Statsig client. {% parameter name="api" type="String" %} Default endpoint for all SDK network requests. Do not override unless you implement the Statsig API elsewhere. {% /parameter %} {% parameter name="disableCurrentActivityLogging" type="Boolean" %} Include the current top-level activity on logged events by default. Set to `true` to disable. {% /parameter %} {% parameter name="disableDiagnosticsLogging" type="Boolean" %} Deprecated. Previously prevented the SDK from sending diagnostic information. {% /parameter %} {% parameter name="initTimeoutMs" type="Long" %} Milliseconds to wait for the initial request before completing. Set to `0` to wait indefinitely. {% /parameter %} {% parameter name="enableAutoValueUpdate" type="Boolean" %} Periodically fetch updated values for the current user when enabled. {% /parameter %} {% parameter name="autoValueUpdateIntervalMinutes" type="Double" %} Frequency (in minutes) for auto value refresh. Minimum is `1` minute. {% /parameter %} {% parameter name="overrideStableID" type="String?" %} Override the SDK-generated `stableID` for the user. {% /parameter %} {% parameter name="loadCacheAsync" type="Boolean" %} Whether the SDK should block on loading saved values from disk. {% /parameter %} {% parameter name="initializeValues" type="Map<String, Any>?" %} Provide the initialize response directly to bootstrap the client synchronously. Go to the NodeJS Server SDK for generating values and the Bootstrap docs. {% /parameter %} {% parameter name="disableHashing" type="Boolean?" %} When `true`, gate/config/experiment names aren't hashed and remain readable. Requires special authorization from Statsig. {% /parameter %} {% parameter name="customCacheKey" type="((sdkKey: String, user: StatsigUser) -> String)" %} Override how the cache key is generated for stored values when the default doesn't fit your needs. {% /parameter %} {% parameter name="evaluationCallback" type="((config: BaseConfig) -> Unit)" %} Callback invoked whenever a gate, config, experiment, or layer is checked. Receives the evaluated `BaseConfig`. {% /parameter %} {% parameter name="lifetimeCallback" type="IStatsigLifetimeCallback?" %} Callbacks that may trigger multiple times over the lifetime of the client SDK. The SDK calls them on the main thread. * `onValuesUpdated()` - called whenever the fired when new values are received and available for use. May be called after `Statsig.updateUser()`, `Statsig.updateUserAsync()`, or auto value updates (see `enableAutoValueUpdate` above) {% /parameter %} #### Methods * **setTier | setEnvironmentParameter | getEnvironment** * used to signal the environment tier the user is currently in. * `setTier` can be PRODUCTION, STAGING or DEVELOPMENT. For example, passing in a value of `Tier.STAGING` allows your users to pass any condition that passes for the staging environment tier, and fail any condition that only passes for other environment tiers. * `setEnvironmentParameter` can be used for custom tiers, for example `options.setEnvironmentParameter("tier", "test")` #### Runtime options Starting in `V4.43.0`, a subset of options can be set during initialization and later updated while the Statsig client is running. These options are defined in `StatsigRuntimeMutableOptions` (which `StatsigOptions` extends) and are detailed below. Call `Statsig.updateRuntimeOptions(runtimeMutableOptions: StatsigRuntimeMutableOptions)` or the corresponding method in `StatsigClient` to update the Statsig client with new values. * **loggingEnabled**: `Boolean`, default `true` * Setting this value to `false` prevents the Statsig client from sending logging events over the network or saving events to its on-disk cache. The 1000 most recent events are queued in memory. You can log them to network (or cache them) by setting `loggingEnabled` to `true` later during that session. * Calling `Statsig.flush()` after setting `loggingEnabled` to `true` immediately clears the queue and minimizes loss of older log events. * This is useful for cases where users must grant permission before events should be logged, or in any other cases where logging shouldn't be enabled. ## Shutting Statsig Down To save data and battery usage and prevent logged events from being dropped, the SDK keeps event logs in client cache and flushes them periodically. Because of this, some events may not have been sent when your app shuts down. To ensure all logged events are flushed or saved locally, call `shutdown` when your app is closing: {% codetabs %} ```java Java Statsig.shutdown(); ``` ```kotlin Kotlin Statsig.shutdown() ``` {% /codetabs %} ## Using persistent evaluations To ensure that a user's variant stays consistent while an experiment is running, regardless of changes to allocation or targeting, use persistent storage. The Android SDK supports a minimal implementation using the keepDeviceValues flag. Refer to the [Client Persistent Assignment Doc](/client/concepts/persistent_assignment#support-in-ios-and-android-sdks) for more information. ## Local overrides To locally override gates/configs/experiments/layers for testing, Statsig offers convenient methods for a quick local override. Unless you call the remove method, the SDK persists these session-to-session on the client's device. These overrides apply locally only and don't affect definitions in the console or elsewhere. ```kotlin // Overrides the given gate to the specified value overrideGate(gateName: String, value: Boolean) // Overrides the given config (dynamic config or experiment) to the provided value overrideConfig(configName: String, value: Map<String, Any>) // Removes any overrides associated with the provided gate/config/experiment name removeOverride(name: String) // Removes all overrides removeAllOverrides() // Returns the set of gate and config overrides currently in place on the client getAllOverrides(): StatsigOverrides class StatsigOverrides( @SerializedName("gates") val gates: MutableMap<String, Boolean>, @SerializedName("configs") val configs: MutableMap<String, Map<String, Any>> ) {} ``` ## Manual exposures {% callout type="warning" %} Manual logging is error-prone and can introduce issues like uneven exposures, which compromise experiment results. {% /callout %} You can query your gates/experiments without triggering an exposure, and manually log the exposures later: {% tabs %} {% tab title="Check Gate" %} To check a gate without an exposure being logged: {% codetabs %} ```kotlin Kotlin val result = Statsig.checkGateWithExposureLoggingDisabled("a_gate_name") ``` ```java Java boolean result = Statsig.checkGateWithExposureLoggingDisabled("a_gate_name"); ``` {% /codetabs %} Later, to manually log the gate exposure: {% codetabs %} ```kotlin Kotlin Statsig.manuallyLogGateExposure("a_gate_name") ``` ```java Java Statsig.manuallyLogGateExposure("a_gate_name"); ``` {% /codetabs %} {% /tab %} {% tab title="Get Config" %} To get a dynamic config without an exposure being logged: {% codetabs %} ```kotlin Kotlin val config = Statsig.getConfigWithExposureLoggingDisabled("a_config_name") ``` ```java Java DynamicConfig config = Statsig.getConfigWithExposureLoggingDisabled("a_config_name"); ``` {% /codetabs %} Later, to manually log the config exposure: {% codetabs %} ```kotlin Kotlin Statsig.manuallyLogConfigExposure("a_config_name") ``` ```java Java Statsig.manuallyLogConfigExposure("a_config_name"); ``` {% /codetabs %} {% /tab %} {% tab title="Get Experiment" %} To get an experiment without an exposure being logged: {% codetabs %} ```kotlin Kotlin val experiment = Statsig.getExperimentWithExposureLoggingDisabled("an_experiment_name") ``` ```java Java DynamicConfig experiment = Statsig.getExperimentWithExposureLoggingDisabled("an_experiment_name"); ``` {% /codetabs %} Later, to manually log the experiment exposure: {% codetabs %} ```kotlin Kotlin Statsig.manuallyLogExperimentExposure("an_experiment_name", false) ``` ```java Java Statsig.manuallyLogExperimentExposure("an_experiment_name", false); ``` {% /codetabs %} {% /tab %} {% tab title="Get Layer" %} To get a layer parameter without an exposure being logged: {% codetabs %} ```kotlin Kotlin val layer = Statsig.getLayerWithExposureLoggingDisabled("a_layer_name", false) val result = layer.getString("a_parameter_name", "fallback") ``` ```java Java Layer layer = Statsig.getLayerWithExposureLoggingDisabled("a_layer_name"); String result = layer.getString("a_parameter_name", "fallback"); ``` {% /codetabs %} Later, to manually log the layer parameter exposure: {% codetabs %} ```kotlin Kotlin Statsig.manuallyLogLayerParameterExposure("a_layer_name", "a_parameter_name", false) ``` ```java Java Statsig.manuallyLogLayerParameterExposure("a_layer_name", "a_parameter_name", false); ``` {% /codetabs %} {% /tab %} {% /tabs %} ## StableID Each client SDK has a stableID: a device-level identifier generated the first time the SDK is initialized and stored locally for all future sessions. The stableID doesn't change unless storage is wiped or the app is deleted. This enables device-level experiments and experiments where other user-identifiable information is unavailable, such as for logged-out users. ```kotlin // Retrieve the StableID Statsig.getStableID(); // Override the StableID before initializing, if you have something you'd prefer to use instead val opts = StatsigOptions(overrideStableID = "my_stable_id") Statsig.initialize(app, "client-xyx", options = opts) ``` ## Using multiple instances of the SDK The examples above use the SDK's singleton. Multiple instances of the SDK are also supported. The `Statsig` singleton wraps a single instance of the SDK (typically called a `StatsigClient`) that you can instantiate directly. {% callout type="note" %} You must use a different SDK key for each SDK instance you create for this to work. Various functionality of the Statsig client is keyed on the SDK key being used. Using the same key leads to collisions. {% /callout %} All top-level static methods from the singleton carry over as instance methods. To create an instance of the Statsig SDK: {% codetabs %} ```java Java StatsigClient client = new StatsigClient(); client.initializeAsync(application, sdkKey, user, callback, options); ``` ```kotlin Kotlin var client: StatsigClient = StatsigClient() client.initialize(application, sdkKey, user, options) ``` {% /codetabs %} ## Initialize response The SDK provides a method to access the raw values used internally for gate, config, and layer evaluation. This is useful for debugging or advanced use cases where you need to access the underlying data. For example, you can use these values to bootstrap another SDK, such as the JavaScript SDK when opening an in-app browser. The `getInitializeResponseJson` method returns an `ExternalInitializeResponse` object that contains: 1. A JSON string representation of the initialize response values 2. Evaluation details that provide metadata about how the values were obtained (network, cache, etc.) {% codetabs %} ```java Java // Get the raw values that the SDK is using internally to provide gate/config/layer results ExternalInitializeResponse response = Statsig.getInitializeResponseJson(); // Get the JSON string representation of the initialize response String jsonValues = response.getInitializeResponseJSON(); // Get the evaluation details EvaluationDetails details = response.getEvaluationDetails(); ``` ```kotlin Kotlin // Get the raw values that the SDK is using internally to provide gate/config/layer results val response = Statsig.getInitializeResponseJson() // Get the JSON string representation of the initialize response val jsonValues = response.getInitializeResponseJSON() // Get the evaluation details val details = response.getEvaluationDetails() ``` {% /codetabs %} ================================================================================ # iOS/tvOS/macOS Client SDK URL: https://docs.statsig.com/client/iosClientSDK Updated: 2026-07-11 ================================================================================ ## Set up the SDK {% steps %} {% step title="Install the SDK" %} To use the SDK in your project, add Statsig as a dependency. {% tabs %} {% tab title="Swift Package Manager" %} In your Xcode, select File > Swift Packages > Add Package Dependency and enter the URL https://github.com/statsig-io/statsig-kit.git. You can also include it directly in your project's Package.swift. Find the latest release version on the [GitHub page](https://github.com/statsig-io/statsig-kit/releases). ```swift //... dependencies: [ // see the latest version on https://github.com/statsig-io/statsig-kit/releases .package(url: "https://github.com/statsig-io/statsig-kit.git", .upToNextMinor("X.Y.Z")), ], //... targets: [ .target( name: "YOUR_TARGET", dependencies: ["Statsig"] ) ], //... ``` {% /tab %} {% tab title="Cocoapods" %} If you use CocoaPods, the pod name is 'Statsig'. Include the following line in your Podfile: ```ruby use_frameworks! target 'TargetName' do //... pod 'Statsig', '~> X.Y.Z' end ``` Find the latest versions by searching [cocoapods.org](https://cocoapods.org/) or on [Github](https://github.com/statsig-io/statsig-kit/releases). {% /tab %} {% /tabs %} {% /step %} {% step title="Initialize the SDK" %} Initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you want to target later in a gate or experiment. {% codetabs %} ```swift Swift Statsig.initialize( sdkKey: "my_client_sdk_key", user: StatsigUser(userID: "my_user_id"), options: StatsigOptions(environment: StatsigEnvironment(tier: .Staging))) { error in // Statsig has finished fetching the latest feature gate and experiment values for your user. // If you need the most recent values, you can get them now. // You can also check error.message and error.code for any debugging information. } ``` ```objective-c Objective C StatsigUser *user = [[StatsigUser alloc] initWithUserID:@"my_user_id"]; [Statsig initializeWithSDKKey:@"my_client_sdk_key" user:user completion:^(StatsigClientError * error) { // Statsig has finished fetching the latest feature gate and experiment values for your user. // If you need the most recent values, you can get them now. // You can also check error.message and error.code for any debugging information. }]; ``` {% /codetabs %} The SDK calls the completion block after the network request to fetch the latest feature gate and experiment values for your user. If you request any value before the SDK calls the completion block, you may get the cached value from the previous session or the default value. Wait for the completion block before requesting the latest value. {% /step %} {% /steps %} ## Use the SDK ### Checking a Feature Flag/Gate Now that your SDK is initialized, check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (think `return false;`) by default. {% codetabs %} ```swift Swift if Statsig.checkGate("new_homepage_design") { // Gate is on, show new home page } else { // Gate is off, show old home page } ``` ```objective-c Objective C if ([Statsig checkGateForName:@"new_homepage_design"]) { // Gate is on, show new home page } else { // Gate is off, show old home page } ``` {% /codetabs %} ### Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional advanced user targeting. To send different values (strings, numbers, and similar types) to your clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates but returns a full JSON object you can configure on the server and fetch typed parameters from. For example: {% codetabs %} ```swift Swift let config = Statsig.getConfig("awesome_product_details") // The 2nd parameter is the default value to be used in case the given parameter name does not exist on // the Dynamic Config object. This can happen when there is a typo, or when the user is offline and the // value has not been cached on the client. let itemName = config.getValue(forKey: "product_name", defaultValue: "Awesome Product v1") let price = config.getValue(forKey: "price", defaultValue: 10.0) let shouldDiscount = config.getValue(forKey: "discount", defaultValue: false) ``` ```objective-c Objective C DynamicConfig *config = [Statsig getConfigForName:@"awesome_product_details"]; // The 2nd parameter is the default value to be used in case the given parameter name does not exist on // the Dynamic Config object. This can happen when there is a typo, or when the user is offline and the // value has not been cached on the client. NSString *itemName = [config.getStringForKey:@"product_name" defaultValue:@"Awesome Product v1"]; double price = [config getDoubleForKey:@"price" defaultValue:10.0]; BOOL shouldDiscount = [config getBoolForKey:@"discount" defaultValue:false]; ``` {% /codetabs %} ### Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Statsig offers two APIs, but recommends [layers](/experiments/layers-overview) to enable quicker iterations with parameter reuse. {% codetabs %} ```swift Swift // Values via getLayer let layer = Statsig.getLayer("user_promo_experiments") let promoTitle = layer.getValue(forKey: "title", defaultValue: "Welcome to Statsig!") let discount = layer.getValue(forKey: "discount", defaultValue: 0.1) // or, via getExperiment let titleExperiment = Statsig.getExperiment("new_user_promo_title") let priceExperiment = Statsig.getExperiment("new_user_promo_price") let promoTitle = titleExperiment.getValue(forKey: "title", defaultValue: "Welcome to Statsig") let discount = priceExperiment.getValue(forKey: "discount", defaultValue: 0.1) ... let price = msrp * (1 - discount); ``` ```objective-c Objective C DynamicConfig *expConfig = [Statsig getExperimentForName:@"new_user_promo"]; NSString *promoTitle = [expConfig.getStringForKey:@"title" defaultValue:@"Welcome to Statsig! Use discount code WELCOME10OFF for 10% off your first purchase!"]; double discount = [expConfig getDoubleForKey:@"discount" defaultValue:0.1]; double price = msrp * (1 - discount); ``` {% /codetabs %} ### Logging an Event After you set up a Feature Gate or an Experiment, you can track custom events to measure how your new features or experiment groups affect those events. Call the Log Event API for the event, and optionally provide a value and/or a metadata object to log together with the event: {% codetabs %} ```swift Swift Statsig.logEvent(withName: "purchase", value: 2.99, metadata: ["item_name": "remove_ads"]) ``` ```objective-c Objective C [Statsig logEvent:@"purchase" doubleValue:2.99 metadata:@{ @"item_name" : @"remove_ads" }]; ``` {% /codetabs %} ## Parameter Stores Parameter Stores hold a set of parameters for your mobile app. These parameters can be remapped dynamically from a static value to a Statsig entity (Feature Gates, Experiments, and Layers), so you can decouple your code from the configuration in Statsig. Go to [Parameter Stores](/client/concepts/parameter-stores) to learn more. ## Statsig User Provide a StatsigUser object to check or get your configurations. Pass as much information as possible to take advantage of advanced gate and config conditions. The `userID` field is usually required to provide a consistent experience for a given user. (Refer to [logged-out experiments](/guides/first-device-level-experiment) to understand how to correctly run experiments for logged-out users.) Besides `userID`, the `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion` fields are also available as top-level fields on StatsigUser. You can also pass any key-value pairs in an object/dictionary to the `custom` field to create targeting based on them. After the user logs in or their attributes change, call `updateUserWithResult` with the updated `userID` and/or any other updated user attributes. {% codetabs %} ```swift Swift let user = StatsigUser( userID: "a-user-id", email: "user@example.com", ip: "192.168.1.1", userAgent: "Mozilla/5.0", country: "US", locale: "en_US", appVersion: "1.0.0", custom: [ "plan": "premium", "age": 25 ], customIDs: [ "stableID": "stable-id-123" ], privateAttributes: [ "email": "private@example.com" ] ) ``` ```objective-c Objective C StatsigUser *user = [[StatsigUser alloc] initWithUserID:@"a-user-id"]; user.email = @"user@example.com"; user.ip = @"192.168.1.1"; user.userAgent = @"Mozilla/5.0"; user.country = @"US"; user.locale = @"en_US"; user.appVersion = @"1.0.0"; user.custom = @{ @"plan": @"premium", @"age": @25 }; user.customIDs = @{ @"stableID": @"stable-id-123" }; user.privateAttributes = @{ @"email": @"private@example.com" }; ``` {% /codetabs %} ## Statsig Options {% parameter name="initTimeout" type="Double" %} Determines how long the Statsig client waits for the initial network request to respond before calling the completion block. The Statsig client returns either cached values (if any) or default values if checkGate/getConfig/getExperiment is called before the initial network request completes. If you always want to wait for the latest values fetched from Statsig server, set this to 0 to avoid timing out the network request. {% /parameter %} {% parameter name="disableCurrentVCLogging" type="Bool" %} By default, any custom event your application logs with `Statsig.logEvent()` includes the current root View Controller. This allows Statsig to generate user journey funnels for your users. Set this parameter to `true` to disable this behavior. {% /parameter %} {% parameter name="printHandler" type="((String) -> Void)?" %} A handler for log messages from the SDK. If not provided, the SDK prints logs to the console. The handler receives the message string that would otherwise be printed to the console. Useful for redirecting logs to your own logging system, suppressing unnecessary console output, or debugging issues with the SDK. {% /parameter %} {% parameter name="environment" type="StatsigEnvironment" %} StatsigEnvironment is a class for setting environment variables that apply to all of your users in the same session. Statsig uses these variables for targeting purposes. For example, passing in a value of `StatsigEnvironment(tier: .Staging)` allows your users to pass any condition that passes for the staging environment tier, and fail any condition that only passes for other environment tiers. {% /parameter %} {% parameter name="evaluationCallback" type="EvaluationCallbackData" %} EvaluationCallback provides a callback when an evaluation occurs against one of your configurations (gate, dynamic config, experiment, layer, and parameter stores). This is useful when you want to trigger specific actions or log evaluations based on the results received from Statsig. To use the EvaluationCallback, provide a callback function during SDK initialization through StatsigOptions. The SDK invokes the callback every time an evaluation occurs for feature gates, dynamic configs, experiments, layers, or parameter stores. The EvaluationCallbackData enum defines the different types of data that the evaluationCallback returns when the Statsig iOS SDK evaluates feature gates, dynamic configs, experiments, layers, or parameter stores. Here is the structure of the enum: ```swift public enum EvaluationCallbackData { case gate (FeatureGate) case config (DynamicConfig) case experiment (DynamicConfig) case layer (Layer) case parameterStore (ParameterStore) } ``` Here's an example of how to set up an evaluation callback: ```swift func callback(data: StatsigOptions.EvaluationCallbackData) { switch data { case .gate(let gate): // Handle gate evaluation case .config(let config): // Handle dynamic config evaluation case .experiment(let experiment): // Handle experiment evaluation case .layer(let layer): // Handle layer evaluation case .parameterStore(let paramStore): // Handle parameter store evaluation } } let opts = StatsigOptions(evaluationCallback: callback) Statsig.initialize(sdkKey: "client-key", options: opts) ``` {% /parameter %} {% parameter name="storageProvider" type="StorageProvider" %} Lets you implement a custom caching strategy by passing an object that conforms to the `StorageProvider` protocol. Default cache key: `com.statsig.cache` ```swift @objc public protocol StorageProvider { @objc func read(_ key: String) -> Data? @objc func write(_ value: Data, _ key: String) @objc func remove(_ key: String) } ``` {% /parameter %} {% parameter name="overrideStableID" type="String" %} Overrides the auto generated StableID that is set for the device. {% /parameter %} {% parameter name="enableCacheByFile" type="Bool" %} Use file caching instead of UserDefaults. Useful if you run into size limits with UserDefaults (ie tvOS). {% /parameter %} {% parameter name="eventLoggingEnabled" type="Bool" %} Controls whether the SDK sends events over the network. Useful when user consent is needed before sending events. The iOS SDK stores up to 1MB of unsent request payloads. {% /parameter %} {% parameter name="initializeValues" type="[String: Any]" %} Provide a Dictionary representing the "initialize response" required to synchronously initialize the SDK. Obtain this value from a Statsig server SDK and use it to [Bootstrap](/client/concepts/initialize/#2-bootstrap-initialization) the SDK when initializing. {% /parameter %} {% parameter name="disableDiagnostics" type="Bool" %} Prevent the SDK from sending useful debug information to Statsig. {% /parameter %} {% parameter name="disableHashing" type="Bool" %} When disabled, the SDK doesn't hash gate/config/experiment names, and they remain readable as plain text. {% callout type="info" %} This requires special authorization from Statsig. Reach out to the support team, your sales contact, or through the [Slack community](https://statsig.com/slack) if you want this enabled. {% /callout %} {% /parameter %} {% parameter name="shutdownOnBackground" type="Bool" %} The SDK automatically shuts down when an app enters the background. If you need to use the SDK while your app is in the background, set this to `false`. {% /parameter %} {% parameter name="initializationURL" type="URL" %} Override the URL used to initialize the SDK. Learn more at /custom_proxy ```swift StatsigOptions(initializationURL: URL(string: "https://example.com/setup")) ``` {% /parameter %} {% parameter name="eventLoggingURL" type="URL" %} Override the URL used to log events. Learn more at /custom_proxy ```swift StatsigOptions(eventLoggingURL: URL(string: "https://example.com/info")) ``` {% /parameter %} ## StableID Each client SDK has the concept of stableID: a device-level identifier generated the first time the SDK is initialized and stored locally for all future sessions. Unless storage is wiped (or the app is deleted), the stableID doesn't change. This allows Statsig to run device-level experiments and experiments when other user-identifiable information is unavailable (logged-out users). {% codetabs %} ```swift Swift let options = StatsigOptions(overrideStableID: "my_stable_id") Statsig.initialize(sdkKey: "client-xyz", options: options) ``` ```objective-c Objective C StatsigOptions *options = [[StatsigOptions alloc] init]; options.overrideStableID = @"my_stable_id"; [Statsig initializeWithSDKKey:@"client-xyz" user:nil options:options completion:nil]; ``` {% /codetabs %} ## Manual Exposures {% callout type="warning" %} Manual logging is error-prone and can often introduce issues like uneven exposures, which compromise experiment results. {% /callout %} You can query your gates/experiments without triggering an exposure and manually log the exposures later. {% tabs %} {% tab title="Feature Gates" %} {% codetabs %} ```swift Swift // Swift - Check without logging exposure let result = Statsig.checkGateWithExposureLoggingDisabled("a_gate_name") // ... // Later, when ready to log the exposure Statsig.manuallyLogGateExposure("a_gate_name") ``` ```objc Objective-C // Objective C - Check without logging exposure bool result = [Statsig checkGateWithExposureLoggingDisabled:@"a_gate_name"]; // ... // Later, when ready to log the exposure [Statsig manuallyLogGateExposure:@"a_gate_name"]; ``` {% /codetabs %} {% /tab %} {% tab title="Dynamic Configs" %} {% codetabs %} ```swift Swift // Swift - Get config without logging exposure let config = Statsig.getConfigWithExposureLoggingDisabled("a_config_name") // ... // Later, when ready to log the exposure Statsig.manuallyLogConfigExposure("a_config_name") ``` ```objc Objective-C // Objective C - Get config without logging exposure DynamicConfig *config = [Statsig getConfigWithExposureLoggingDisabled:@"a_config_name"]; // ... // Later, when ready to log the exposure [Statsig manuallyLogConfigExposure:@"a_config_name"]; ``` {% /codetabs %} {% /tab %} {% tab title="Experiments" %} {% codetabs %} ```swift Swift // Swift - Get experiment without logging exposure let experiment = Statsig.getExperimentWithExposureLoggingDisabled("an_experiment_name") // ... // Later, when ready to log the exposure Statsig.manuallyLogExperimentExposure("an_experiment_name") ``` ```objc Objective-C // Objective C - Get experiment without logging exposure DynamicConfig *experiment = [Statsig getExperimentWithExposureLoggingDisabled:@"an_experiment_name"]; // ... // Later, when ready to log the exposure [Statsig manuallyLogExperimentExposure:@"an_experiment_name"]; ``` {% /codetabs %} {% /tab %} {% tab title="Layers" %} {% codetabs %} ```swift Swift // Swift - Get layer without logging exposure let layer = Statsig.getLayerWithExposureLoggingDisabled("a_layer_name") let result = layer.getValue(forKey: "a_parameter_name", defaultValue: "fallback") // ... // Later, when ready to log the exposure Statsig.manuallyLogLayerParameterExposure("a_layer_name", "a_parameter_name") ``` ```objc Objective-C // Objective C - Get layer without logging exposure Layer *layer = [Statsig getLayerWithExposureLoggingDisabled:@"a_layer_name"]; NSString *result = [layer getStringForKey:@"a_parameter_name" defaultValue:@"fallback"]; // ... // Later, when ready to log the exposure [Statsig manuallyLogLayerParameterExposure:@"a_layer_name" parameterName:@"a_parameter_name"]; ``` {% /codetabs %} {% /tab %} {% /tabs %} ## Local Overrides To locally override gates/configs/experiments/layers for testing, use the local override methods. Unless you call the remove method, these overrides persist session-to-session on the client's device. These overrides apply locally only and don't affect definitions in the console or elsewhere. ## Shutting Statsig Down To save users' data and battery usage and prevent logged events from being dropped, the SDK keeps event logs in client cache and flushes them periodically. Because of this, some events may not have been sent when your app shuts down. To ensure all logged events are flushed or saved locally, call shutdown when your app is closing. {% codetabs %} ```swift Swift Statsig.shutdown() ``` ```objective-c Objective C [Statsig shutdown]; ``` {% /codetabs %} ## Using multiple instances of the SDK The examples above use the SDK's singleton. Statsig also supports creating multiple instances of the SDK. The `Statsig` singleton wraps a single instance of the SDK (typically called a `StatsigClient`) that you can instantiate directly. {% callout type="note" %} Use a different SDK key for each SDK instance. Various functionality of the Statsig client is keyed on the SDK key being used. Using the same key causes collisions. {% /callout %} All top-level static methods from the singleton carry over as instance methods. To create an instance of the Statsig SDK: {% codetabs %} ```swift Swift let client = StatsigClient( sdkKey: "client-xyz", user: StatsigUser(userID: "user-1"), options: StatsigOptions(environment: StatsigEnvironment(tier: .Production)) ) { error in // ready } let gateOn = client.checkGate("some_gate") ``` ```objective-c Objective C StatsigOptions *options = [[StatsigOptions alloc] initWithEnvironment:[[StatsigEnvironment alloc] initWithTier:StatsigEnvironmentTierProduction]]; StatsigUser *user = [[StatsigUser alloc] initWithUserID:@"user-1"]; StatsigClient *client = [[StatsigClient alloc] initWithSdkKey:@"client-xyz" user:user options:options completion:^(StatsigClientError * error) { // ready }]; BOOL gateOn = [client checkGate:@"some_gate"]; ``` {% /codetabs %} {% callout type="warning" %} Use a unique SDK key per instance to avoid collisions. {% /callout %} ## Initialize Response The SDK provides a method to access the raw values used internally for gate, config, and layer values. This is useful for debugging or for advanced use cases where you need to access the underlying data. For example, you can use these values to bootstrap another SDK, such as the JavaScript SDK when you open an in-app browser. The `getInitializeResponseJson` method returns an `ExternalInitializeResponse` object that contains: 1. A JSON string representation of the initialize response values 2. Evaluation details that provide metadata about how the values were obtained (network, cache, etc.) {% codetabs %} ```swift Swift let response = Statsig.getInitializeResponseJson() if let values = response.values { print(values) } let details = response.evaluationDetails ``` ```objective-c Objective C ExternalInitializeResponse *response = [Statsig getInitializeResponseJson]; NSString *values = response.values; if (values) { NSLog(@"%@", values); } EvaluationDetails *details = response.evaluationDetails; ``` {% /codetabs %} ## Listening for changes In v1.14.0+, you can listen for SDK changes using `StatsigListening`. {% codetabs %} ```swift Swift class MyViewController: UIViewController, StatsigListening { override func viewDidLoad() { super.viewDidLoad() if Statsig.isInitialized() { render() } else { Statsig.addListener(self) renderLoading() } } private func render() { let showNewUI = Statsig.checkGate("new_ui_enabled") if showNewUI { // Render the new UI } else { // Render the old UI } } private func renderLoading() { /* loading UI */ } private func renderError(error: StatsigClientError) { /* error UI */ } // StatsigListening func onInitializedWithResult(_ error: StatsigClientError?) { if let error = error { renderError(error) return } render() } func onUserUpdatedWithResult(_ error: StatsigClientError?) { /* optional rerender */ } } ``` {% /codetabs %} ================================================================================ # C++ Client SDK URL: https://docs.statsig.com/client/CPP Updated: 2026-07-11 ================================================================================ ## Set Up the SDK {% steps %} {% step title="Install the SDK" %} {% tabs %} {% tab title="Downloaded From GitHub" %} ```cpp add_subdirectory(path/to/downloaded/statsig_sdk) target_link_libraries(${PROJECT_NAME} StatsigClientSDK) ``` {% /tab %} {% tab title="FetchContent" %} ```cpp include(FetchContent) FetchContent_Declare(statsig GIT_REPOSITORY https://github.com/statsig-io/cpp-client-sdk.git GIT_TAG main ) FetchContent_MakeAvailable(statsig) target_link_libraries(${PROJECT_NAME} StatsigClientSDK) ``` {% /tab %} {% /tabs %} {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later on in a gate or experiment. ```cpp #include <statsig/statsig.h> using namespace statsig; StatsigUser user; user.user_id = "a-user"; user.custom_ids = { {"employeeID", "an-employee"} }; // Create your own instance StatsigClient client; // Initialize synchronously using cached values from the previous session client.InitializeSync("client-{YOUR_CLIENT_SDK_KEY}", user); // or, Initialize asynchronously from network client.InitializeAsync( "client-{YOUR_CLIENT_SDK_KEY}", [](StatsigResultCode result) { // completion callback }, user ); ``` **Synchronous** initialization uses cache (if available) and returns immediately. Data for subsequent sessions is then fetched in the background. **Asynchronous** initialization provides a callback, allowing you to wait for the most current data to be fetched. For convenience, there is also a singleton instance accessible through `StatsigClient::Shared()`. ```cpp // Initialize synchronously using cached values from the previous session StatsigClient::Shared().InitializeSync("client-{YOUR_CLIENT_SDK_KEY}", user); // or, Initialize asynchronously from network StatsigClient::Shared().InitializeAsync( "client-{YOUR_CLIENT_SDK_KEY}", [](StatsigResultCode result) { // completion callback }, user ); ``` **Optional** - Configuration through StatsigOptions To adjust how the SDK works, pass a [StatsigOptions](#statsig-options) struct during initialization. ```cpp StatsigOptions options; options.environment = StatsigEnvironment{"staging"}; client.InitializeSync(..., options); // or client.InitializeAsync(..., options); ``` {% /step %} {% /steps %} ## Use the SDK ### Checking a Feature Flag/Gate After the SDK is initialized, check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (equivalent to `return false;`) by default. ```cpp if (client.CheckGate("a_gate")) { // show new feature } // or, use the shared instance if (StatsigClient::Shared().CheckGate("a_gate")) { // show new feature } ``` ### Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional advanced user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates, but returns an entire JSON object you configure on the server, from which you can fetch typed parameters. For example: ```cpp DynamicConfig config = client.GetDynamicConfig("a_config"); // or, use the shared instance DynamicConfig config = StatsigClient::Shared().GetDynamicConfig("a_config"); // then access the params std::cout << config.GetValue()["a_string_param"] << std::endl; ``` {% callout type="info" %} `DynamicConfig.GetValue` returns `JsonObj`, which is an unordered map of string to `nlohmann/json`. Refer to https://github.com/nlohmann/json {% /callout %} ### Getting a Layer/Experiment **Layers/Experiments** support running A/B/n experiments. Two APIs are available, but [layers](/experiments/layers-overview) are recommended to enable quicker iterations with parameter reuse. ```cpp // Values via getLayer Layer layer = StatsigClient::Shared().GetLayer("name"); std::string promoTitle = layer.GetValue("title").get<std::string>(); double discount = layer.GetValue("discount").get<double>(); // or, via getExperiment Experiment titleExperiment = StatsigClient::Shared().GetExperiment("new_user_promo_title"); Experiment priceExperiment = StatsigClient::Shared().GetExperiment("new_user_promo_price"); std::string promoTitle = titleExperiment.GetValue()["title"].get<std::string>(); double discount = priceExperiment.GetValue()["discount"].get<double>(); ``` {% callout type="info" %} `Layer.GetValue` and `Experiment.GetValue` return `JsonObj`, which are unordered maps of string to `nlohmann/json`. Refer to https://github.com/nlohmann/json {% /callout %} ### Logging an Event After setting up a Feature Gate or an Experiment, you can track custom events to see how your new features or different experiment groups affect those events. Call the Log Event API for the event, and optionally provide a value and metadata object to be logged with the event: ```cpp std::unordered_map<std::string, std::string> metadata{ { "price", "9.99" }, { "item_name", "some_great_product" } }; StatsigEvent event("add_to_cart", "SKU_12345", metadata); StatsigClient::Shared().LogEvent(event); // Then, at some point later, you need to "flush" the events StatsigClient::Shared().Flush(); ``` ## Statsig User You need to provide a StatsigUser object to check or get your configurations. Pass as much information as possible to take advantage of advanced gate and config conditions. The `userID` field is required in most cases to provide a consistent experience for a given user (refer to [logged-out experiments](/guides/first-device-level-experiment) for how to run experiments for logged-out users). In addition to `userID`, the following top-level fields are available on StatsigUser: `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion`. You can also pass any key-value pairs in an object or dictionary to the `custom` field to create targeting based on them. ```cpp StatsigUser user; user.user_id = "a-user"; user.email = "developer@statsig.com"; user.custom_ids = { {"employeeID", "an-employee"} }; ``` ### Private attributes To prevent sensitive user PII from being logged, use the `privateAttributes` field on the StatsigUser object. Any attribute set in `privateAttributes` is used only for evaluation and targeting, and Statsig removes it from logs before sending them to the server. For example, if you have feature gates that should only pass for users with emails ending in "@statsig.com" but don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ### Updating users When user data changes, call an UpdateUser function to make Statsig aware of the new user. ```cpp client.UpdateUserSync(user); // or, use the shared instance StatsigClient::Shared().UpdateUserSync(user); ``` To ensure you have the latest values during an update (for example, when transitioning from logged out to logged in), use the asynchronous update function. ```cpp {client or StatsigClient::Shared()}.UpdateUserAsync( user, [](StatsigResultCode result) { if (result == StatsigResultCode::Ok) { // do something now that the latest values have been fetched } else { // error state } } ); ``` Asynchronous vs Synchronous behaviors are the same as the Initialize functions. ## Statsig Options `StatsigClient::Initialize`, in addition to `sdk_key` and `user`, takes an optional `options` parameter to customize the StatsigClient. Current options are listed below: {% parameter name="api" type="std::string" %} The API to use for all SDK network requests. You don't need to override this unless you have another API that implements the Statsig API endpoints. {% /parameter %} {% parameter name="providers" type="EvaluationsDataProvider" %} Array of EvaluationsDataProvider, used to customize the initialization and update behavior. {% /parameter %} ## Shutting Statsig Down To save data and battery usage and prevent logged events from being dropped, the SDK keeps event logs in client cache and flushes them periodically. Because of this, some events may not have been sent when your app shuts down. To ensure all logged events are flushed or saved locally, call `Shutdown` when your app is closing: ```cpp client.Shutdown(); // or, use the shared instance StatsigClient::Shared().Shutdown(); ``` #### How do I run experiments for logged-out users? Go to the guide on [device level experiments](/guides/first-device-level-experiment). ================================================================================ # Dart Client SDK URL: https://docs.statsig.com/client/Dart Updated: 2026-07-11 ================================================================================ ## Set Up the SDK {% steps %} {% step title="Install the SDK" %} With Dart: ```bash dart pub add statsig ``` With Flutter: ```bash flutter pub add statsig ``` {% callout type="note" %} If you are using **Flutter**, be sure to add Statsig as part of the [app lifecycle](#flutter-lifecycle-hooks) to avoid losing events. {% /callout %} {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later on in a gate or experiment. ```dart import 'package:statsig/statsig.dart'; await Statsig.initialize('client-sdk-key', StatsigUser(userId: "a-user-id")); ``` {% /step %} {% /steps %} ## Use the SDK ### Checking a Feature Flag/Gate After the SDK is initialized, check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (equivalent to `return false;`) by default. ```dart if (Statsig.checkGate("new_homepage_design")) { // Gate is on, show new home page } else { // Gate is off, show old home page } ``` ### Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional advanced user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates, but returns an entire JSON object you configure on the server, from which you can fetch typed parameters. For example: ```dart var config = Statsig.getConfig("awesome_product_details"); // The 2nd parameter is the default value to be used in case the given parameter name does not exist on // the Dynamic Config object. This can happen when there is a typo, or when the user is offline and the // value has not been cached on the client. var itemName = config.get("product_name", "Awesome Product v1"); var price = config.get("price", 10.0); var shouldDiscount = config.get("discount", false); ``` ### Getting a Layer/Experiment **Layers/Experiments** support running A/B/n experiments. Two APIs are available, but [layers](/experiments/layers-overview) are recommended to enable quicker iterations with parameter reuse. ```dart // Values via getLayer var layer = Statsig.getLayer("user_promo_experiments"); var promoTitle = layer.getString("title", "Welcome to Statsig!"); var discount = layer.getDouble("discount", 0.1); // or, via getExperiment var titleExperiment = Statsig.getExperiment("new_user_promo_title"); var priceExperiment = Statsig.getExperiment("new_user_promo_price"); var promoTitle = titleExperiment.get("title", "Welcome to Statsig!"); var discount = priceExperiment.get("discount", 0.1); ``` ### Logging an Event After setting up a Feature Gate or an Experiment, you can track custom events to see how your new features or different experiment groups affect those events. Call the Log Event API for the event, and optionally provide a value and metadata object to be logged with the event: ```dart // Provide a doubleValue argument for number values Statsig.logEvent("purchase", doubleValue: 2.99, metadata: {"item_name": "remove_ads"}); // or provide a stringValue argument for string values Statsig.logEvent("login", stringValue: "a.user@mail.com"); ``` ## Parameter Stores Parameter Stores hold a set of parameters for your mobile app. These parameters can be remapped dynamically from a static value to a Statsig entity (Feature Gates, Experiments, and Layers), so you can decouple your code from the configuration in Statsig. Refer to [Param Stores](/client/concepts/parameter-stores) for more information. ```dart var homepageStore = Statsig.getParameterStore("homepage"); var title = homepageStore.get("title", "Welcome"); var shouldShowUpsell = homepageStore.get("upsell_upgrade_now", false); ``` ## Statsig User You need to provide a StatsigUser object to check or get your configurations. Pass as much information as possible to take advantage of advanced gate and config conditions. The `userID` field is required in most cases to provide a consistent experience for a given user (refer to [logged-out experiments](/guides/first-device-level-experiment) for how to run experiments for logged-out users). In addition to `userID`, the following top-level fields are available on StatsigUser: `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion`. You can also pass any key-value pairs in an object or dictionary to the `custom` field to create targeting based on them. After the user logs in or their attributes change, call `updateUser` with the updated `userID` and any other updated user attributes: ```dart await Statsig.updateUser(StatsigUser("a_new_user")); ``` ### Private attributes To prevent sensitive user PII from being logged, use the `privateAttributes` field on the StatsigUser object. Statsig uses any attribute set in `privateAttributes` only for evaluation and targeting, and removes it from logs before sending them to the server. For example, if you have feature gates that should only pass for users with emails ending in "@statsig.com" but don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ## Statsig Options {% parameter name="initTimeout" type="int" %} Used to decide how long (in seconds) the Statsig client waits for the initial network request to respond before calling the completion block. The Statsig client returns either cached values (if any) or default values if `checkGate`/`getConfig`/`getExperiment` is called before the initial network request completes. If you always want to wait for the latest values fetched from the Statsig server, set this to `0` so the network request doesn't time out. {% /parameter %} {% parameter name="api" type="string" %} The endpoint to use for all SDK network requests. Don't override this unless you have another API that implements the Statsig API endpoints. {% /parameter %} {% parameter name="environment" type="string" %} The environment tier to evaluate rules for the current user. Default is empty, which is the same as "production." On non-production tiers, events don't make it into downstream pulse results. {% /parameter %} ## Lifecycle and advanced usage ## Shutting Statsig Down To save data and battery usage and prevent logged events from being dropped, the SDK keeps event logs in client cache and flushes them periodically. Because of this, some events may not have been sent when your app shuts down. To ensure all logged events are flushed or saved locally, call `shutdown` when your app is closing: ```dart await Statsig.shutdown(); ``` ## Flutter lifecycle hooks Mobile apps can be closed by the operating system when they're no longer in the foreground. To ensure all events are logged before the operating system closes the app, add Statsig to the app lifecycle events. This flushes all pending events when an app state change is detected. Something like the following: ```dart // An example App Lifecycle Observer class StatsigLifecycleObserver extends WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) async { switch (state) { case AppLifecycleState.resumed: await Statsig.initialize('client-sdk-key'); break; case AppLifecycleState.paused: await Statsig.shutdown(); break; } } } ``` Then in your app code, add this observer to the WidgetsBinding instance. ```dart @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(StatsigLifecycleObserver()); } ``` ## FAQs ### How do I run experiments for logged out users? Go to the guide on [device level experiments](/client/concepts/persistent_assignment). ================================================================================ # .NET Client SDK URL: https://docs.statsig.com/client/DotNet Updated: 2026-07-11 ================================================================================ ## Set up the SDK {% steps %} {% step title="Install the SDK" %} The package is hosted on [Nuget](https://www.nuget.org/packages/Statsig/). Install it from your Visual Studio's Nuget package manager, or through the NuGet CLI: ```shell nuget install Statsig ``` {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later on in a gate or experiment. ```csharp using Statsig; using Statsig.Client; await StatsigClient.Initialize( "client-sdk-key", new StatsigUser { UserID = "some_user_id", Email = "user@email.com" }, new StatsigOptions(new StatsigEnvironment(EnvironmentTier.Development)) // optional, use when needed to customize certain behaviors ); ``` {% /step %} {% /steps %} ## Use the SDK ### Checking a Feature Flag/Gate Now that your SDK is initialized, let's check a [**Feature Gate**](/feature-flags/overview). Feature Gates can be used to create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (think `return false;`) by default. ```csharp if (StatsigClient.CheckGate("new_homepage_design")) { // Gate is on, show new home page } else { // Gate is off, show old home page } ``` ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional advanced user targeting. To send a different set of values (strings, numbers, etc.) to clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates, but you get a complete JSON object you can configure on the server and fetch typed parameters from it. For example: ```csharp DynamicConfig config = StatsigClient.GetConfig("awesome_product_details"); // The 2nd parameter is the default value to be used in case the given parameter name does not exist on // the Dynamic Config object. This can happen when there is a typo, or when the user is offline and the // value has not been cached on the client. string itemName = config.Get<string>("product_name", "Awesome Product v1"); double price = config.Get<double>("price", 10.0); bool shouldDiscount = config.Get<bool>("discount", false); ``` ### Getting a Layer/Experiment **Layers/Experiments** let you run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for quicker iterations with parameter reuse. ```csharp // Values via getLayer Layer layer = StatsigClient.GetLayer("user_promo_experiments"); var promoTitle = layer.Get("title", "Welcome to Statsig!"); var discount = layer.Get("discount", 0.1); // or, via getExperiment DynamicConfig titleExperiment = StatsigClient.GetExperiment("new_user_promo_title"); DynamicConfig priceExperiment = StatsigClient.GetExperiment("new_user_promo_price"); var promoTitle = titleExperiment.Get("title", "Welcome to Statsig!"); var discount = priceExperiment.Get("discount", 0.1); ... double price = msrp * (1 - discount); ``` ### Logging an Event After setting up a Feature Gate or Experiment, you may want to track custom events to see how new features or experiment groups affect those events. Call the Log Event API for the event. You can also provide a value and metadata object to be logged with the event: ```csharp StatsigClient.LogEvent( "add_to_cart", "SKU_12345", new Dictionary<string, string>() { { "price", "9.99" }, { "item_name", "diet_coke_48_pack" } } ); ``` ## Statsig User You need to provide a StatsigUser object to check/get your configurations. Pass as much information as possible to take advantage of advanced gate and config conditions. Most of the time, the `userID` field is needed to provide a consistent experience for a given user (refer to [logged-out experiments](/guides/first-device-level-experiment) to understand how to correctly run experiments for logged-out users). Besides `userID`, Statsig also supports `email`, `ip`, `userAgent`, `country`, `locale` and `appVersion` as top-level fields on StatsigUser. In addition, you can pass any key-value pairs in an object/dictionary to the `custom` field and create targeting based on them. After the user logs in or their attributes change, call `updateUser` with the updated `userID` and any other updated user attributes: ```csharp // if you want to update the existing user, or change to a different user, call updateUser await StatsigClient.UpdateUser( new StatsigUser { UserID = "new_user_id", Email = "new_user@email.com" }, ); ``` ### Private Attributes To prevent sensitive user PII from being logged, use the `privateAttributes` field on the StatsigUser object. Statsig uses attributes set in `privateAttributes` only for evaluation and targeting, then removes them from any logs before sending them to the server. For example, if you have feature gates that pass only for users with emails ending in "@statsig.com" but don't want to log your users' email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ## Statsig Options `Initialize()` takes an optional parameter `options` in addition to `sdkKey` and `user` that you can provide to customize the Statsig client. {% parameter name="environment" type="StatsigEnvironment" %} Set environment variables that apply to all users in the session for targeting purposes. Commonly used to set the environment tier, e.g. `new StatsigEnvironment(EnvironmentTier.Staging)`. {% /parameter %} {% parameter name="ClientRequestTimeoutMs" type="int" %} Maximum milliseconds to wait for `/initialize` before proceeding with cached/default values. {% /parameter %} {% parameter name="PersistentStorageFolder" type="string" %} Directory path for persistent storage of cached values and logs. {% /parameter %} ## Shutting Statsig Down The SDK keeps event logs in the client cache and flushes them periodically to save data and battery usage. Because of this, some events may not have been flushed when your app shuts down. To ensure all logged events are flushed or saved locally, shut down Statsig when your app is closing: ```csharp StatsigClient.Shutdown(); ``` ## FAQs #### How do I run experiments for logged out users? Refer to [Device-level experiments](/sdks/user#device-level-experiments). ================================================================================ # Expo Client SDK URL: https://docs.statsig.com/client/Expo Updated: 2026-07-11 ================================================================================ ## Set up the SDK {% steps %} {% step title="Install the SDK" %} ```shell npx expo install @statsig/expo-bindings ``` ### Peer Dependencies The `@statsig/expo-bindings` package has peer dependencies which may also need to be installed if they aren't already in your project. ```shell npx expo install expo-device expo-application @react-native-async-storage/async-storage ``` {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later on in a gate or experiment. The setup for an Expo environment is very similar to a plain [React environment](/client/React). The only difference is that you need to use the Expo specific `StatsigProviderExpo`. This automatically switches out the storage layer used by the SDK, utilizing [AsyncStorage](https://github.com/react-native-async-storage) instead of LocalStorage (which isn't available in RN environments). ```tsx import { StatsigProviderExpo, useFeatureGate } from "@statsig/expo-bindings"; function Content() { const gate = useFeatureGate("a_gate"); return <Text>Reason: {gate.details.reason}</Text>; // Reason: Network or NetworkNotModified } function App() { return ( <StatsigProviderExpo sdkKey={YOUR_CLIENT_KEY} user={{ userID: "a-user" }} options={{ environment: { tier: "development" } }} // (Optional) loadingComponent={<Text>Loading...</Text>} > <Content /> </StatsigProviderExpo> ); } ``` {% /step %} {% /steps %} ## Use the SDK You can get an instance of the StatsigClient to check gates, experiments, dynamic configs, layers, and log events. ```jsx import { useStatsigClient } from "@statsig/expo-bindings"; const { client } = useStatsigClient(); ``` The methods you can call on the client are listed below. ### Checking a Feature Flag/Gate Now that your SDK is initialized, let's check a [**Feature Gate**](/feature-flags/overview). Feature Gates can be used to create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (think `return false;`) by default. You can evaluate a gate by getting the client with the `useStatsigClient` hook, and then calling `checkGate` ```tsx const { client } = useStatsigClient(); return ( <div>Gate is {client.checkGate('check_user') ? 'passing' : 'failing'}.</div> ); ``` ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional advanced user targeting. To send a different set of values (strings, numbers, etc.) to clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates, but you get a complete JSON object you can configure on the server and fetch typed parameters from it. For example: You can get a DynamicConfig value by getting the client with the `useStatsigClient` hook, and then calling `getConfig` ```tsx const { client } = useStatsigClient(); const config = client.getConfig('app_properties'); return ( <div>{config.get('title', 'Default Title')}</div> ); ``` ### Getting a Layer/Experiment **Layers/Experiments** let you run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for quicker iterations with parameter reuse. You can access the experiment variant and parameters for the user by getting the client with the `useStatsigClient` hook, and then calling `getExperiment`. ```tsx const { client } = useStatsigClient(); const experiment = client.getExperiment('headline_test'); return ( <div>Headline Parameter: {experiment.get('headline', 'Default')}.</div> ); ``` You can access layers and layer parameters for the user by getting the client with the `useStatsigClient` hook, and then calling `getLayer`. ```tsx const { client } = useStatsigClient(); const layer = client.getLayer('homepage_layer'); return ( <div>Headline Parameter: {layer.get('hero_text', 'Welcome')}.</div> ); ``` ### Logging an Event After setting up a Feature Gate or Experiment, you may want to track custom events to see how new features or experiment groups affect those events. Call the Log Event API for the event. You can also provide a value and metadata object to be logged with the event. Get the client with the `useStatsigClient` hook, then call `logEvent`: ```tsx const { client } = useStatsigClient(); return <button onClick={() => client.logEvent("button_click")}>Click Me</button> ``` ### Flushing Logged Events `flush()` sends queued events immediately. Use `shutdown()` when your app is exiting. ```tsx import { Button } from 'react-native'; import { useStatsigClient } from '@statsig/expo-bindings'; const { client } = useStatsigClient(); return ( <button onClick={async () => { await client.flush(); }} > Flush Events </button> ); ``` ## Loading State Depending on your setup, you may want to wait for the latest values before checking a gate or experiment. If you are using `StatsigProviderExpo`, pass in a `loadingComponent` prop to display a loading state while the SDK is initializing. If you are using the `useClientAsyncInitExpo` hook, check the `isLoading` prop to determine if the SDK is still loading. {% tabs %} {% tab title="StatsigProviderExpo" %} ```tsx export function App() { const loadingComponent = <div>Loading...</div>; return ( <StatsigProviderExpo ... loadingComponent={loadingComponent} // <- Pass in the loading component > <YourComponent /> </StatsigProviderExpo> ); } ``` {% /tab %} {% tab title="useClientAsyncInitExpo" %} ```tsx export function App() { const { client, isLoading } = useClientAsyncInitExpo(...); if (isLoading) { return <div>Loading...</div>; } return ( <StatsigProviderExpo client={client}> <YourComponent /> </StatsigProviderExpo> ); } ``` {% /tab %} {% /tabs %} ## Lifecycle & Advanced Usage ## Shutting Statsig Down The SDK keeps event logs in the client cache and flushes them periodically to save data and battery usage. Because of this, the SDK may not have flushed some events when your app shuts down. To ensure all logged events are flushed or saved locally, shut down Statsig when your app is closing: ```tsx import { useEffect } from 'react'; import { useStatsigClient } from '@statsig/expo-bindings'; const { client } = useStatsigClient(); useEffect(() => { return () => { void client.shutdown(); }; }, [client]); ``` ## Advanced ### Expo Without React In some scenarios, you may need to use the `StatsigClient` outside the React component tree. For example, in background tasks or notification handlers, use the Expo-specific `StatsigClientExpo`. ```tsx import { StatsigClientExpo } from '@statsig/expo-bindings'; const myClient = new StatsigClientExpo( YOUR_CLIENT_KEY, { userID: "a-user" } ); await myClient.initializeAsync(); if (myClient.checkGate("my_gate")) { // do something cool } ``` ### Synchronous Storage with MMKV If you are using [MMKV](https://github.com/mrousavy/react-native-mmkv) in your project and prefer it over the default ([AsyncStorage](https://github.com/react-native-async-storage)), provide your own `StorageProvider` through `StatsigOptions`. ```tsx import { MMKV } from "react-native-mmkv"; import { StorageProvider } from "@statsig/client-core"; import { StatsigProviderExpo } from "@statsig/expo-bindings"; function App() { const [storageProvider] = useState<StorageProvider>(() => { const mmkv = new MMKV(); return { isReady: () => true, isReadyResolver: () => null, getProviderName: () => "MMKV", getAllKeys: () => mmkv.getAllKeys(), getItem: (key: string) => mmkv.getString(key) ?? null, setItem: (key: string, value: string) => mmkv.set(key, value), removeItem: (key: string) => mmkv.delete(key), }; }); return ( <StatsigProviderExpo sdkKey={YOUR_CLIENT_KEY} user={{ userID: "a-user" }} options={{ storageProvider, // <- Passed into StatsigOptions }} > <Text>...</Text> </StatsigProviderExpo> ); } ``` ## Debugging ### Network Issues Some users have reported `ERROR: A networking error occured during POST request` messages when first initializing Statsig. Statsig resolved this issue in SDK versions after 3.1.0. Upgrade your SDK to resolve the error. If the error persists, reach out through [Slack](https://statsig.com/slack). ================================================================================ # React Native Client SDK URL: https://docs.statsig.com/client/ReactNative Updated: 2026-07-11 ================================================================================ ## Set up the SDK {% steps %} {% step title="Install the SDK" %} ## Installation Statsig uses a multi-package strategy. Install both the Statsig client and the React Native specific bindings. {% codetabs %} ```shell NPM npm install @statsig/react-native-bindings ``` ```shell Yarn yarn add @statsig/react-native-bindings ``` {% /codetabs %} ### Peer Dependencies The `@statsig/react-native-bindings` package has peer dependencies which may also need to be installed if they aren't already in your project. {% codetabs %} ```shell NPM npm install react-native-device-info @react-native-async-storage/async-storage ``` ```shell Yarn yarn add react-native-device-info @react-native-async-storage/async-storage ``` {% /codetabs %} {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later on in a gate or experiment. ## React Native + React Specific Setup The setup for a ReactNative environment is very similar to a plain [React environment](/client/React). The only difference is that you need to use the ReactNative specific `StatsigProviderRN`. This automatically switches out the storage layer used by the SDK, utilizing [AsyncStorage](https://github.com/react-native-async-storage) instead of LocalStorage (which isn't available in RN environments). ```tsx import { StatsigProviderRN, useFeatureGate, } from "@statsig/react-native-bindings"; function Content() { const gate = useFeatureGate("a_gate"); // Reason: Network or NetworkNotModified return ( <View> <Text>Value: {gate.value ? "Pass" : "Fail"}</Text> <Text>Reason: {gate.details.reason}</Text> </View> ); } function App() { return ( <StatsigProviderRN sdkKey={YOUR_CLIENT_KEY} user={{ userID: "a-user" }} loadingComponent={<Text>Loading...</Text>} > <Content /> </StatsigProviderRN> ); } ``` {% /step %} {% /steps %} ## Use the SDK Get an instance of the StatsigClient to check gates, experiments, dynamic configs, layers, and log events. ```jsx import { useStatsigClient } from "@statsig/react-native-bindings"; const { client } = useStatsigClient(); ``` The available methods are listed in the sections below. ### Checking a Feature Flag/Gate Now that your SDK is initialized, let's check a [**Feature Gate**](/feature-flags/overview). Feature Gates can be used to create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (think `return false;`) by default. You can evaluate a gate by getting the client with the `useStatsigClient` hook, and then calling `checkGate` ```tsx const { client } = useStatsigClient(); return ( <div>Gate is {client.checkGate('check_user') ? 'passing' : 'failing'}.</div> ); ``` ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional advanced user targeting. To send a different set of values (strings, numbers, etc.) to clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates, but you get a complete JSON object you can configure on the server and fetch typed parameters from it. For example: You can get a DynamicConfig value by getting the client with the `useStatsigClient` hook, and then calling `getConfig` ```tsx const { client } = useStatsigClient(); const config = client.getConfig('app_properties'); return ( <div>{config.get('title', 'Default Title')}</div> ); ``` ### Getting a Layer/Experiment **Layers/Experiments** let you run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for quicker iterations with parameter reuse. You can access the experiment variant and parameters for the user by getting the client with the `useStatsigClient` hook, and then calling `getExperiment`. ```tsx const { client } = useStatsigClient(); const experiment = client.getExperiment('headline_test'); return ( <div>Headline Parameter: {experiment.get('headline', 'Default')}.</div> ); ``` You can access layers and layer parameters for the user by getting the client with the `useStatsigClient` hook, and then calling `getLayer`. ```tsx const { client } = useStatsigClient(); const layer = client.getLayer('homepage_layer'); return ( <div>Headline Parameter: {layer.get('hero_text', 'Welcome')}.</div> ); ``` ### Logging an Event After setting up a Feature Gate or Experiment, you may want to track custom events to see how new features or different experiment groups affect those events. Call the Log Event API for the event. You can also provide a value and metadata object to be logged with the event. Get the client with the `useStatsigClient` hook, then call `logEvent`: ```tsx const { client } = useStatsigClient(); return <button onClick={() => client.logEvent("button_click")}>Click Me</button> ``` ### Flushing Logged Events `flush()` sends queued events immediately. Use `shutdown()` when your app is exiting. ```tsx import { Button } from 'react-native'; import { useStatsigClient } from '@statsig/react-native-bindings'; const { client } = useStatsigClient(); return ( <Button title="Flush Events" onPress={async () => { await client.flush(); }} /> ); ``` ## Loading State Depending on your setup, you may want to wait for the latest values before checking a gate or experiment. If you are using `StatsigProviderRN`, pass in a `loadingComponent` prop to display a loading state while the SDK initializes. If you are using the `useClientAsyncInitRN` hook, check the `isLoading` prop to determine if the SDK is still loading. {% tabs %} {% tab title="StatsigProviderRN" %} ```tsx export function App() { const loadingComponent = <div>Loading...</div>; return ( <StatsigProviderRN ... loadingComponent={loadingComponent} // <- Pass in the loading component > <YourComponent /> </StatsigProviderRN> ); } ``` {% /tab %} {% tab title="useClientAsyncInitRN" %} ```tsx export function App() { const { client, isLoading } = useClientAsyncInitRN(...); if (isLoading) { return <div>Loading...</div>; } return ( <StatsigProviderRN client={client}> <YourComponent /> </StatsigProviderRN> ); } ``` {% /tab %} {% /tabs %} ## Lifecycle & Advanced Usage ## Shutting Statsig Down The SDK keeps event logs in the client cache and flushes them periodically to save data and battery usage. Because of this, the SDK may not have flushed some events when your app shuts down. To ensure all logged events are flushed or saved locally, shut down Statsig when your app is closing: ```tsx import { useEffect } from 'react'; import { useStatsigClient } from '@statsig/react-native-bindings'; const { client } = useStatsigClient(); useEffect(() => { return () => { void client.shutdown(); }; }, [client]); ``` ## Advanced ### StatsigClient Outside the Component Tree In some scenarios, you may need to use the `StatsigClient` outside the React component tree. For example, in background tasks or notification handlers, use the RN-specific `StatsigClientRN`. ```tsx import { StatsigClientRN } from '@statsig/react-native-bindings'; const myClient = new StatsigClientRN( YOUR_CLIENT_KEY, { userID: "a-user" } ); await myClient.initializeAsync(); if (myClient.checkGate("my_gate")) { // do something cool } ``` To access the StatsigClient instance created by the StatsigProvider outside the component tree, use the `StatsigClientRN.instance()` method. This returns the first StatsigClient instance created. If you have multiple instances, pass in the SDK key to get a specific instance. ```tsx // Inside the component tree function App() { return <StatsigProviderRN sdkKey={YOUR_CLIENT_KEY} user={{ userID: "a-user" }}> <Text>...</Text> </StatsigProviderRN> } // Outside the component tree const client = StatsigClientRN.instance(); // get the first created instance const client = StatsigClientRN.instance(YOUR_CLIENT_KEY); // get a specific instance by SDK key ``` ### Synchronous Storage with MMKV Because LocalStorage is unavailable in React Native environments, the SDK prefetches all Statsig cache entries during initialization by default. If you are using MMKV in your project and prefer it over the default (AsyncStorage), provide your own `StorageProvider` through `StatsigOptions`. ```tsx import { MMKV } from "react-native-mmkv"; import { StorageProvider } from "@statsig/client-core"; import { StatsigProviderRN } from '@statsig/react-native-bindings'; function App() { const [storageProvider] = useState<StorageProvider>(() => { const mmkv = new MMKV(); return { isReady: () => true, isReadyResolver: () => null, getProviderName: () => "MMKV", getAllKeys: () => mmkv.getAllKeys(), getItem: (key: string) => mmkv.getString(key) ?? null, setItem: (key: string, value: string) => mmkv.set(key, value), removeItem: (key: string) => mmkv.delete(key), }; }); return ( <StatsigProviderRN sdkKey={YOUR_CLIENT_KEY} user={{ userID: "a-user" }} options={{ storageProvider, // <- Passed into StatsigOptions }} > <Text>...</Text> </StatsigProviderRN> ); } ``` ================================================================================ # Statsig in Angular URL: https://docs.statsig.com/client/Angular Updated: 2026-07-11 ================================================================================ ## Set Up the SDK {% steps %} {% step title="Install the SDK" %} {% codetabs %} ```bash npm npm install @statsig/angular-bindings ``` ```bash yarn yarn add @statsig/angular-bindings ``` {% /codetabs %} {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later on in a gate or experiment. The Statsig Angular bindings package provides a `StatsigService` that you can inject into your components. The way you provide and inject this service can vary depending on how you structure your app. ```ts import { STATSIG_INIT_CONFIG } from '@statsig/angular-bindings'; const StatsigConfig = { sdkKey: "client-KEY", user: {}, // initial user object options: {...} // optional } ``` ### Using app config ```ts // app.config.ts import { ApplicationConfig } from '@angular/core'; import { STATSIG_INIT_CONFIG } from '@statsig/angular-bindings'; export const appConfig: ApplicationConfig = { providers: [ { provide: STATSIG_INIT_CONFIG, useValue: StatsigConfig, }, ], }; //main.ts import { AppComponent } from './app/app.component'; import { appConfig } from './app/app.config'; bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err), ); ``` ### Using app module ```ts // app.module.ts import { StatsigService } from '@statsig/angular-bindings'; import { AppComponent } from './app.component'; @NgModule({ declarations: [AppComponent], imports: [], providers: [ { provide: STATSIG_INIT_CONFIG, useValue: StatsigConfig, }, ], bootstrap: [AppComponent], }) export class AppModule {} ``` {% /step %} {% /steps %} ## Use the SDK After providing the Statsig config token, you can inject the service into a component or another service and use it. ```ts // example.component.ts import { Component } from '@angular/core'; import { StatsigService } from '@statsig/angular-bindings'; @Component({ selector: 'app-example', template: `...`, }) export class ExampleComponent { constructor(private statsigService: StatsigService) {} } ``` ### Checking a Feature Flag/Gate After the SDK is initialized, check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (equivalent to `return false;`) by default. ```ts // feature-gate.component.ts import { CommonModule } from '@angular/common'; import { Component, OnInit } from '@angular/core'; import { StatsigService } from '@statsig/angular-bindings'; @Component({ standalone: true, selector: 'app-feature-gate', imports: [CommonModule], template: `<div *ngIf="isFeatureEnabled">Feature is enabled!</div>`, }) export class FeatureGateComponent implements OnInit { isFeatureEnabled = false; constructor(private statsigService: StatsigService) {} ngOnInit(): void { this.isFeatureEnabled = this.statsigService.checkGate('feature_gate_name'); } } ``` ### Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional advanced user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates, but returns an entire JSON object you configure on the server, from which you can fetch typed parameters. For example: ```ts // dynamic-config.component.ts import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { StatsigService } from '@statsig/angular-bindings'; @Component({ standalone: true, selector: 'app-dynamic-config', imports: [CommonModule], template: `<div *ngIf="configValue">Config Value: {{ configValue }}</div>`, }) export class DynamicConfigComponent implements OnInit { configValue: string | null = null; constructor(private statsigService: StatsigService) {} ngOnInit(): void { const dynamicConfig = this.statsigService.getDynamicConfig('config_name'); this.configValue = dynamicConfig.get('key', 'default_value'); } } ``` ### Getting a Layer/Experiment **Layers/Experiments** support running A/B/n experiments. Two APIs are available, but [layers](/experiments/layers-overview) are recommended to enable quicker iterations with parameter reuse. ```ts // experiment.component.ts import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { StatsigService } from '@statsig/angular-bindings'; @Component({ standalone: true, selector: 'app-experiment', imports: [CommonModule], template: `<div *ngIf="experimentValue">Experiment Value: {{ experimentValue }}</div>`, }) export class ExperimentComponent implements OnInit { experimentValue: string | null = null; constructor(private statsigService: StatsigService) {} ngOnInit(): void { const experiment = this.statsigService.getExperiment('experiment_name'); this.experimentValue = experiment.get('experiment_key', 'default'); } } ``` ```ts // layer.component.ts import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { StatsigService } from '@statsig/angular-bindings'; @Component({ standalone: true, selector: 'app-layer', imports: [CommonModule], template: `<div *ngIf="layerValue">Layer Value: {{ layerValue }}</div>`, }) export class LayerComponent implements OnInit { layerValue: string | null = null; constructor(private statsigService: StatsigService) {} ngOnInit(): void { const layer = this.statsigService.getLayer('layer_name'); this.layerValue = layer.get('layer_key', 'default_layer_value'); } } ``` ### Logging an Event After setting up a Feature Gate or an Experiment, you can track custom events to see how your new features or different experiment groups affect those events. Call the Log Event API for the event, and optionally provide a value and metadata object to be logged with the event: ```ts import { Component, OnInit } from '@angular/core'; import { CommonModule } from '@angular/common'; import { StatsigService } from '@statsig/angular-bindings'; @Component({ standalone: true, selector: 'app-log-event', imports: [CommonModule], template: ` <button (click)="logUserAction()">Click Me</button> <p *ngIf="message">{{ message }}</p> `, }) export class LogEventComponent { message: string | null = null; constructor(private statsigService: StatsigService) {} logUserAction(): void { this.statsigService.logEvent('UserClickedButton', 1, { buttonColor: 'blue', buttonText: 'Click Me', }); this.message = 'Event logged: UserClickedButton'; } } ``` ### Flushing logged events `flush()` sends queued events immediately. Use `shutdown()` when your app is exiting. ```ts import { Component } from '@angular/core'; import { CommonModule } from '@angular/common'; import { StatsigService } from '@statsig/angular-bindings'; @Component({ standalone: true, selector: 'app-flush-events', imports: [CommonModule], template: ` <button (click)="flushEvents()">Flush Events</button> <p *ngIf="message">{{ message }}</p> `, }) export class FlushEventsComponent { message: string | null = null; constructor(private statsigService: StatsigService) {} async flushEvents(): Promise<void> { await this.statsigService.getClient().flush(); this.message = 'Queued events flushed'; } } ``` ## Updating user properties (e.g., Login) Update user properties when the user logs in and a userID is assigned, or when new properties are identified. Statsig fetches new values for all gate, experiment, and config evaluations in response. Call `updateUserAsync` from the service: ```ts import { Component } from '@angular/core'; import { StatsigService } from '@statsig/angular-bindings'; @Component({ selector: 'app-user-update', template: ` <button (click)="updateUser()">Update User</button> `, }) export class UserUpdateComponent { constructor(private statsigService: StatsigService) {} updateUser(): void { const user = { userID: 'user-1234', email: 'user@example.com', // Add other user properties here }; this.statsigService.updateUserAsync(user) .then(() => { console.log('User updated successfully'); }) .catch((error) => { console.error('Error updating user:', error); }); } } ``` ## Loading State Depending on your setup, you may want to wait for the latest values before checking a gate or experiment. Use the `isLoading` observable to determine whether the SDK is still loading and display a loading state accordingly. ```ts import { CommonModule } from '@angular/common'; import { Component, OnInit } from '@angular/core'; import { StatsigService } from '@statsig/angular-bindings'; @Component({ selector: 'app-loading-state', imports: [CommonModule], template: ` <div *ngIf="isLoading | async; else content"> <p>Loading...</p> </div> <ng-template #content> <p>Content loaded successfully!</p> </ng-template> `, }) export class LoadingStateComponent implements OnInit { isLoading = this.statsigService.isLoading$; constructor(private statsigService: StatsigService) {} ngOnInit(): void { // other initialization logic here } } ``` ## Shutting Statsig Down To save data and battery usage and prevent logged events from being dropped, the SDK keeps event logs in client cache and flushes them periodically. Because of this, some events may not have been sent when your app shuts down. To ensure all logged events are flushed or saved locally, call `shutdown` when your app is closing: ```ts import { StatsigService } from '@statsig/angular-bindings'; export class ShutdownStatsigComponent { constructor(private statsigService: StatsigService) {} ngOnDestroy(): void { void this.statsigService.getClient().shutdown(); } } ``` ## Angular directives ### Statsig Module To use the directives, you need to import the `StatsigModule` in your Angular module. ```ts // app.module.ts import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; import { StatsigModule } from '@statsig/angular-bindings'; @NgModule({ declarations: [AppComponent], imports: [BrowserModule, StatsigModule], bootstrap: [AppComponent] }) export class AppModule {} ``` ### Check gate directive The `stgCheckGate` directive conditionally displays content in your Angular templates based on the evaluation of a Feature Gate from Statsig. It listens for updates to the feature gate's value and dynamically adds or removes content based on whether the gate is enabled. ```ts // feature-demo.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-feature-demo', template: ` <div *stgCheckGate="'new_feature_gate'"> <p>This content will only show if the 'new_feature_gate' is enabled.</p> </div> `, }) export class FeatureDemoComponent {} ``` ## Session Replay By including the [`@statsig/session-replay`](https://www.npmjs.com/package/@statsig/session-replay) package in your project, you can automatically capture and log user sessions as videos. This is useful for debugging and understanding user behavior. Read more about [Session Replay](/session-replay/overview). ```ts import { STATSIG_INIT_CONFIG } from '@statsig/angular-bindings'; import { StatsigSessionReplayPlugin } from '@statsig/session-replay'; const StatsigConfig = { sdkKey: "client-KEY", user: { userID: 'a-user' }, options: { plugins: [ new StatsigSessionReplayPlugin() ] } } ``` ## Web analytics / auto capture By including the [`@statsig/web-analytics`](https://www.npmjs.com/package/@statsig/web-analytics) package in your project, you can automatically capture common web events such as clicks and page views. For more information on filtering events, enabling console log capture, and other configuration options, refer to the [Web Analytics Configuration](/webanalytics/overview#event-filtering-and-console-configuration) documentation. ```ts import { STATSIG_INIT_CONFIG } from '@statsig/angular-bindings'; import { StatsigAutoCapturePlugin } from '@statsig/web-analytics'; const StatsigConfig = { sdkKey: "client-KEY", user: { userID: 'a-user' }, options: { plugins: [ new StatsigAutoCapturePlugin() ] } } ``` ================================================================================ # Roku Client SDK URL: https://docs.statsig.com/client/Roku Updated: 2026-07-11 ================================================================================ ## Set up the SDK {% steps %} {% step title="Install the SDK" %} Start by downloading [the GitHub repository](https://github.com/statsig-io/roku-sdk). Roku doesn't have a package manager, so you copy the implementation files from this repository to integrate Statsig in your Roku app. You will need the following files: ``` Statsig -- components -- statsigsdk -- StatsigTask.brs -- StatsigTask.xml -- source -- DynamicConfig.brs -- Statsig.brs -- StatsigClient.brs -- StatsigUser.brs ``` The library consists of two main parts: * **source/Statsig**: an object used by SceneGraph components that connects a StatsigClient with the background StatsigTask. * **components/StatsigTask**: a SceneGraph Task that runs in the background to batch events and fetch values from Statsig servers. The `components` folder contains SceneGraph components and the `source` folder contains BrightScript files. All of these files must be included in their respective folders of the application. If you change the file paths, you must update the file references in `StatsigTask.xml` and include those references in your main XML file. {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later on in a gate or experiment. To initialize the SDK, first integrate the SDK files into your application. Include `StatsigClient.brs`, `StatsigUser.brs`, `DynamicConfig.brs`, and `Statsig.brs`: ```xml <script type="text/brightscript" uri="pkg:/source/Statsig.brs" /> <script type="text/brightscript" uri="pkg:/source/StatsigClient.brs" /> <script type="text/brightscript" uri="pkg:/source/StatsigUser.brs" /> <script type="text/brightscript" uri="pkg:/source/DynamicConfig.brs" /> ``` Next, initialize the library in your `init()` function and add a listener for when gates/experiments have been fetched: ```xml <!-- in component xml --> <StatsigTask id="statsigTask" /> ``` ```brightscript statsigTask = m.top.findNode("statsigTask") statsigTask.observeField("initializeValues", "onStatsigReady") m.statsig = Statsig(statsigTask) user = StatsigUser() user.setUserID("456") m.statsig.initialize("<STATSIG_CLIENT_SDK_KEY>", user) ``` For more information on all of the user fields you can use, refer to the [StatsigUser docs](/sdks/user). Before the SDK has loaded the updated values, all APIs will return default values (false for gates, empty configs and experiments). To implement a callback handler for Statsig being ready, and tell the SDK to load the updated values in the `onStatsigReady` function observed above: ```brightscript function onStatsigReady() as void m.statsig.load() // Check gates, log events, check experiments, etc gate = m.statsig.checkGate("gate_id") config = m.statsig.getConfig("config_id") experiment = m.statsig.getExperiment("experiment_id") m.statsig.logEvent("event_name", "event_value", {metadata: "event_metadata"}) end function ``` To update the user, `m.statsig.updateUser(newUser)` triggers the same `onStatsigReady` callback after the new gate/config/experiment values have been fetched from Statsig servers. {% /step %} {% /steps %} ================================================================================ # Unity SDK URL: https://docs.statsig.com/client/Unity Updated: 2026-07-11 ================================================================================ ## Set up the SDK {% steps %} {% step title="Install the SDK" %} The project is on [GitHub](https://github.com/statsig-io/unity-sdk). Add the package to your Unity project through **Package Manager** > **Add package from Git URL**, then enter `https://github.com/statsig-io/unity-sdk.git` (include the `.git` part in the URL). {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later on in a gate or experiment. **Make sure all API calls to `Statsig` are made from the main thread.** Operations that take longer, such as network requests, are made asynchronously and won't block the main thread. ```csharp using StatsigUnity; await Statsig.Initialize( "client-sdk-key", new StatsigUser { UserID = "some_user_id", Email = "user@email.com" }, new StatsigOptions // optional parameters to customize your Statsig client, see "Statsig Options" section below to see details on available options { EnvironmentTier = EnvironmentTier.Development, InitializeTimeoutMs = 5000, } ); ``` {% /step %} {% /steps %} ## Use the SDK ### Checking a Feature Flag/Gate Now that your SDK is initialized, let's check a [**Feature Gate**](/feature-flags/overview). Feature Gates can be used to create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (think `return false;`) by default. ```csharp if (Statsig.CheckGate("show_new_loading_screen")) { // Gate is on, show new loading screen } else { // Gate is off, show old loading screen } ``` ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional advanced user targeting. To send a different set of values (strings, numbers, etc.) to clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates, but you get a complete JSON object you can configure on the server and fetch typed parameters from it. For example: ```csharp var config = Statsig.GetConfig("awesome_product_details"); // The 2nd parameter is the default value to be used in case the given parameter name does not exist on // the Dynamic Config object. This can happen when there is a typo, or when the user is offline and the // value has not been cached on the client. string itemName = config.Get<string>("product_name", "Awesome Product v1"); double price = config.Get<double>("price", 10.0); bool shouldDiscount = config.Get<bool>("discount", false); ``` ### Getting a Layer/Experiment **Layers/Experiments** let you run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for quicker iterations with parameter reuse. ```csharp // Values via getLayer var layer = Statsig.GetLayer("user_promo_experiments"); var promoTitle = layer.Get("title", "Welcome to Statsig!"); var discount = layer.Get("discount", 0.1); // or, via getExperiment var titleExperiment = Statsig.GetExperiment("new_user_promo_title"); var priceExperiment = Statsig.GetExperiment("new_user_promo_price"); var promoTitle = titleExperiment.Get("title", "Welcome to Statsig!"); var discount = priceExperiment.Get("discount", 0.1); ... double price = msrp * (1 - discount); ``` ### Logging an Event After setting up a Feature Gate or Experiment, you may want to track custom events to see how new features or different experiment groups affect those events. Call the Log Event API for the event. You can also provide a value and metadata object to be logged with the event: ```csharp Statsig.LogEvent( "purchase", "new_player_pack", new Dictionary<string, string>() { { "price", "9.99" } } ); ``` ## Statsig User You need to provide a StatsigUser object to check/get your configurations. Pass as much information as possible to take advantage of advanced gate and config conditions. Most of the time, the `userID` field is needed to provide a consistent experience for a given user (refer to [logged-out experiments](/guides/first-device-level-experiment) to understand how to correctly run experiments for logged-out users). Besides `userID`, Statsig also supports `email`, `ip`, `userAgent`, `country`, `locale` and `appVersion` as top-level fields on StatsigUser. In addition, you can pass any key-value pairs in an object/dictionary to the `custom` field and create targeting based on them. After the user logs in or their attributes change, call `updateUser` with the updated `userID` and any other updated user attributes: ```csharp // if you want to update the existing user, or change to a different user, call UpdateUser. // The API makes a network request to fetch values for the new user. await Statsig.UpdateUser( new StatsigUser { UserID = "new_user_id", Email = "new_user@email.com" }, ); ``` ### Private Attributes To prevent sensitive user PII from being logged, use the `privateAttributes` field on the StatsigUser object. Statsig uses attributes set in `privateAttributes` only for evaluation and targeting, then removes them from any logs before sending them to the server. For example, if you have feature gates that pass only for users with emails ending in "@statsig.com" but don't want to log your users' email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ## Statsig Options `Initialize()` takes an optional parameter `options` in addition to `clientKey` and `user` that you can provide to customize the Statsig client. {% parameter name="EnvironmentTier" type="EnvironmentTier" %} Set the environment tier for the user. Values: `Production | Development | Staging`. Users with `null` or `Production` tier are included in Pulse metrics by default. {% /parameter %} {% parameter name="InitializeTimeoutMs" type="int" %} Maximum milliseconds `Statsig.Initialize()` will wait before proceeding with cached/default values. {% /parameter %} {% parameter name="LoggingIntervalMs" type="int" %} Interval for periodically flushing logging events to the Statsig backend. {% /parameter %} {% parameter name="LoggingBufferMaxSize" type="int" %} Maximum number of events the logger batches before flushing. {% /parameter %} ## Shutting Statsig Down The SDK keeps event logs in client cache and flushes them periodically to save data and battery usage. Because of this, some events may not have been flushed when your app shuts down. To ensure all logged events are flushed or saved locally, shut down Statsig when your app is closing: ```csharp // This function is async, and you can choose to await for it so that we make sure all the // events that are yet to be flushed get flushed await Statsig.Shutdown(); ``` ## FAQs #### How do I run experiments for logged out users? Refer to [Device-level experiments](/guides/first-device-level-experiment). ================================================================================ # On Device Client SDKs URL: https://docs.statsig.com/client/onDeviceOverview Updated: 2026-07-11 ================================================================================ ## How on-device eval SDKs work Statsig's client-side On-Device Eval SDKs provide an alternate client-side architecture. The definition of each experiment or gate is kept in memory on the device, which allows faster evaluation when the user object changes frequently. (On regular client SDKs, a frequently changing user object requires re-initialization.) The trade-off is that config definitions are exposed on the device. Use the traditional client SDKs unless you have specific requirements that prevent doing so. If you choose to use the On-Device Eval SDKs, contact the Statsig team first to understand the privacy risks. ## Alternatives An alternative to On-Device Eval SDKs is the [Local Eval Adapter](/client/concepts/local-eval-adapter), which lets you evaluate locally for only a subset of your experiments and gates that must be available at startup, rather than exposing all configs in your project. ================================================================================ # Swift On Device Evaluation SDK URL: https://docs.statsig.com/client/swiftOnDeviceEvaluationSDK Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Statsig's normal (remote evaluation) SDKs are recommended for most client applications. Understand the use case and privacy risks by reading the [On-Device Eval SDK overview](/client/onDeviceOverview). On-device evaluation SDKs are for Enterprise & Pro Tier only. {% /callout %} These SDKs use a different paradigm than their precomputed counterparts: [JS](/client/javascript-sdk), [Android](/client/Android), [iOS](/client/iosClientSDK). On-device evaluation SDKs behave more like Server SDKs. Rather than requiring a user up front, you can check gates, configs, and experiments for any set of user properties, because the SDK downloads a complete representation of your project and evaluates checks in real time. ### Pros * No network request is needed when changing user properties: check the gate/config/experiment locally * You can bring your own CDN or synchronously initialize with a preloaded project definition * Lower latency to download configs cached at the edge, rather than evaluated for a given user (which can't be cached as much) ### Cons * The entire project definition is available client-side: the names and configurations of all experiments and feature flags accessible by your client key are exposed. Refer to [client key with server permission best practices](/access-management/api-keys#client-keys-with-server-permissions). * Payload size is larger than what is required for the traditional SDKs * Evaluation performance is slightly slower: rather than looking up the value, the SDK must evaluate targeting conditions and an allocation decision * Does not support ID list segments with > 1000 IDs * Does not support IP or User Agent based checks (Browser Version/Name, OS Version/Name, IP, Country) ## Set up the SDK {% steps %} {% step title="Install the SDK" %} To use the SDK in your project, you must add Statsig as a dependency. {% codetabs %} ```swift Swift Package Manager // In your Xcode, select File > Swift Packages > Add Package Dependency // and enter the URL https://github.com/statsig-io/swift-on-device-evaluations-sdk.git. // // You can also include it directly in your project's Package.swift. // Find out the latest release version on our GitHub page: // https://github.com/statsig-io/swift-on-device-evaluations-sdk/releases dependencies: [ // see the latest version on https://github.com/statsig-io/swift-on-device-evaluations-sdk/releases .package(url: "https://github.com/statsig-io/swift-on-device-evaluations-sdk.git", .upToNextMinor("X.Y.Z")), ], //... targets: [ .target( name: "YOUR_TARGET", dependencies: ["StatsigOnDeviceEvaluations"] ) ], ``` ```ruby CocoaPods # If you are using CocoaPods, our pod name is 'StatsigOnDeviceEvaluations', # and you can include the following line to your Podfile: use_frameworks! target 'TargetName' do //... pod 'StatsigOnDeviceEvaluations', '~> X.Y.Z' end # Find the latest versions by searching cocoapods.org or on Github: # https://github.com/statsig-io/swift-on-device-evaluations-sdk/releases ``` {% /codetabs %} {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you want to target later on in a gate or experiment. {% callout type="warning" %} For On-Device Evaluation, add the **"Allow Download Config Specs"** scope. Client keys, by default, can't download the project definition for on-device evaluation. While client keys are safe to include, Server and Console keys should always be kept private. {% /callout %} {% accordion title="How to add the scope" %} {% tabs %} {% tab title="New SDK Keys" %} When creating a new client key, select **"Allow Download Config Specs"** ![Add DCS Scope to New Key](/images/local-eval/new-keys.png) {% /tab %} {% tab title="Existing SDK Keys" %} To add the scope to an existing key, under **Project Settings** → **API Keys** → **Client API Keys**, select **Actions** → **Edit Scopes**, and select **"Allow Download Config Specs"**, then **Save**. ![Add DCS Scope to Existing Key](/images/local-eval/existing-keys.png) {% /tab %} {% /tabs %} {% /accordion %} {% codetabs %} ```swift Async (Swift) import StatsigOnDeviceEvaluations // (optional) Configure the SDK if needed let opts = StatsigOptions() opts.environment.tier = "staging" Statsig.shared.initialize("client-sdk-key", options: opts) { err in if let err = err { print("Error \(err)") } } // or, create your own instance let myStatsigInstance = Statsig() myStatsigInstance.initialize("client-sdk-key", options: opts) { err in if let err = err { print("Error \(err)") } } ``` ```objective-c Objective C StatsigOptions *options = [StatsigOptions new]; StatsigEnvironment *env = [StatsigEnvironment new]; env.tier = @"staging"; options.environment = env; [[Statsig sharedInstance] initializeWithSDKKey:@"client-sdk-key" options:options completion:^(NSError * _Nullable error) { if (error != nil) { NSLog(@"Error %@", error); } }]; ``` ```swift Synchronous (Swift) import StatsigOnDeviceEvaluations // (optional) Configure the SDK if needed let opts = StatsigOptions() opts.environment.tier = "staging" let specs: NSString = "..." // JSON string of your configurations let error = client.initializeSync("client-sdk-key", initialSpecs: specs) if let err = error { print("Error \(err)") } ``` {% /codetabs %} You can configure the SDK to use cached values if they are newer than the local file. This is useful when you ship your app with a local file but want it used only for the first session. In the following example, the SDK uses `initialSpecs` only when there is no cache or the cache is older than `initialSpecs`. ```swift let options = StatsigOptions() options.useNewerCacheValuesOverProvidedValues = true client.initializeSync( "client-sdk-key", initialSpecs: specs, options: options ) ``` {% callout type="note" %} You can get a copy of your current specs data by visiting: `https://api.statsigcdn.com/v1/download_config_specs/client-{YOUR_SDK_KEY}.json` {% /callout %} {% /step %} {% /steps %} ## Working with the SDK ### Checking a feature flag/gate After the SDK is initialized, check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (think `return false;`) by default. {% codetabs %} ```swift Swift // Simple Pass/Fail check let isPassing: Bool = Statsig.shared.checkGate("my_gate", user) // or, the verbose FeatureGate check let gate = Statsig.shared.getFeatureGate("my_gate", user) print(gate.evaluationDetails.reason) // "Network" | "Cache" | "Unrecognized" let isPassing: Bool = gate.value ``` ```objective-c Objective C BOOL isPassing = [[Statsig sharedInstance] checkGate:@"my_gate" forUser:user]; ``` {% /codetabs %} ### Reading a dynamic config Feature Gates are useful for simple on/off switches with optional advanced user targeting. If you need to send a different set of values (strings, numbers, and so on) to your clients based on specific user attributes (such as country), use **Dynamic Configs**. The API is similar to Feature Gates, but you receive an entire JSON object you can configure on the server and fetch typed parameters from. ```swift let config = Statsig.shared.getDynamicConfig("my_dynamic_config", user) let name: String? = config.value["product_name"] as? String let price: Double? = config.value["price"] as? Double ``` ### Getting a layer/experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available, but [layers](/experiments/layers-overview) are recommended because they enable quicker iterations with parameter reuse. ```swift // Getting values via getLayer let layer = Statsig.shared.getLayer("my_layer", user) let name: String? = layer.getValue(param: "product_name", fallback: "Unknown") as? String // or, using getExperiment let experiment = Statsig.shared.getExperiment("my_experiment", user) let name: String? = experiment.value["product_name"] as? String let price: Double? = experiment.value["price"] as? Double ``` ### Logging an event After you set up a Feature Gate or an Experiment, you can track custom events to see how your features or experiment groups affect those events. Call the Log Event API for the event, and optionally provide a value and an object of metadata to be logged with the event: ```swift let event = StatsigEvent( eventName: "add_to_cart", value: "SKU_1234", metadata: [ "price": "9.99", "item_name": "CoolProduct" ] ) Statsig.shared.logEvent(event, user) ``` ### Code examples Working sample apps are available in the repository: * [Swift & Objective C Examples](https://github.com/statsig-io/swift-on-device-evaluations-sdk/tree/main/Sample/App/Examples/OnDeviceEvaluations) ## Statsig user Provide a `StatsigUser` object to check or get your configurations. Pass as much information as possible to use advanced gate and config conditions. The `userID` field is usually required to provide a consistent experience for a given user. Refer to [logged-out experiments](/guides/first-device-level-experiment) to understand how to correctly run experiments for logged-out users. In addition to `userID`, `StatsigUser` has the following top-level fields: `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion`. You can also pass any key-value pairs in an object or dictionary to the `custom` field to create targeting based on them. After the user logs in or their attributes change, call `updateUser` with the updated `userID` and any other updated user attributes: {% codetabs %} ```swift Swift let user = StatsigUser( userID: "a-user", customIDs: ["EmployeeID": "an-employee"], email: "user@statsig.io", ip: "58.84.239.246", userAgent: "Mozilla/5.0 (iPad; CPU OS 13_4_1....", country: "NZ", locale: "en_NZ", appVersion: "3.2.1", custom: ["Level": "9001"], privateAttributes: ["SensitiveInfo": "shhh"] ) ``` ```objective-c Objective C StatsigUser *user = [StatsigUser userWithUserID:@"a-user"]; user.customIDs = @{ @"EmployeeID": @"an-employee" }; user.email = @"user@statsig.io"; user.ip = @"58.84.239.246"; user.userAgent = @"Mozilla/5.0 (iPad; CPU OS 13_4_1...."; user.country = @"NZ"; user.locale = @"en_NZ"; user.appVersion = @"3.2.1"; [user.custom setString:@"9001" forKey:@"Level"]; [user.privateAttributes setString:@"shhh" forKey:@"SensitiveInfo"]; ``` {% /codetabs %} ### Private attributes The `StatsigUser` object has a `privateAttributes` field: an object/dictionary for setting private user attributes. Any attribute set in `privateAttributes` is used only for evaluation and targeting, and Statsig removes it from any logs before sending them to the server. For example, if you have feature gates that should pass only for users with emails ending in "@statsig.com" but don't want to log users' email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ### Setting a global user To avoid passing the user object to every evaluation call, you can set a global user. The global user is used for all evaluations unless otherwise specified. ```swift Statsig.shared.setGlobalUser(myGlobalUser) Statsig.shared.checkGate("my_gate") // <- Will use myGlobalUser Statsig.shared.checkGate("my_gate", StatsigUser(userID: "user-123")) // <- Will NOT use myGlobalUser ``` {% callout type="note" %} Unlike precomputed evaluation SDKs, the on-device evaluation SDK doesn't have an `updateUser` method since it evaluates gates/configs/experiments in real-time for any user object you pass in. {% /callout %} ## Statsig options Configure the SDK behavior by passing a `StatsigOptions` object during initialization. {% parameter name="eventQueueMaxSize" type="Int" %} The maximum number of events to batch before flushing logs to the server. {% /parameter %} {% parameter name="eventQueueInternalMs" type="Double" %} How frequently to flush queued logs. {% /parameter %} {% parameter name="eventLoggingAPI" type="String" %} The API where all events are sent. {% /parameter %} {% parameter name="configSpecAPI" type="String" %} The API used to fetch the latest configurations. {% /parameter %} {% parameter name="environment" type="StatsigEnvironment" %} An object you can use to set environment variables that apply to all of your users in the same session and Statsig uses for targeting purposes. {% /parameter %} ## Lifecycle and advanced usage ## Shutting Statsig down The SDK keeps event logs in a client cache and flushes them periodically to save battery usage and prevent dropped events. Some events may not be sent when your app shuts down before a flush occurs. To flush or save all logged events locally, call shutdown when your app is closing: {% codetabs %} ```swift Swift Statsig.shared.shutdown { err in if let err = err { print("An error occurred during Statsig shutdown: \(err)") } else { print("Statsig shutdown successfully") } } ``` ```objective-c Objective C [[Statsig sharedInstance] shutdownWithCompletion:^(NSError * _Nullable error) { if (error != nil) { NSLog(@"An error occurred during Statsig shutdown: %@", error); } else { NSLog(@"Statsig shutdown successfully"); } }]; ``` {% /codetabs %} ## Post-init syncing ### From network By default, the SDK syncs only during initialization. To re-sync after initialization, call the `Statsig.update` method. This triggers a network call to fetch the latest changes from the server. ```swift Statsig.shared.update { err in if let err = err { print("Statsig update error: \(err)") } } ``` ### From a local file If you maintain your own copy of the "specs" json, pass it to `Statsig.updateSync()`. This skips the network call and uses the provided specs instead. ```swift let result = Statsig.shared.updateSync(updatedSpecs: myJsonData) ``` ### Scheduled polling To have the SDK regularly poll for updates, start the polling task with `Statsig.scheduleBackgroundUpdates()`. This calls `Statsig.update` internally to fetch the latest changes from the network. ```swift let pollingTask = Statsig.shared.scheduleBackgroundUpdates() // Defaults to 1 hour interval // or, specify a custom interval let pollingTask = Statsig.shared.scheduleBackgroundUpdates(intervalSeconds: 300) // and, if you need to cancel it later pollingTask?.cancel() ``` ## Local overrides Override the values returned by the Statsig SDK for unit testing or for enabling features in local development. To set up local overrides, pass an instance of `LocalOverrideAdapter` to the SDK through the `StatsigOptions` object. {% callout type="note" %} You can write your own override adapter by implementing the [`OverrideAdapter`](https://github.com/statsig-io/swift-on-device-evaluations-sdk/blob/main/Sources/StatsigOnDeviceEvaluations/OverrideAdapter.swift) protocol and passing it in instead. {% /callout %} {% codetabs %} ```swift Swift let user = StatsigUser(userID: "a-user") let overrides = LocalOverrideAdapter() // Override a gate overrides.setGate(user, FeatureGate.create("local_override_gate", true)) // Override a dynamic config (Similar for Layer and Experiment) overrides.setDynamicConfig(user, DynamicConfig.create("local_override_dynamic_config", ["foo": "bar"])) let opts = StatsigOptions() opts.overrideAdapter = overrides Statsig.shared.initialize(YOUR_SDK_KEY, options: opts) { _ in let gate = Statsig.shared.getFeatureGate("local_override_gate", user) print("Result: \(gate.value) (\(gate.evaluationDetails.reason))") } ``` ```objective-c Objective C StatsigUser *user = [StatsigUser userWithUserID:@"a-user"]; LocalOverrideAdapter *overrides = [LocalOverrideAdapter new]; // Override a gate [overrides setGateForUser:user gate:[FeatureGate createWithName:@"local_override_gate" andValue:true]]; // Override a dynamic config (Similar for Layer and Experiment) [_overrides setDynamicConfigForUser:user config:[DynamicConfig createWithName:@"local_override_dynamic_config" andValue:@{@"foo": @"bar"}]]; StatsigOptions *options = [StatsigOptions new]; options.overrideAdapter = overrides; [[Statsig sharedInstance] initializeWithSDKKey:YOUR_SDK_KEY options:options completion:^(NSError * _Nullable error) { FeatureGate *gate = [[Statsig sharedInstance] getFeatureGate:@"local_override_gate" forUser:user options:nil]; NSLog(@"Result: %d (%@)", gate.value, gate.evaluationDetails.reason); }]; ``` {% /codetabs %} ## FAQs {% accordion-group %} {% accordion title="How do I run experiments for logged out users?" %} Refer to the guide on [device level experiments](/guides/first-device-level-experiment). {% /accordion %} {% /accordion-group %} ## Additional resources * [On-Device Evaluation SDK Overview](/client/onDeviceOverview) * [Client Keys with Server Permissions](/access-management/api-keys#client-keys-with-server-permissions) * [Debugging SDK Evaluations](/sdks/debugging) ================================================================================ # Android On Device Evaluation SDK URL: https://docs.statsig.com/client/androidOnDeviceEvaluationSDK Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Statsig's normal (remote evaluation) SDKs are recommended for most client applications. Understand the use case and privacy risks by reading the [On-Device Eval SDK overview](/client/onDeviceOverview). On-device evaluation SDKs are for Enterprise & Pro Tier only. {% /callout %} These SDKs use a different paradigm than their precomputed counterparts ([JS](/client/javascript-sdk), [Android](/client/Android), [iOS](/client/iosClientSDK)). Rather than requiring a user upfront, you can check gates/configs/experiments for any set of user properties, because the SDK downloads a complete representation of your project and evaluates checks in real time. ### Pros * No network request needed when changing user properties: check the gate/config/experiment locally * Can bring your own CDN or synchronously initialize with a preloaded project definition * Lower latency to download configs cached at the edge, rather than evaluated for a given user (which can't be cached as much) ### Cons * The entire project definition is available client-side: your client key exposes the names and configurations of all experiments and feature flags. Refer to [client key with server permission best practices](/access-management/api-keys#client-keys-with-server-permissions). * Payload size is strictly larger than what is required for the traditional SDKs * Evaluation performance is slightly slower - rather than looking up the value, the SDK must actually evaluate targeting conditions and an allocation decision * Does not support ID list segments with > 1000 IDs * Does not support IP or User Agent based checks (Browser Version/Name, OS Version/Name, IP, Country) ## Set Up the SDK {% steps %} {% step title="Install the SDK" %} You can install the SDK using JitPack. Go to https://jitpack.io/#statsig-io/android-local-eval for the latest version and installation steps. {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later on in a gate or experiment. {% callout type="warning" %} For On-Device Evaluation, you'll need to add the **"Allow Download Config Specs"** scope. Client keys can't download the project definition for on-device evaluation by default. While client keys are safe to include, Server and Console keys should always be kept private. {% /callout %} {% accordion title="How to add the scope" %} {% tabs %} {% tab title="New SDK Keys" %} When creating a new client key, select **"Allow Download Config Specs"** ![Add DCS Scope to New Key](/images/local-eval/new-keys.png) {% /tab %} {% tab title="Existing SDK Keys" %} To add the scope to an existing key, under **Project Settings** → **API Keys** → **Client API Keys**, select **Actions** → **Edit Scopes**, and select **"Allow Download Config Specs"**, then **Save**. ![Add DCS Scope to Existing Key](/images/local-eval/existing-keys.png) {% /tab %} {% /tabs %} {% /accordion %} {% codetabs %} ```java Java import com.statsig.androidlocalevalsdk.*; // ... android.app.Application app = // ref to your Application instance StatsigOptions opts = new StatsigOptions(); opts.setEnvironmentParameter("tier", "staging"); StatsigClient client = Statsig.INSTANCE.getClient(); client.initializeAsync( app, "client-YOUR_CLIENT_SDK_KEY", new IStatsigCallback() { @Override public void onStatsigInitialize(@NotNull InitializationDetails initDetails) { // Statsig Ready } @Override public void onStatsigInitialize() { // deprecated } }, opts ); // or, create your own instance of StatsigClient StatsigClient client = new StatsigClient(); client.initializeAsync(...); ``` ```kotlin Kotlin import com.statsig.androidlocalevalsdk.* // ... val opts = StatsigOptions() opts.setEnvironmentParameter("tier", "staging") Statsig.client.initializeAsync( application, // ref to your Application instance "client-YOUR_CLIENT_SDK_KEY", object : IStatsigCallback { override fun onStatsigInitialize(initDetails: InitializationDetails) { // Statsig Ready } override fun onStatsigInitialize() { // deprecated } }, opts ) // or, create your own instance of StatsigClient val client = StatsigClient() client.initializeAsync(...) ``` {% /codetabs %} ### Synchronous Initialization ```kotlin import com.statsig.androidlocalevalsdk.* // (optional) Configure the SDK if needed val opts = StatsigOptions() opts.environment.tier = "staging" val specs = "..." // JSON string of your configurations let details = Statsig.client.initializeSync(application, "client-YOUR_CLIENT_SDK_KEY", specs, opts) ``` You can configure the SDK to use cached values if they are newer than the local file. This is useful if you ship your app with a local file, but want it to only be used for the first session. In the following example, the SDK only uses initialSpecs if there is no cache or if the cache is older than initialSpecs. ```kotlin val options = StatsigOptions() options.useNewerCacheValuesOverProvidedValues = true Statsig.client.initializeSync( application, "client-YOUR_CLIENT_SDK_KEY", specs, options ) ``` {% callout type="note" %} You can get a copy of your current specs data by visiting: `https://api.statsigcdn.com/v1/download_config_specs/client-{YOUR_SDK_KEY}.json` {% /callout %} {% /step %} {% /steps %} ## Working with the SDK ### Checking a Feature Flag/Gate Now that your SDK is initialized, let's check a [**Feature Gate**](/feature-flags/overview). Feature Gates can be used to create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (think `return false;`) by default. {% codetabs %} ```java Java StatsigUser user = new StatsigUser("a-user"); StatsigClient client = Statsig.INSTANCE.getClient(); if (client.checkGate(user, "new_homepage_design")) { // Gate is on, show new home page } else { // Gate is off, show old home page } ``` ```kotlin Kotlin val user = StatsigUser("user_id") if (Statsig.client.checkGate(user, "new_homepage_design")) { // Gate is on, show new home page } else { // Gate is off, show old home page } ``` {% /codetabs %} ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional advanced user targeting. To send a different set of values (strings, numbers, etc.) to clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates, but you get a complete JSON object you can configure on the server and fetch typed parameters from it. For example: {% codetabs %} ```java Java StatsigUser user = new StatsigUser("a-user"); StatsigClient client = Statsig.INSTANCE.getClient(); DynamicConfig config = client.getConfig(user, "awesome_product_details"); // The 2nd parameter is the default value to be used in case the given parameter name does not exist on // the Dynamic Config object. This can happen when there is a typo, or when the user is offline and the // value has not been cached on the client. String itemName = config.getString("product_name", "Awesome Product v1"); Double price = config.getDouble("price", 10.0); Boolean shouldDiscount = config.getBoolean("discount", false); ``` ```kotlin Kotlin val user = StatsigUser("user_id") val config = Statsig.client.getConfig(user, "awesome_product_details") // The 2nd parameter is the default value to be used in case the given parameter name does not exist on // the Dynamic Config object. This can happen when there is a typo, or when the user is offline and the // value has not been cached on the client. val itemName = config.getString("product_name", "Awesome Product v1") val price = config.getDouble("price", 10.0) val shouldDiscount = config.getBoolean("discount", false) ``` {% /codetabs %} ### Getting a Layer/Experiment **Layers/Experiments** let you run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for quicker iterations with parameter reuse. {% codetabs %} ```java Java StatsigUser user = new StatsigUser("a-user"); StatsigClient client = Statsig.INSTANCE.getClient(); // Values via getLayer Layer layer = client.getLayer(user, "user_promo_experiments") String promoTitle = layer.getString("title", "Welcome to Statsig!"); Double discount = layer.getDouble("discount", 0.1); // or, via getExperiment DynamicConfig titleExperiment = client.getExperiment(user, "new_user_promo_title"); DynamicConfig priceExperiment = client.getExperiment(user, "new_user_promo_price"); String promoTitle = titleExperiment.getString("title", "Welcome to Statsig!"); Double discount = priceExperiment.getDouble("discount", 0.1); ... Double price = msrp * (1 - discount); ``` ```kotlin Kotlin val user = StatsigUser("user_id") // Values via getLayer val layer = Statsig.client.getLayer(user, "user_promo_experiments") val promoTitle = layer.getString("title", "Welcome to Statsig!") val discount = layer.getDouble("discount", 0.1) // or, via getExperiment val titleExperiment = Statsig.client.getExperiment(user, "new_user_promo_title") val priceExperiment = Statsig.client.getExperiment(user, "new_user_promo_price") val promoTitle = titleExperiment.getString("title", "Welcome to Statsig!") val discount = priceExperiment.getDouble("discount", 0.1) ... val price = msrp * (1 - discount); ``` {% /codetabs %} ### Logging an Event After setting up a Feature Gate or Experiment, you may want to track custom events to see how new features or different experiment groups affect those events. Call the Log Event API for the event. You can also provide a value and metadata object to be logged with the event: {% codetabs %} ```java Java StatsigUser user = new StatsigUser("user_id"); StatsigClient client = Statsig.INSTANCE.getClient(); client.logEvent(user, "purchase", 2.99, Map.of("item_name", "remove_ads")); ``` ```kotlin Kotlin val user = StatsigUser("user_id") Statsig.client.logEvent(user, "purchase", 2.99, mapOf("item_name" to "remove_ads")) ``` {% /codetabs %} ### Code Examples Working sample apps are available in the repository: * [Java & Kotlin Examples](https://github.com/statsig-io/android-local-eval/tree/main/samples) ## Statsig User You need to provide a StatsigUser object to check/get your configurations. Pass as much information as possible to take advantage of advanced gate and config conditions. Most of the time, the `userID` field is needed to provide a consistent experience for a given user (refer to [logged-out experiments](/guides/first-device-level-experiment) to understand how to correctly run experiments for logged-out users). Besides `userID`, Statsig also supports `email`, `ip`, `userAgent`, `country`, `locale` and `appVersion` as top-level fields on StatsigUser. In addition, you can pass any key-value pairs in an object/dictionary to the `custom` field and create targeting based on them. After the user logs in or their attributes change, call `updateUser` with the updated `userID` and any other updated user attributes: {% callout type="note" %} For the Android On-Device Evaluation SDK, you pass the `StatsigUser` object directly into each evaluation method (`checkGate`, `getConfig`, etc.) rather than during initialization. {% /callout %} {% callout type="note" %} Unlike precomputed evaluation SDKs, the on-device evaluation SDK doesn't have an `updateUser` method since it evaluates gates/configs/experiments in real-time for any user object you pass in. {% /callout %} ### Setting a Global User To avoid passing the user object to every evaluation call, set a global user. When checking a gate/experiment/layer, provide `null` for the user to use the global user. The global user is used for all evaluations unless a specific user is provided. ```kotlin Statsig.client.setGlobalUser(myGlobalUser) Statsig.client.checkGate(null, "my_gate") // <- Will use myGlobalUser Statsig.client.checkGate(StatsigUser(userID: "user-123"), "my_gate") // <- Will NOT use myGlobalUser ``` ## Statsig Options Pass an optional `options` parameter in addition to `sdkKey` and `user` during initialization to customize the Statsig client. {% parameter name="configSpecAPI" type="String" %} The endpoint to use for downloading config spec network requests. You shouldn't need to override this (unless you have another API that implements the Statsig API endpoints). {% /parameter %} {% parameter name="eventLoggingAPI" type="String" %} The endpoint to use for log events. You shouldn't need to override this (unless you have another API that implements the Statsig API endpoints). {% /parameter %} {% parameter name="initTimeoutMs" type="Long" %} Milliseconds to wait for the initial network request before calling the completion block. The Statsig client will return either cached values (if any) or default values if checkGate/getConfig/getExperiment is called before the initial network request completes. Set to `0` to wait indefinitely for the latest values. {% /parameter %} {% parameter name="overrideStableID" type="String?" %} Overrides the `stableID` in the SDK that is set for the user. {% /parameter %} {% parameter name="loadCacheAsync" type="Boolean" %} Whether or not the SDK should block on loading saved values from disk. {% /parameter %} {% parameter name="initializeValues" type="Map<String, Any>?" %} Provide the `download_config_specs` response values directly to the Android SDK to synchronously initialize the client. You can get a copy of your current specs data by visiting: `https://api.statsigcdn.com/v1/download_config_specs/client-{YOUR_SDK_KEY}.json` {% /parameter %} {% parameter name="disableDiagnosticsLogging" type="Boolean" %} Prevent the SDK from sending useful debug information to Statsig. {% /parameter %} #### Methods * **setTier | setEnvironmentParameter | getEnvironment** * Used to signal the environment tier for the user. * `setTier` can be PRODUCTION, STAGING or DEVELOPMENT. e.g. passing in a value of `Tier.STAGING` will allow your users to pass any condition that pass for the staging environment tier, and fail any condition that only passes for other environment tiers. * `setEnvironmentParameter` can be used for custom tiers, eg `options.setEnvironmentParameter("tier", "test")` ## Lifecycle & Advanced Usage ## Shutting Statsig Down The SDK keeps event logs in client cache and flushes them periodically to save data and battery usage. Because of this, some events may not have been flushed when your app shuts down. To ensure all logged events are flushed or saved locally, shut down Statsig when your app is closing: {% codetabs %} ```java Java Statsig.shutdown(); ``` ```kotlin Kotlin Statsig.shutdown() ``` {% /codetabs %} ## Post Init Syncing ### From Network By default, the SDK syncs only during initialization. To re-sync after initialization, call the `Statsig.client.updateAsync` method. This triggers a network call to fetch the latest changes from the server. ```kotlin val details = Statsig.client.updateAsync() ``` ### Scheduled Polling To have the SDK regularly poll for updates, start the polling task with `Statsig.client.scheduleBackgroundUpdates()`. The SDK fetches the latest changes from the network on each interval. ```kotlin val pollingTask = Statsig.cloent.scheduleBackgroundUpdates() // Defaults to 1 hour interval // or, specify a custom interval val intervalSeconds = 300 val pollingTask = Statsig.client.scheduleBackgroundUpdates(intervalSeconds) // and, if you need to cancel it later pollingTask?.cancel() ``` ## Using Persistent Evaluations To ensure that a user's variant stays consistent while an experiment is running, regardless of changes to allocation or targeting, implement the `UserPersistentStorageInterface` and set it in `StatsigOptions` when you initialize the SDK. #### Synchronous Persistent Evaluations The `UserPersistentStorageInterface` exposes two methods for synchronous persistent storage, which are called by default when evaluating an experiment. ``` interface UserPersistentStorageInterface { suspend fun load(key: String): PersistedValues fun save(key: String, experimentName: String, data: String) fun delete(key: String, experiment: String) ... } ``` The `key` string is a combination of ID and ID Type: for example, `"123:userID"` or `"abc:stableID"`. The SDK constructs this key and calls `get` and `set` on it by default. Use this interface to persist evaluations synchronously to local storage. If you need an async interface, continue to the next section. #### Asynchronous Persistent Evaluations The `UserPersistentStorageInterface` exposes two methods for asynchronous persistent evaluations. Because the `getExperiment` call is synchronous, you must load the value first and pass it in as `userPersistedValues`. ```kotlin interface UserPersistentStorageInterface { fun loadAsync(key: String, callback: IPersistentStorageCallback) fun save(key: String, experimentName: String, data: String) fun delete(key: String, experiment: String) ... } interface IPersistentStorageCallback { fun onLoaded(values: PersistedValues) } ``` A top-level method is available to load the value for a given user and ID Type: ```kotlin // Asynchronous load values val userPersistedValues = Statsig.client.loadUserPersistedValuesAsync( user: StatsigUser, idType: string, // userID, stableID, customIDxyz, etc callback: IPersistentStorageCallback ); // Synchronous load values val userPersistedvalues = Statsig.client.loadUserPersistedValues( user: StatsigUser, idType: string, // userID, stableID, customIDxyz, etc ) ``` After implementing the `UserPersistentStorageInterface` and setting it on `StatsigOptions`, the call site looks like this: ```kotlin // Asynchronous val callback = object: IPersistentStorageCallback { @override fun onLoaded(values: PersistedValues) { Statsig.getExperiment(user, "sample_experiment", GetExperimentOptions(userPersistedValues = values)) } } val userValues = Statsig.client.loadUserPersistedValuesAsync(user, "userID", callback) // Synchronous val user = StatsigUser(userID = "user123") val userValues = Statsig.client.loadUserPersistedValues(user, 'userID'); const experiment = statsig.getExperiment({userID: "123"}, 'the_allocated_experiment', { userPersistedValues: userValues }); ``` If you are using Java, override only the `loadAsync` function and leave the `load` function empty. ## Local Overrides You can override the values returned by the Statsig SDK. This is useful in unit testing or for enabling features for local development. To set up local overrides, pass an instance of `LocalOverrideAdapter` to the SDK through the `StatsigOptions` object. {% callout type="note" %} It is possible to write your own override adapter. You can implement the [`IOverrideAdapter`](https://github.com/statsig-io/android-local-eval/blob/main/src/main/java/com/statsig/androidlocalevalsdk/IOverrideAdapter.kt) interface and pass that in instead. {% /callout %} {% codetabs %} ```kotlin Kotlin val user = StatsigUser("user-a") val overrides = LocalOverrideAdapter() // Override a gate overrides.setGate(user, "local_override_gate", true) // Override a dynamic config (Similar for Layer and Experiment) val config = DynamicConfig("local_override_dynamic_config", mapOf("key" to "val")) overrides.setConfig(user, config) val opts = StatsigOptions() opts.overrideAdapter = overrides Statsig.client.initializeAsync( app, YOUR_SDK_KEY, callback, opts // <- Pass in StatsigOptions ) ``` ```java Java StatsigUser user = new StatsigUser("a-user"); LocalOverrideAdapter overrides = new LocalOverrideAdapter(); // Override a gate overrides.setGate(user, "local_override_gate", true); // Override a dynamic config (Similar for Layer and Experiment) HashMap<String, Object> configValue = new HashMap<String, Object>() {}; DynamicConfig config = new DynamicConfig( "local_override_dynamic_config", configValue, "local_override", null, new ArrayList<Map<String, String>>(), null ); overrides.setConfig(user, config); StatsigOptions opts = new StatsigOptions(); opts.setOverrideAdapter(overrides); StatsigClient client = Statsig.INSTANCE.getClient(); client.initializeAsync( app, YOUR_SDK_KEY, callback, opts // <- Pass in StatsigOptions ); ``` {% /codetabs %} ## FAQs {% accordion-group %} {% accordion title="How do I run experiments for logged out users?" %} Refer to [Device-level experiments](/guides/first-device-level-experiment). {% /accordion %} {% /accordion-group %} ## Additional Resources * [On-Device Evaluation SDK Overview](/client/onDeviceOverview) * [Client Keys with Server Permissions](/access-management/api-keys#client-keys-with-server-permissions) * [Debugging SDK Evaluations](/sdks/debugging) ================================================================================ # JavaScript On-Device Evaluation Client SDK URL: https://docs.statsig.com/client/jsOnDeviceEvaluationSDK Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Statsig's normal (remote evaluation) SDKs are recommended for most client applications. Understand the use case and privacy risks by reading the [On-Device Eval SDK overview](/client/onDeviceOverview). On-device evaluation SDKs are for Enterprise & Pro Tier only. {% /callout %} These SDKs use a different paradigm than their precomputed counterparts ([JS](/client/javascript-sdk), [Android](/client/Android), [iOS](/client/iosClientSDK)) and behave more like Server SDKs. Rather than requiring a user up front, you can check gates/configs/experiments for any set of user properties, because the SDK downloads a complete representation of your project and evaluates checks in real time. ### Pros * No need for a network request when changing user properties - just check the gate/config/experiment locally * Can bring your own CDN or synchronously initialize with a preloaded project definition * Lower latency to download configs cached at the edge, rather than evaluated for a given user (which can't be cached as much) ### Cons * Entire project definition is available client side: the names and configurations of all experiments and feature flags accessible by your client key are exposed. Refer to [client key with server permission best practices](/access-management/api-keys#client-keys-with-server-permissions). * Payload size is strictly larger than what is required for the traditional SDKs * Evaluation performance is slightly slower - rather than looking up the value, the SDK must actually evaluate targeting conditions and an allocation decision * Does not support ID list segments with > 1000 IDs * Does not support IP or User Agent based checks (Browser Version/Name, OS Version/Name, IP, Country) ## Set Up the SDK {% steps %} {% step title="Install the SDK" %} Install the Statsig SDK using npm, yarn, or jsdelivr: {% codetabs %} ```bash npm npm install @statsig/js-on-device-eval-client ``` ```bash yarn yarn add @statsig/js-on-device-eval-client ``` ```html CDN / <script> <script src="https://cdn.jsdelivr.net/npm/@statsig/js-on-device-eval-client@1/build/statsig-js-on-device-eval-client.min.js"></script> ``` {% /codetabs %} Statsig is hosted on the [jsDelivr](https://www.jsdelivr.com/package/npm/@statsig/js-client) CDN. To access the current primary JavaScript bundle, use: `https://cdn.jsdelivr.net/npm/@statsig/js-client/build/statsig-js-client.min.js` To access specific files/versions: `https://cdn.jsdelivr.net/npm/@statsig/js-client@{version}/build/statsig-js-client.min.js` {% /step %} {% step title="Initialize the SDK" %} Initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later in a gate or experiment. {% callout type="warning" %} For On-Device Evaluation, you'll need to add the **"Allow Download Config Specs"** scope. Client keys, by default, can't download the project definition for on-device evaluation. While client keys are safe to include, Server and Console keys should always be kept private. {% /callout %} {% accordion title="How to add the scope" %} {% tabs %} {% tab title="New SDK Keys" %} When creating a new client key, select **"Allow Download Config Specs"** ![Add DCS Scope to New Key](/images/local-eval/new-keys.png) {% /tab %} {% tab title="Existing SDK Keys" %} To add the scope to an existing key, under **Project Settings** → **API Keys** → **Client API Keys**, select **Actions** → **Edit Scopes**, and select **"Allow Download Config Specs"**, then **Save**. ![Add DCS Scope to Existing Key](/images/local-eval/existing-keys.png) {% /tab %} {% /tabs %} {% /accordion %} ```typescript import { StatsigOnDeviceEvalClient } from '@statsig/js-on-device-eval-client'; const myStatsigClient = new StatsigOnDeviceEvalClient( YOUR_CLIENT_KEY, { environment: {tier: 'development'} } ); // initialize and wait for the latest values await myStatsigClient.initializeAsync(); ``` {% callout type="note" %} In advanced use cases, you may want to Prefetch or Bootstrap (Provide) values for initialization. Go to [Using EvaluationsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter) to learn how to do this. {% /callout %} {% /step %} {% /steps %} ## Working with the SDK ## Setup a StatsigUser To interact with the SDK, create a `StatsigUser` object. The full definition of this object is [here](#statsig-user). ```typescript const myUser = { userID: "a-user", email: "user@statsig.com" }; ``` ### Checking a Feature Flag/Gate Now that your SDK is initialized, check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (think `return false;`) by default. ```typescript if (myStatsigClient.checkGate("new_homepage_design", myUser)) { // Gate is on, show new home page } else { // Gate is off, show old home page } ``` ### Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional advanced user targeting. To send different values (strings, numbers, and similar types) to your clients based on specific user attributes such as country, use **Dynamic Configs**. The API is similar to Feature Gates but returns a full JSON object you can configure on the server and fetch typed parameters from. For example: ```typescript const dynamicConfig = myStatsigClient.getDynamicConfig("awesome_product_details", myUser); const itemName = dynamicConfig.value["product_name"] ?? "Some Fallback"; const price = dynamicConfig.value["price"] ?? 10.0; if (dynamicConfig.value["is_discount_enabled"] === true) { // apply some discount logic } ``` ### Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Statsig offers two APIs, but recommends [layers](/experiments/layers-overview) to enable quicker iterations with parameter reuse. ```typescript // Values via getLayer const layer = myStatsigClient.getLayer("user_promo_experiments", myUser); const promoTitle = layer.get("title") ?? "Welcome to Statsig!"; const discount = layer.get("discount") ?? 0.1; // or, via getExperiment const titleExperiment = myStatsigClient.getExperiment("new_user_promo_title", myUser); const priceExperiment = myStatsigClient.getExperiment("new_user_promo_price", myUser); const promoTitle = titleExperiment.value["title"] ?? "Welcome to Statsig!"; const discount = priceExperiment.value["discount"] ?? 0.1; ``` ### Logging an Event After you set up a Feature Gate or an Experiment, you can track custom events to measure how your new features or experiment groups affect those events. Call the Log Event API for the event, and optionally provide a value and/or a metadata object to log together with the event: ```typescript import type { StatsigEvent } from '@statsig/client-core'; // log a simple event myStatsigClient.logEvent('my_simple_event'); // or, include more information by using a StatsigEvent object const myEvent: StatsigEvent = { eventName: 'add_to_cart', value: 'SKU_12345', metadata: { price: '9.99', item_name: 'diet_coke_48_pack', }, }; myStatsigClient.logEvent(myEvent); ``` ### Flushing Logged Events `flush()` sends queued events immediately. Use `shutdown()` when your app is exiting. ```typescript await myStatsigClient.flush(); ``` ### Code Examples Working sample apps are available in the repository: * [JavaScript & TypeScript Examples](https://github.com/statsig-io/js-client-monorepo/tree/main/samples) ## Statsig User Provide a StatsigUser object to check or get your configurations. Pass as much information as possible to take advantage of advanced gate and config conditions. The `userID` field is usually required to provide a consistent experience for a given user. (Refer to [logged-out experiments](/guides/first-device-level-experiment) to understand how to correctly run experiments for logged-out users.) Besides `userID`, the `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion` fields are available as top-level fields on StatsigUser. You can also pass any key-value pairs in an object/dictionary to the `custom` field to create targeting based on them. {% callout type="note" %} For the JavaScript On-Device Evaluation SDK, you pass the `StatsigUser` object into each evaluation method (`checkGate`, `getConfig`, etc.) rather than during initialization. {% /callout %} {% callout type="note" %} Unlike precomputed evaluation SDKs, the on-device evaluation SDK doesn't have an `updateUser` method since it evaluates gates/configs/experiments in real-time for any user object you pass in. {% /callout %} ## Client Event Emitter You can subscribe to StatsigClientEvents (not to be confused with [StatsigEvent](#logging-an-event)). These events occur at various stages while using the Statsig client. Subscribe to specific events by specifying the StatsigClientEvent name, or subscribe to all events using the wildcard token `'*'`. ```typescript import type { AnyStatsigClientEvent, StatsigClientEvent, StatsigClientEventCallback, } from '@statsig/client-core'; const onAnyClientEvent = (event: AnyStatsigClientEvent) => { console.log("Any Client Event", event); }; const onLogsFlushed = (event: StatsigClientEvent<'logs_flushed'>) => { console.log("Logs", event.events); }; // subscribe to an individual StatsigClientEvent myStatsigClient.on('logs_flushed', onLogsFlushed); // or, subscribe to all StatsigClientEvents myStatsigClient.on('*', onAnyClientEvent); // then later, unsubscribe from the events myStatsigClient.off('logs_flushed', onLogsFlushed); myStatsigClient.off('*', onAnyClientEvent); ``` The full list of events and descriptions can be found [here](https://github.com/statsig-io/js-client-monorepo/blob/main/packages/client-core/src/StatsigClientEventEmitter.ts). ## Statsig Options Configure the SDK's behavior by passing a StatsigOptions object during initialization. {% parameter name="api" type="string" %} The API to use for all SDK network requests. You don't need to override this unless you have another API that implements the Statsig API endpoints. {% /parameter %} {% parameter name="logEventUrl" type="string" %} The URL used to flush queued events via a POST request. Takes precedence over `StatsigOptions.api`. {% /parameter %} {% parameter name="logEventBeaconUrl" type="string" %} The URL used to flush queued events via `window.navigator.sendBeacon` (web only). Takes precedence over `StatsigOptions.api`. {% /parameter %} {% parameter name="downloadConfigSpecsUrl" type="string" %} The URL used to fetch your latest Statsig specifications. Takes precedence over `StatsigOptions.api`. {% /parameter %} {% parameter name="environment" type="StatsigEnvironment" %} An object you can use to set environment variables that apply to all of your users in the same session. {% /parameter %} {% parameter name="overrideStableID" type="string" %} Overrides the auto-generated stableID that is set for the device. {% /parameter %} {% parameter name="logLevel" type="LogLevel" %} How much information is allowed to be printed to the console. {% /parameter %} {% parameter name="dataAdapter" type="SpecsDataAdapter" %} Implementing this type allows customization of the initialization. See [Using SpecsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter) to learn more. {% /parameter %} {% parameter name="networkTimeoutMs" type="number" %} The maximum amount of time (in milliseconds) that any network request can take before timing out. {% /parameter %} {% parameter name="loggingBufferMaxSize" type="number" %} The maximum number of events to batch before flushing logs to Statsig. {% /parameter %} {% parameter name="loggingIntervalMs" type="number" %} How often (in milliseconds) to flush logs to Statsig. {% /parameter %} {% parameter name="overrideAdapter" type="OverrideAdapter" %} An implementor of `OverrideAdapter`, used to alter evaluations before they are returned to the caller of a check API (checkGate/getExperiment etc). {% /parameter %} ## Manual Exposures {% callout type="warning" %} Manual logging is error-prone and can often introduce issues like uneven exposures, which compromise experiment results. {% /callout %} You can query your gates/experiments without triggering an exposure and manually log the exposures later. ### Gates ```typescript // Check gate with exposure disabled const result = myStatsigClient.checkGate('a_gate_name', { user, disableExposureLog: true }); // Manually log the exposure myStatsigClient.checkGate('a_gate_name', { user }); ``` ### Configs ```typescript // Get config with exposure disabled const config = myStatsigClient.getConfig('a_dynamic_config_name', { user, disableExposureLog: true }); // Manually log the exposure myStatsigClient.getConfig('a_dynamic_config_name', { user }); ``` ### Experiments ```typescript // Get experiment with exposure disabled const experiment = myStatsigClient.getExperiment('an_experiment_name', { user, disableExposureLog: true }); // Manually log the exposure myStatsigClient.getExperiment('an_experiment_name', { user }); ``` ### Layers ```typescript // Get layer with exposure disabled const layer = myStatsigClient.getLayer('a_layer_name', { user, disableExposureLog: true }); const paramValue = layer.get('a_param_name', 'fallback_value'); // Manually log the exposure const layer = myStatsigClient.getLayer('a_layer_name', { user }); const paramValue = layer.get('a_param_name', 'fallback_value'); ``` ## Lifecycle & Advanced Usage ## Shutting Statsig Down To save users' data and battery usage and prevent logged events from being dropped, the SDK keeps event logs in client cache and flushes them periodically. Because of this, some events may not have been sent when your app shuts down. To ensure all logged events are flushed or saved locally, call shutdown when your app is closing. ```typescript await myStatsigClient.shutdown(); ``` ## Data Adapter The `EvaluationsDataAdapter` type outlines how the `StatsigClient` fetches and caches data during initialize and update operations. By default, the `StatsigClient` uses `StatsigEvaluationsDataAdapter`, a Statsig-provided implementor of the `EvaluationsDataAdapter` type. `StatsigEvaluationsDataAdapter` provides ways to fetch data synchronously from Local Storage and asynchronously from Statsig's servers. Go to [Using EvaluationsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter) to learn more and see example usage. ## FAQs {% accordion-group %} {% accordion title="Does the SDK use the browser local storage or cookies? If so, for what purposes?" %} The SDK doesn't use any cookies. It does use local storage for feature targeting and experimentation purposes only. Values for feature gates, dynamic configs, and experiments are cached in local storage as a backup if your website/app can't reach the Statsig server to fetch the latest values. If any events were logged but couldn't be sent to the Statsig server due to issues like network failure, Statsig also saves them in local storage to be sent again when the network restores. {% /accordion %} {% accordion title="How do I run experiments for logged out users?" %} Go to the guide on [device level experiments](/guides/first-device-level-experiment). {% /accordion %} {% /accordion-group %} ## Additional Resources * [On-Device Evaluation SDK Overview](/client/onDeviceOverview) * [Client Keys with Server Permissions](/access-management/api-keys#client-keys-with-server-permissions) * [Using EvaluationsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter) * [Debugging SDK Evaluations](/sdks/debugging) ================================================================================ # React Native On-Device Evaluation URL: https://docs.statsig.com/client/ReactNativeOnDeviceEvaluation Updated: 2026-07-11 ================================================================================ {% callout type="info" %} **Tip:** Get started quickly with one of our [sample apps](https://github.com/statsig-io/js-client-monorepo/tree/main/samples)! {% /callout %} {% callout type="info" %} Statsig's normal (remote evaluation) SDKs are recommended for most client applications. Understand the use case and privacy risks by reading the [On-Device Eval SDK overview](/client/onDeviceOverview). On-device evaluation SDKs are for Enterprise & Pro Tier only. {% /callout %} These SDKs use a different paradigm than their precomputed counterparts ([JS](/client/javascript-sdk), [Android](/client/Android), [iOS](/client/iosClientSDK)). Rather than requiring a user upfront, you can check gates/configs/experiments for any set of user properties, because the SDK downloads a complete representation of your project and evaluates checks in real time. ### Pros * No network request needed when changing user properties: check the gate/config/experiment locally * Can bring your own CDN or synchronously initialize with a preloaded project definition * Lower latency to download configs cached at the edge, rather than evaluated for a given user (which can't be cached as much) ### Cons * The entire project definition is available client-side: your client key exposes the names and configurations of all experiments and feature flags. Refer to [client key with server permission best practices](/access-management/api-keys#client-keys-with-server-permissions). * Payload size is strictly larger than what is required for the traditional SDKs * Evaluation performance is slightly slower - rather than looking up the value, the SDK must actually evaluate targeting conditions and an allocation decision * Does not support ID list segments with > 1000 IDs * Does not support IP or User Agent based checks (Browser Version/Name, OS Version/Name, IP, Country) {% callout type="note" %} Since `@statsig/react-native-bindings-on-device-eval` works in conjunction with `@statsig/js-on-device-eval-client`, documentation on the [JavaScript On-Device Evaluation SDK](/client/jsOnDeviceEvaluationSDK) is also relevant for React Native implementations. {% /callout %} ## Set Up the SDK {% steps %} {% step title="Install the SDK" %} ## Installation Statsig uses a multi-package strategy. Install both the Statsig client and the React Native specific bindings. {% codetabs %} ```shell npm npm install @statsig/react-native-bindings-on-device-eval ``` ```shell yarn yarn add @statsig/react-native-bindings-on-device-eval ``` {% /codetabs %} ### Peer Dependencies The `@statsig/react-native-bindings-on-device-eval` package has peer dependencies which may also need to be installed if they aren't already in your project. {% codetabs %} ```shell npm npm install @react-native-async-storage/async-storage ``` ```shell yarn yarn add @react-native-async-storage/async-storage ``` {% /codetabs %} {% /step %} {% step title="Initialize the SDK" %} Next, initialize the SDK with a client SDK key from the ["API Keys" tab on the Statsig console](https://console.statsig.com/api_keys). These keys are safe to embed in a client application. Along with the key, pass in a [User Object](#statsig-user) with the attributes you'd like to target later on in a gate or experiment. {% callout type="warning" %} For On-Device Evaluation, you'll need to add the **"Allow Download Config Specs"** scope. Client keys can't download the project definition for on-device evaluation by default. While client keys are safe to include, Server and Console keys should always be kept private. {% /callout %} {% accordion title="How to add the scope" %} {% tabs %} {% tab title="New SDK Keys" %} When creating a new client key, select **"Allow Download Config Specs"** ![Add DCS Scope to New Key](/images/local-eval/new-keys.png) {% /tab %} {% tab title="Existing SDK Keys" %} To add the scope to an existing key, under **Project Settings** → **API Keys** → **Client API Keys**, select **Actions** → **Edit Scopes**, and select **"Allow Download Config Specs"**, then **Save**. ![Add DCS Scope to Existing Key](/images/local-eval/existing-keys.png) {% /tab %} {% /tabs %} {% /accordion %} ## React Native Specific Setup To set up Statsig in a React Native component tree, use the RN-specific `StatsigProviderOnDeviceEvalRN`. This automatically switches out the storage layer used by the SDK, using [AsyncStorage](https://github.com/react-native-async-storage) instead of LocalStorage (which is unavailable in RN environments). ```tsx import { StatsigProviderOnDeviceEvalRN, useFeatureGate, } from '@statsig/react-native-bindings-on-device-eval'; function Content() { const gate = useFeatureGate('a_gate'); return <div>Reason: {gate.details.reason}</div>; // Reason: Network or NetworkNotModified } function App() { return ( <StatsigProviderOnDeviceEvalRN sdkKey={YOUR_CLIENT_KEY} loadingComponent={<Text>...</Text>} > <Content /> </StatsigProviderOnDeviceEvalRN> ); } ``` {% /step %} {% /steps %} ## Working with the SDK ## Set up a StatsigUser To interact with the SDK, you will need to create a `StatsigUser` object. The full definition of this object can be found [here](#statsig-user). ```typescript const myUser = { userID: "a-user", email: "user@statsig.com" }; ``` ## React Hooks ### useGateValue or useFeatureGate ```typescript import { useGateValue } from '@statsig/react-native-bindings-on-device-eval'; const gateValue = useGateValue('a_gate', { userID: "a-user" }); // <-- Returns the boolean value if (gateValue) { // } ``` ```typescript import { useFeatureGate } from '@statsig/react-native-bindings-on-device-eval'; const gate = useFeatureGate('a_gate', { userID: "a-user" }); // <-- Returns the FeatureGate object if (gate.value) { // } ``` ### useDynamicConfig ```typescript import { useDynamicConfig } from '@statsig/react-native-bindings-on-device-eval'; function MyComponent() { const config = useDynamicConfig('a_config', { userID: 'a-user' }); // <-- Returns the DynamicConfig object const bgColor = config.value['bg_color'] as string; return <View style={{backgroundColor: bgColor}}></View>; } ``` ### useExperiment ```typescript import { useExperiment } from '@statsig/react-native-bindings-on-device-eval'; function MyComponent() { const experiment = useExperiment('an_experiment', { userID: 'a-user' }); // <-- Returns the Experiment object const bgColor = experiment.value['bg_color'] as string; return <View style={{backgroundColor: bgColor}}></View>; } ``` ### useLayer ```typescript import { useLayer } from '@statsig/react-native-bindings-on-device-eval'; function MyComponent() { const layer = useLayer('a_layer', { userID: 'a-user' }); // <-- Returns the Layer object const bgColor = layer.getValue('bg_color') as string; return <View style={{backgroundColor: bgColor}}></View>; } ``` ### Code Examples Working sample apps are available in the repository: * [React Native Examples](https://github.com/statsig-io/js-client-monorepo/tree/main/samples) ### Logging Custom Events ### Logging an Event After setting up a Feature Gate or Experiment, you may want to track custom events to see how new features or different experiment groups affect those events. Call the Log Event API for the event. You can also provide a value and metadata object to be logged with the event: ```typescript import type { StatsigEvent } from '@statsig/client-core'; // log a simple event myStatsigClient.logEvent('my_simple_event'); // or, include more information by using a StatsigEvent object const myEvent: StatsigEvent = { eventName: 'add_to_cart', value: 'SKU_12345', metadata: { price: '9.99', item_name: 'diet_coke_48_pack', }, }; myStatsigClient.logEvent(myEvent); ``` ### Flushing Logged Events `flush()` sends queued events immediately. Use `shutdown()` when your app is exiting. ```typescript await myStatsigClient.flush(); ``` ## Statsig User You need to provide a StatsigUser object to check/get your configurations. Pass as much information as possible to take advantage of advanced gate and config conditions. Most of the time, the `userID` field is needed to provide a consistent experience for a given user (refer to [logged-out experiments](/guides/first-device-level-experiment) to understand how to correctly run experiments for logged-out users). Besides `userID`, Statsig also supports `email`, `ip`, `userAgent`, `country`, `locale` and `appVersion` as top-level fields on StatsigUser. In addition, you can pass any key-value pairs in an object/dictionary to the `custom` field and create targeting based on them. After the user logs in or their attributes change, call `updateUser` with the updated `userID` and any other updated user attributes: {% callout type="note" %} For the React Native On-Device Evaluation SDK, you pass the `StatsigUser` object directly into each evaluation method (`checkGate`, `getConfig`, etc.) rather than during initialization. {% /callout %} {% callout type="note" %} Unlike precomputed evaluation SDKs, the on-device evaluation SDK doesn't have an `updateUser` method since it evaluates gates/configs/experiments in real-time for any user object you pass in. {% /callout %} ## Client Event Emitter You can subscribe to StatsigClientEvents (not to be confused with [StatsigEvent](#logging-an-event)). These events occur at various stages while using the Statsig client. Subscribe to specific events by specifying the StatsigClientEvent name, or subscribe to all events by using the wildcard token `'*'`. ```typescript import type { AnyStatsigClientEvent, StatsigClientEvent, StatsigClientEventCallback, } from '@statsig/client-core'; const onAnyClientEvent = (event: AnyStatsigClientEvent) => { console.log("Any Client Event", event); }; const onLogsFlushed = (event: StatsigClientEvent<'logs_flushed'>) => { console.log("Logs", event.events); }; // subscribe to an individual StatsigClientEvent myStatsigClient.on('logs_flushed', onLogsFlushed); // or, subscribe to all StatsigClientEvents myStatsigClient.on('*', onAnyClientEvent); // then later, unsubscribe from the events myStatsigClient.off('logs_flushed', onLogsFlushed); myStatsigClient.off('*', onAnyClientEvent); ``` The full list of events and descriptions can be found [here](https://github.com/statsig-io/js-client-monorepo/blob/main/packages/client-core/src/StatsigClientEventEmitter.ts). ## Statsig Options You can configure SDK behavior by passing a `StatsigOptions` object during initialization. {% parameter name="api" type="string" %} The API to use for all SDK network requests. You shouldn't need to override this unless you have another API that implements the Statsig API endpoints. {% /parameter %} {% parameter name="logEventUrl" type="string" %} The URL used to flush queued events via a POST request. Takes precedence over `StatsigOptions.api`. {% /parameter %} {% parameter name="logEventBeaconUrl" type="string" %} The URL used to flush queued events via `window.navigator.sendBeacon` (web only). Takes precedence over `StatsigOptions.api`. {% /parameter %} {% parameter name="downloadConfigSpecsUrl" type="string" %} The URL used to fetch your latest Statsig specifications. Takes precedence over `StatsigOptions.api`. {% /parameter %} {% parameter name="environment" type="StatsigEnvironment" %} An object you can use to set environment variables that apply to all of your users in the same session. {% /parameter %} {% parameter name="overrideStableID" type="string" %} Overrides the auto-generated stableID that is set for the device. {% /parameter %} {% parameter name="logLevel" type="LogLevel" %} How much information is allowed to be printed to the console. {% /parameter %} {% parameter name="dataAdapter" type="SpecsDataAdapter" %} Implementing this type allows customization of the initialization. Refer to [Using SpecsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter) for details. {% /parameter %} {% parameter name="networkTimeoutMs" type="number" %} The maximum amount of time (in milliseconds) that any network request can take before timing out. {% /parameter %} {% parameter name="loggingBufferMaxSize" type="number" %} The maximum number of events to batch before flushing logs to Statsig. {% /parameter %} {% parameter name="loggingIntervalMs" type="number" %} How often (in milliseconds) to flush logs to Statsig. {% /parameter %} {% parameter name="overrideAdapter" type="OverrideAdapter" %} An implementor of `OverrideAdapter`, used to alter evaluations before its returned to the caller of a check api (checkGate/getExperiment etc). {% /parameter %} ## Manual Exposures {% callout type="warning" %} Manual logging is error-prone and can often introduce issues like uneven exposures, which compromise experiment results. {% /callout %} You can query your gates/experiments without triggering an exposure, and manually log the exposures later: ### Gates ```typescript // Check gate with exposure disabled const result = myStatsigClient.checkGate('a_gate_name', { user, disableExposureLog: true }); // Manually log the exposure myStatsigClient.checkGate('a_gate_name', { user }); ``` ### Configs ```typescript // Get config with exposure disabled const config = myStatsigClient.getConfig('a_dynamic_config_name', { user, disableExposureLog: true }); // Manually log the exposure myStatsigClient.getConfig('a_dynamic_config_name', { user }); ``` ### Experiments ```typescript // Get experiment with exposure disabled const experiment = myStatsigClient.getExperiment('an_experiment_name', { user, disableExposureLog: true }); // Manually log the exposure myStatsigClient.getExperiment('an_experiment_name', { user }); ``` ### Layers ```typescript // Get layer with exposure disabled const layer = myStatsigClient.getLayer('a_layer_name', { user, disableExposureLog: true }); const paramValue = layer.get('a_param_name', 'fallback_value'); // Manually log the exposure const layer = myStatsigClient.getLayer('a_layer_name', { user }); const paramValue = layer.get('a_param_name', 'fallback_value'); ``` ## Lifecycle & Advanced Usage ## Shutting Statsig Down The SDK keeps event logs in the client cache and flushes them periodically to save data and battery usage. Because of this, the SDK may not have flushed some events when your app shuts down. To ensure all logged events are flushed or saved locally, shut down Statsig when your app is closing: ```typescript await myStatsigClient.shutdown(); ``` ## Data Adapter The `EvaluationsDataAdapter` type defines how the `StatsigClient` fetches and caches data during initialize and update operations. By default, the `StatsigClient` uses `StatsigEvaluationsDataAdapter`, which fetches data synchronously from local storage and asynchronously from Statsig's servers. Refer to [Using EvaluationsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter) for details and example usage. ## Additional Resources * [On-Device Evaluation SDK Overview](/client/onDeviceOverview) * [JavaScript On-Device Evaluation SDK](/client/jsOnDeviceEvaluationSDK) * [Client Keys with Server Permissions](/access-management/api-keys#client-keys-with-server-permissions) * [Using EvaluationsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter) * [Debugging SDK Evaluations](/sdks/debugging) ================================================================================ # Migrating to @statsig/js-client URL: https://docs.statsig.com/client/migration-guides/MigrationFromOldJsClient Updated: 2026-07-11 ================================================================================ {% callout type="warning" %} ### Deprecated Migrate soon! Official support for statsig-js ended Jan 31, 2025. {% /callout %} The architecture and most APIs in the updated SDK have been retained. However, some modifications address common pitfalls, resolve existing issues, and streamline the SDK logic, resulting in some breaking changes. ## Breaking Changes * The SDK now offers both a synchronous and asynchronous [initialization](/client/migration-guides/MigrationFromOldJsClient#initialization) and [updateUser](/client/migration-guides/MigrationFromOldJsClient#updating-the-user) methods. * The "getConfig" method has [changed to "getDynamicConfig"](/client/migration-guides/MigrationFromOldJsClient#getconfig-is-now-getdynamicconfig) * When Bootstrapping, you'll now have to [pass the hash parameter as 'djb2'](/client/migration-guides/MigrationFromOldJsClient#bootstrapping) * The top-level static instance has moved to a static method, [StatsigClient.instance()](/client/migration-guides/MigrationFromOldJsClient#static-instance) * Overrides have moved into their own package: [js-local-overrides](/client/migration-guides/MigrationFromOldJsClient#overrides) * Parameters for GDPR compliance have [changed and been centralized](/client/migration-guides/MigrationFromOldJsClient#gdpr) * The method to retrieve a stableID [has changed](/client/migration-guides/MigrationFromOldJsClient#stableid-and-getstableid) * The structure of [cached values has changed](/client/migration-guides/MigrationFromOldJsClient#cached-values), with implications on first-run experience * Several StatsigOptions [have changed](/client/migration-guides/MigrationFromOldJsClient#legacy-statsigoptions) * Manual exposure logging methods (`getExperimentWithExposureLoggingDisabled`, `checkGateWithExposureLoggingDisabled`)have been deprecated. The checkGate and getExperiment methods now support a second argument to suppress exposure logging [as shown here](/client/javascript-sdk/#manual-exposures). ### Initialization Previously, the SDK used a single method for initialization. Because waiting for a method call during app startup can be impractical, the new SDK provides two distinct initialization approaches: one synchronous and one asynchronous. Synchronous initialization uses cache (if available) and returns immediately. Data for subsequent sessions is then fetched in the background. Asynchronous initialization is awaitable and ensures the most current data is fetched and used. {% codetabs %} ```typescript statsig-js (Legacy) import Statsig from "statsig-js"; // initialize returns a promise which always resolves await Statsig.initialize( "client-sdk-key", { userID: "some_user_id" }, { environment: { tier: "staging" } } // optional, pass options here if needed ); ``` ```typescript New - Async import { StatsigClient } from '@statsig/js-client'; const client = new StatsigClient( 'client-sdk-key', { userID: 'some_user_id' }, { environment: { tier: 'staging' } } ); // Async - waits for latest values await client.initializeAsync(); ``` ```typescript New - Sync import { StatsigClient } from '@statsig/js-client'; const client = new StatsigClient( 'client-sdk-key', { userID: 'some_user_id' }, { environment: { tier: 'staging' } } ); // Sync - uses cache, fetches in background client.initializeSync(); ``` {% /codetabs %} {% callout type="tip" %} View a full example on [GitHub](https://github.com/statsig-io/js-client-monorepo/blob/main/samples/react/src/samples/precomputed-client/sample-precomp-initialize.tsx) or read more about StatsigClient initialization [here](/client/javascript-sdk#initialize-the-sdk). {% /callout %} ### getConfig is now getDynamicConfig Update your `getConfig` call sites to call the new method, `getDynamicConfig`. In addition, `DynamicConfig` and `Layer` are no longer classes but JavaScript objects. Convenience `get` methods remain unchanged. ```js // old statsig.getConfig('config_name'); // new statsigClient.getDynamicConfig('config_name'); ``` ### Bootstrapping When bootstrapping from a server SDK, update how the server SDK generates values. The new `js-client` SDK uses a `djb2` hash instead of `sha256` for hashing gate/experiment names. By default, all server SDKs generate `sha256` hashes in the `getClientInitializeResponse` method. Set the hash algorithm parameter to `"djb2"` to bootstrap the new client SDK. This change also reduces the overall payload size, benefiting package size, speed, and payload size. This doesn't change any bucketing logic, only the obfuscation method used for the payload. For example, if you are bootstrapping from a nodejs app, you will need to do: ```js statsig.getClientInitializeResponse( user, '[client-key]', { hash: 'djb2', }, ); ``` ### Updating the User Similar to initialization, the `updateUser` method now supports both synchronous and asynchronous approaches, working the same way as the corresponding initialization approach. {% codetabs %} ```typescript statsig-js (Legacy) import Statsig from "statsig-js"; const user = { userID: "a-user" }; await Statsig.updateUser(user); ``` ```typescript New - Async import { StatsigClient } from '@statsig/js-client'; const client = new StatsigClient('client-sdk-key', { userID: 'initial-user' }); await client.initializeAsync(); // Update to new user - async const newUser = { userID: 'a-user' }; await client.updateUserAsync(newUser); ``` ```typescript New - Sync import { StatsigClient } from '@statsig/js-client'; const client = new StatsigClient('client-sdk-key', { userID: 'initial-user' }); client.initializeSync(); // Update to new user - sync const newUser = { userID: 'a-user' }; client.updateUserSync(newUser); ``` {% /codetabs %} {% callout type="tip" %} View a full example on [GitHub](https://github.com/statsig-io/js-client-monorepo/blob/main/samples/react/src/samples/precomputed-client/sample-precomp-update-user.tsx) {% /callout %} ## Static Instance In the previous SDK version, there was a top-level static interface for using Statsig. To improve support for multiple instances, this has been replaced with a static method that retrieves an instance. {% codetabs %} ```typescript statsig-js (Legacy) import Statsig from "statsig-js"; await Statsig.initialize(YOUR_CLIENT_KEY, { userID: 'a-user' }); // then later, at some other location in your code base if (Statsig.checkGate('a_gate')) { // do something... } ``` ```typescript New import { StatsigClient } from '@statsig/js-client'; // Initialize once in your app const client = new StatsigClient(YOUR_CLIENT_KEY, { userID: 'a-user' }); await client.initializeAsync(); // then later, at some other location in your code base const instance = StatsigClient.instance(YOUR_CLIENT_KEY); if (instance.checkGate('a_gate')) { // do something... } ``` {% /codetabs %} {% callout type="tip" %} View a full example on [GitHub](https://github.com/statsig-io/js-client-monorepo/blob/main/samples/react/src/samples/precomputed-client/sample-precomp-static-instance.tsx) or read more about StatsigClient multi-instance support [here](/client/javascript-sdk#multiple-client-instances). {% /callout %} ## Overrides Previously, the client had top-level methods for managing overrides for gates/configs/experiments/layers. This functionality has been removed from the main SDK and moved to its own package. You can implement your own overrides, or use `@statsig/js-local-overrides` and plug it in as the local override adapter in `StatsigOptions`. ``` import { StatsigClient } from '@statsig/js-client'; import { LocalOverrideAdapter } from '@statsig/js-local-overrides'; const overrideAdapter = new LocalOverrideAdapter(); overrideAdapter.overrideGate('gate_a', false); overrideAdapter.overrideGate('gate_b', true); const client = new StatsigClient( DEMO_CLIENT_KEY, { userID: 'a-user' }, { overrideAdapter, }, ); ``` Full example here: https://github.com/statsig-io/js-client-monorepo/blob/0e7201635f71e77633de04c4c19c1006030a3a81/samples/next-js/src/app/override-adapter-example/OverrideAdapterExample.tsx#L14 ## GDPR In consent management use cases, suspending cache and network usage until the user grants specific permissions is necessary. Previously, the approach was fragmented. In the new SDK, these settings are consolidated for consistency and ease of implementation. {% codetabs %} ```typescript statsig-js (Legacy) // start the SDK without storage or logging Statsig.initialize( 'client-key', { userID: 'a_user' }, { disableAllLogging: true, disableLocalStorage: true }, ); // then, once permission was granted Statsig.shutdown(); Statsig.initialize( 'client-key', { userID: 'a_user' }, { disableAllLogging: false, disableLocalStorage: false }, ); // or, by manually flipping the related flags Statsig.reenableAllLogging(); StatsigLocalStorage.disabled = false; ``` ```typescript New import { StatsigClient } from '@statsig/js-client'; // start the SDK without storage or logging const client = new StatsigClient( 'client-key', { userID: 'a_user' }, { disableLogging: true, disableStorage: true, networkConfig: { preventAllNetworkTraffic: true }, } ); await client.initializeAsync(); // then, once permission was granted client.updateRuntimeOptions({ disableLogging: false, disableStorage: false, networkConfig: { preventAllNetworkTraffic: false }, }); ``` {% /codetabs %} {% callout type="tip" %} View a full example on [GitHub](https://github.com/statsig-io/js-client-monorepo/blob/main/samples/react/src/samples/precomputed-client/sample-precomp-gdpr.tsx) {% /callout %} ## stableID and getStableID `statsig.getStableID()` no longer exists. You can get the stableID like this: `myStatsigClient.getContext().stableID` The key for the stableID in local storage has changed. The new key is a function of the SDK key used to initialize the SDK, which allows multiple SDK instances to coexist on a single page without overlapping cached data. To keep the existing stableID, access it in local storage from the old key `STATSIG_LOCAL_STORAGE_STABLE_ID` and set it in `customIDs` to override the stableID. ## Cached Values The structure of cached values has changed significantly, and there is no supported migration path from the old format to the new format. To pre-populate cached values, run the new SDK without issuing checks against it for some time before switching to use it for all checks. ## Legacy StatsigOptions The options for parameterizing SDK initialization have changed. In some cases, the underlying features were removed or moved and can be enabled or disabled in a different way. In other cases, there is a new API for managing them. The following maps old options to their equivalents in the new SDK. #### disableErrorLogging > This feature does not exist in the new Javascript SDK, so there is no option to disable it. #### disableAutoMetricsLogging > Moved to optional package [`@statsig/web-analytics`](https://www.npmjs.com/package/@statsig/web-analytics) #### disableAllLogging > This is now referred to as `StatsigOptions.disableLogging` #### disableCurrentPageLogging > This is now referred to as `StatsigOptions.includeCurrentPageUrlWithEvents` #### disableLocalStorage > This is now referred to as `StatsigOptions.disableStorage` #### localMode > This is now referred to as `StatsigOptions.networkConfig.preventAllNetworkTraffic` #### loggingIntervalMillis > This is now referred to as `StatsigOptions.loggingIntervalMs` #### loggingBufferMaxSize > Unchanged #### environment > Unchanged #### disableNetworkKeepalive > This has been completely removed. Control network requests with `StatsigOptions.networkConfig.networkOverrideFunc`. #### api > This is now referred to as `StatsigOptions.networkConfig.api` #### overrideStableID > stableID is now just part of `StatsigUser.customIDs`. This brings it inline with Statsig server > SDKs and helps avoid mis-configuration between client and server #### initTimeoutMs > Timeouts now live as part of the `async` call. > > eg: > `myStatsigClient.initializeAsync(1000); // 1 sec timeout` #### initializeValues > Replaced by the usage of [StatsigEvaluationsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter) #### eventLoggingApi > Replaced by `StatsigOptions.networkConfig.logEventUrl` #### prefetchUsers > Replaced by the usage of [StatsigEvaluationsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter) #### initCompletionCallback > Replaced by the usage of [StatsigClientEventEmitter](/client/javascript-sdk#client-event-emitter) ```js statsigClient.on('values_updated', function(evt) { if(evt.status && evt.status === 'Ready') { /* client has initialized */ } }); ``` #### updateUserCompletionCallback > Replaced by the usage of [StatsigClientEventEmitter](/client/javascript-sdk#client-event-emitter) #### fetchMode > Replaced by the usage of [StatsigEvaluationsDataAdapter](/client/javascript-mono/UsingEvaluationsDataAdapter) #### disableDiagnosticsLogging > There currently is no diagnostics logging in the new SDK #### initRequestRetries > This has been completely removed. Control network requests with `StatsigOptions.networkConfig.networkOverrideFunc`. #### ignoreWindowUndefined > No longer applicable #### disableHashing > This is now referred to as StatsigOptions.networkConfig.initializeHashAlgorithm #### logLevel > This has not been changed, but the enum values are no longer `UPPERCASED`. #### logger > Not yet supported #### evaluationCallback > Replaced by the usage of [StatsigClientEventEmitter](/client/javascript-sdk#client-event-emitter) ================================================================================ # Migrating to @statsig/react-bindings URL: https://docs.statsig.com/client/migration-guides/MigrationFromOldReact Updated: 2026-07-11 ================================================================================ {% callout type="warning" %} ### DEPRECATED Migrate soon! Official support for statsig-react ended Jan 31, 2025. {% /callout %} {% callout type="note" %} Also refer to the [migration from js-client guide](/client/migration-guides/MigrationFromOldJsClient), which lists other impacts on statsig-react installations. {% /callout %} Breaking changes: * The [initialization pattern](/client/migration-guides/MigrationFromOldReact#initialize) has changed, along with [waitForInitialization and initializeComponent](/client/migration-guides/MigrationFromOldReact#waitforinitialization-and-initializingcomponent). * Bootstrapping has changed, now requiring a new hashing parameter: [Bootstrapping the StatsigClient](/client/migration-guides/MigrationFromOldReact#bootstrapping-the-statsigclient). ### Initialize In the old `statsig-react` package, all values had to be given to the StatsigProvider, which internally set up the Statsig client instance. This approach caused issues in managing state between the Statsig client and the StatsigProvider, making it fragile and likely to break if you used the Statsig client directly. The new approach runs everything through the Statsig client instance and passes it to the StatsigProvider. {% codetabs %} ```jsx statsig-react (Legacy) import { StatsigProvider } from "statsig-react"; function App() { return ( <StatsigProvider sdkKey="<STATSIG_CLIENT_SDK_KEY>" user={{ userID: "a-user" }} waitForInitialization={true} // StatsigOptions (Not Required) options={{ environment: { tier: "staging" }, }} > <div className="App">{/* Rest of App ... */}</div> </StatsigProvider> ); } ``` ```tsx New import { StatsigProvider } from '@statsig/react-bindings'; import { StatsigClient } from '@statsig/js-client'; // Create the client const client = new StatsigClient( '<STATSIG_CLIENT_SDK_KEY>', { userID: 'a-user' }, { environment: { tier: 'staging' }, } ); function App() { return ( <StatsigProvider client={client} loadingComponent={<div>Loading...</div>}> <div className="App">{/* Rest of App ... */}</div> </StatsigProvider> ); } ``` {% /codetabs %} {% callout type="tip" %} View the full example on [GitHub](https://github.com/statsig-io/js-client-monorepo/blob/main/samples/react/src/samples/react-precomp/sample-react-precomp-initialize.tsx) {% /callout %} ### waitForInitialization and initializingComponent In older versions of the `StatsigProvider`, you could set `waitForInitialization` to block children from rendering until the latest values were fetched from Statsig. When `initializeComponent` was set, that component was displayed while values were being fetched. You could also set `waitForInitialization` to `false`, causing the StatsigProvider to render immediately before values were ready. This wasn't recommended because values could change between checks, resulting in unexpected layout changes. **Where did waitForInitialization go?** The newer `StatsigProvider` removes this option to prevent developers from unintentionally allowing values to change mid-session. You can still replicate the `waitForInitialization=false` behavior, but it isn't recommended. Go to the [Initialization Strategies](/client/concepts/initialize) guide to learn how to replicate that behavior and for recommended synchronous initialization approaches. ### Bootstrapping the StatsigClient When bootstrapping the Statsig Client in your React app from a Statsig Server SDK, you may need to update how the server SDK generates values. The `@statsig/js-client` SDK uses a `djb2` hash instead of `sha256` for hashing gate/experiment names. This doesn't change any bucketing logic, only the obfuscation method used for the payload. By default, all server SDKs generate `sha256` hashes in the `getClientInitializeResponse` method. Set the hash algorithm parameter to `"djb2"` to bootstrap the new client SDK. This change also reduces the overall payload size, which benefits package size, speed, and payload size. For example, if you bootstrap from the Statsig Node SDK: ```js statsig.getClientInitializeResponse( user, '[client-key]', { hash: 'djb2', // <- New Hashing Algorithm }, ); ``` ### Updating the User {% codetabs %} ```jsx statsig-react (Legacy) import { useContext, useState } from "react"; import { StatsigUser, StatsigProvider, StatsigContext } from "statsig-react"; function UpdateUserButton() { const { updateUser } = useContext(StatsigContext); return <button onClick={() => updateUser({ userID: "b-user" })}>Update</button> } function App() { const [user, setUser] = useState<StatsigUser>({ userID: "a-user" }); return ( <StatsigProvider sdkKey="<STATSIG_CLIENT_SDK_KEY>" user={user} setUser={setUser} > <UpdateUserButton /> </StatsigProvider> ); } ``` ```tsx New import { StatsigProvider, useClientAsyncInit } from '@statsig/react-bindings'; import { StatsigClient } from '@statsig/js-client'; const client = new StatsigClient('<STATSIG_CLIENT_SDK_KEY>', { userID: 'a-user' }); function UpdateUserButton() { const { client } = useClientAsyncInit(client); const handleUpdate = async () => { await client.updateUserAsync({ userID: 'b-user' }); }; return <button onClick={handleUpdate}>Update</button>; } function App() { return ( <StatsigProvider client={client}> <UpdateUserButton /> </StatsigProvider> ); } ``` {% /codetabs %} {% callout type="tip" %} View the full example on [GitHub](https://github.com/statsig-io/js-client-monorepo/blob/main/samples/react/src/samples/react-precomp/sample-react-precomp-update-user.tsx) {% /callout %} ================================================================================ # Server Core Overview URL: https://docs.statsig.com/server-core Updated: 2026-07-11 ================================================================================ ## Statsig Server Core Statsig Server Core is the second generation of Server SDKs: a full rewrite with a shared, performance-focused core library that delivers improved performance and feature maturity across all Server SDKs. Server Core SDKs include: * **Faster evaluation:** A shared Rust evaluation engine that evaluates 3-5x faster than native code SDKs. * **Better non-evaluation performance:** More efficient network and CPU usage, plus ongoing performance optimizations. * **Features new to Server SDKs:** Parameter Stores, Contextual Multi-Armed Bandits, and more. * **Additional capabilities:** Observability Interface, streaming config changes (from the Statsig Forward Proxy), and more. ### Availability across SDKs Server Core SDKs are available on an opt-in basis, with native SDKs still available and supported in all languages. If you are new to Statsig, use a Server Core SDK when convenient. Bug fixes continue to be applied to [Legacy SDKs](/server-core/legacy-sdks) until end-of-support dates are announced. Statsig provides migration guidance at that time. | SDK | Status | Package | Migration Guide | |-----|--------|---------|-----------------| | [Node](/server-core/node-core) | Stable | [npm](https://www.npmjs.com/package/@statsig/statsig-node-core) | [Link](/server-core/migration-guides/node) | | [Python](/server-core/python-core) | Stable | [PyPI](https://pypi.org/project/statsig-python-core/) | [Link](/server-core/migration-guides/python) | | [Java](/server-core/java-core) | Stable | [Maven Central](https://central.sonatype.com/artifact/com.statsig/javacore/overview) | [Link](server-core/migration-guides/java) | | [PHP](/server-core/php-core) | Stable | [Packagist](https://packagist.org/packages/statsig/statsigsdk) | | | [Rust](/server-core/rust-core) | Stable | [Crates.io](https://crates.io/crates/statsig-rust) | | | [Elixir](/server-core/elixir-core) | Stable | | | | [C++](/server-core/cpp-core) | Stable | | | | [.NET](/server-core/dotnet-core) | Stable | [NuGet](https://www.nuget.org/packages/Statsig.Dotnet) | | | Ruby | In Development | | | | [Go](/server-core/go-core) | Stable | | | ### Technical differences **Build process:** Statsig Server Core uses a core library written in Rust, with bindings for other languages. In most cases, the development experience is unchanged and performance is superior. Because the Rust code must compile into a binary compatible with your development and deployment environments, consider the following: * **Choosing the right build:** In most cases, the SDK's package manager automatically installs the correct version. The exception is Java, where the SDK prints the correct build if it isn't included at runtime. * **Managing lockfiles:** If your deployment and development environments require different builds (which is common), include both versions in a lockfile such as `package-lock.json`. * **Untested environments:** Certain environments, such as the edge, aren't compatible with this build process. Use [native SDKs](/server-core/legacy-sdks) in those cases. **New Configuration Spec:** Server Core uses a smaller "ruleset", or configuration spec. If you use the spec directly, your logic and parsing will have to change. **Event Logging** Server Core SDKs, starting in v0.4.0, use a new event logging architecture. During normal operation, events stream to Statsig servers. During outages on the event logging endpoint, the SDK throttles or drops events to allow the service to start healthy before processing steady-state traffic. The following parameters tune this behavior: ``` - event_logging_max_queue_size: Controls batch size (default 2000). Note that exceeding the backend request size limit (10MB) will drop requests - event_logging_max_pending_batch_queue_size: Controls max pending batches (default: 20). The tradeoff here is increased memory usage to buffer events when requests fail if you increase it, and losing additional events if you decrease it - disable_all_logging: Completely disables event logging ``` ``` +----------------+ +----------------+ +----------------+ | Event Sources | | Event Queue | | Flush Triggers | |----------------| |----------------| |----------------| | - Gate Checks |---->| - Pending | | - Scheduled | | - Config Reads | | Events | | (Time-based) | | - Custom Events| | - Batches |<----| - Manual | +----------------+ +----------------+ | - Shutdown | | +----------------+ v +----------------+ | Flush Process | |----------------| | - Batch Events | | - Send to API | | - Process | | Response | +----------------+ / \ / \ v v +---------------------------+---------------------------+ | On Request Failure | On Request Success | |---------------------------|---------------------------| | - Double interval | - Halve interval | | (max: 60000ms) | (min: 1000ms) | | - Retry errors | - No specific limit | | up to 5 times | flush mechanism | +---------------------------+---------------------------+ ``` ### Support If you have trouble with a Server Core SDK, share your feedback in [Slack](https://statsig.com/slack). ================================================================================ # Node Server SDK URL: https://docs.statsig.com/server-core/node-core Updated: 2026-07-11 ================================================================================ {% callout type="tip" %} Migrating from the legacy Node SDK? Refer to our [Migration Guide](/server-core/migration-guides/node) . {% /callout %} ## Set up the SDK {% steps %} {% step title="Install the SDK" %} ```shell npm i @statsig/statsig-node-core ``` The Node SDK is pre-built and compiled for different operating systems and CPU architectures. Package managers resolve the correct version automatically. {% accordion-group %} {% accordion title="Frozen Lockfile Setup" %} If your service has locked dependencies with package-lock.json or pnpm-lock.yml, include all required platform versions. For example, if you develop locally on macOS and deploy to Linux: ``` dependencies { "@statsig/statsig-node-core-darwin-arm64": "0.1.0" // for macOS "@statsig/statsig-node-core-linux-x64-gnu": "0.1.0" // for linux x64 machines } ``` {% /accordion %} {% accordion title="Usage with Next.js/Webpack/esbuild" %} `statsig-node-core` uses native binary files that can't be packaged with webpack/esbuild. To prevent errors, take the following steps: {% tabs %} {% tab title="Next.js" %} In your `next.config.js` file, add the `@statsig/statsig-node-core` package to the `serverExternalPackages` array: ```jsx const nextConfig = { serverExternalPackages: ['@statsig/statsig-node-core'], } ``` {% /tab %} {% tab title="esbuild" %} Add the `--packages=external` flag to your build script: ```shell esbuild --packages=external ``` {% /tab %} {% /tabs %} {% /accordion %} {% /accordion-group %} {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Keep Server Secret Keys private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} The optional `options` parameter accepts a `StatsigOptions` object to customize the SDK. ```jsx // Basic initialization import { Statsig, StatsigUser } from '@statsig/statsig-node-core'; //Or, in common JS, const { Statsig, StatsigUser } = require('@statsig/statsig-node-core'); const statsig = new Statsig("secret-key"); await statsig.initialize(); // or with StatsigOptions const options: StatsigOptions = { environment: "staging" }; const statsigWithOptions = new Statsig("secret-key", options); await statsigWithOptions.initialize(); ``` `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ### Checking a Feature Flag/Gate After the SDK is initialized, you can fetch a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (equivalent to `return false;`) by default. All APIs require a user object (refer to [Statsig user](#statsig-user)). For example: ```jsx const user = new StatsigUser({ userID: "a-user" }); if (statsig.checkGate(user, "a_gate")) { // Gate is on, enable new feature } else { // Gate is off } ``` ### Reading a Dynamic Config Feature Gates are useful for on/off switches with optional user targeting. To send different values (strings, numbers, and similar types) to clients based on user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The API is similar to Feature Gates, but returns a full JSON object configurable on the server, from which you can fetch typed parameters. ```jsx // Get the dynamic config const config = statsig.getDynamicConfig(user, "a_config"); // Get typed values using the getValue() method const itemName = config.getValue("product_name", "Awesome Product v1"); const price = config.getValue("price", 10.0); const shouldDiscount = config.getValue("discount", false); // Or access the entire value object directly const value = config.value; ``` ### Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available. Statsig recommends [Layers](/experiments/layers-overview) because they make parameters reusable and support mutually exclusive experiments. ```jsx // Or, via individual experiments const titleExp = statsig.getExperiment(user, "new_user_promo_title"); const priceExp = statsig.getExperiment(user, "new_user_promo_price"); const experimentTitle = titleExp.getValue("title", "Welcome to Statsig!"); const experimentDiscount = priceExp.getValue("discount", 0.1); // Get values via Layer const layer = statsig.getLayer(user, "user_promo_experiments"); const title = layer.getValue("title", "Welcome to Statsig!"); const discount = layer.getValue("discount", 0.1); ``` ### Retrieving Feature Gate Metadata In certain scenarios, you may need more information about a gate evaluation than just a boolean value. For additional metadata about the evaluation, use the Get Feature Gate API, which returns a FeatureGate object: ```jsx const gate = statsig.getFeatureGate(statsigUser, "example_gate") console.log(gate.rule_id) console.log(gate.value) ``` ### Parameter Stores Use Parameter Stores when you want to define a parameter without deciding whether it should be a Feature Gate, Experiment, or Dynamic Config. Parameter Stores let you change the parameter type at any point in the Statsig console without a new deployment. Parameter Stores are optional, but parameterizing your application provides future flexibility and allows non-technical Statsig users to turn parameters into experiments. ```jsx const paramStore = statsig.getParameterStore(statsigUser, "my_parameters") const paramStoreValue = paramStore.getValue('my_parameter_value') ``` ### Logging an Event To track custom events, call the Log Event API. Specify the user, event name, and an optional value or metadata object: ```jsx statsig.logEvent( user, "add_to_cart", null, { price: "9.99", item_name: "diet_coke_48_pack" } ); ``` Learn more about identifying users, group analytics, and best practices for logging events in the [logging events guide](/guides/logging-events). ### Sending Events to Log Explorer You can forward logs to Logs Explorer for convenient analysis using the Forward Log Line Event API. This lets you include custom metadata and event values with each log. ```jsx const user = new StatsigUser({ userID: "a-user", custom: { service: "my-service", pod: "my-pod", namespace: "my-namespace", container: "my-container", // ...include any service-specific metadata } }); // levels: trace, debug, info, log, warn, error statsig.forwardLogLineEvent(user, "warn", "script failed to load", { cusom_metadata: "script_name:my-script" // ... include any event-specific metadata }); ``` ## Using shared instance To create a single Statsig instance accessible globally throughout your codebase, use the shared instance functionality, which provides a singleton pattern: ```jsx // Create a shared instance that can be accessed globally const statsig = Statsig.newShared("secret-key"); await statsig.initialize(); // Access the shared instance from anywhere in your code const sharedStatsig = Statsig.shared(); const isFeatureEnabled = sharedStatsig.checkGate(user, "feature_name"); // Check if a shared instance exists if (Statsig.hasSharedInstance()) { // Use the shared instance } // Remove the shared instance when no longer needed Statsig.removeShared(); ``` The shared instance lets a single Statsig instance be created and accessed globally throughout your application. This is useful when multiple parts of the codebase need Statsig without passing an instance around. * `Statsig.newShared(sdkKey, options)`: Creates a new shared instance of Statsig that can be accessed globally * `Statsig.shared()`: Returns the shared instance * `Statsig.hasSharedInstance()`: Checks if a shared instance exists (useful when the shared instance may not be ready yet) * `Statsig.removeShared()`: Removes the shared instance (useful when you want to switch to a new shared instance) {% callout type="note" %} `hasSharedInstance()` and `removeShared()` are helpful in specific scenarios but aren't required in most use cases where the shared instance is set up near the top of your application. Also note that only one shared instance can exist at a time. Attempting to create a second shared instance will result in an error. {% /callout %} ## Manual exposures By default, the SDK automatically logs an exposure event when you check a gate, get a config, get an experiment, or call get() on a parameter in a layer. To delay exposure logging (for example, to log only after the user actually uses the feature), use manual exposures. All main SDK functions (`checkGate`, `getDynamicConfig`, `getExperiment`, `getLayer`) accept an optional `disableExposureLogging` parameter. When set to `true`, the SDK doesn't automatically log an exposure. You can then log the exposure manually at a later time: {% tabs %} {% tab title="Feature Gates" %} ```jsx const result = statsig.checkGate(aUser, 'a_gate_name', {disableExposureLogging: true}); ``` ```jsx statsig.manuallyLogGateExposure(aUser, 'a_gate_name'); ``` {% /tab %} {% tab title="Dynamic Configs" %} ```jsx const config = statsig.getDynamicConfig(aUser, 'a_dynamic_config_name', {disableExposureLogging: true}); ``` ```jsx statsig.manuallyLogDynamicConfigExposure(aUser, 'a_dynamic_config_name'); ``` {% /tab %} {% tab title="Experiments" %} ```jsx const experiment = statsig.getExperiment(aUser, 'an_experiment_name', {disableExposureLogging: true}); ``` ```jsx statsig.manuallyLogExperimentExposure(aUser, 'an_experiment_name'); ``` {% /tab %} {% tab title="Layers" %} ```jsx const layer = statsig.getLayer(aUser, 'a_layer_name', {disableExposureLogging: true}); const paramValue = layer.get('a_param_name', 'fallback_value'); ``` ```jsx statsig.manuallyLogLayerParameterExposure(aUser, 'a_layer_name', 'a_param_name'); ``` {% /tab %} {% /tabs %} ## Statsig User The `StatsigUser` object represents a user in Statsig. You must provide a `userID` or at least one of the `customIDs` to identify the user. When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks) and to correctly measure the impact of experiments on metrics and events. As explained [here](/sdks/user#why-is-an-id-always-required-for-server-sdks), at least one identifier (userID or customID) is required to provide a consistent experience for a given user. In addition to userID, the top-level fields on StatsigUser are: email, ip, userAgent, country, locale, and appVersion. You can also pass any key-value pairs in an object or dictionary to the custom field and create targeting based on them. ### Private Attributes Private attributes are user attributes that Statsig uses for evaluation but doesn't forward to any integrations. They are useful for PII or sensitive data that you don't want to send to third-party services. ```typescript const user = new StatsigUser({ userID: "a-user-id", email: "user@example.com", ip: "192.168.1.1", userAgent: "Mozilla/5.0...", country: "US", locale: "en_US", appVersion: "1.0.0", custom: { // Custom fields plan: "premium", age: 25 }, customIDs: { // Custom ID types stableID: "stable-id-123" }, privateAttributes: { // Private attributes not forwarded to integrations email: "private@example.com" } }); ``` ## Statsig Options You can pass an optional `options` parameter in addition to `sdkKey` during initialization to customize the Statsig client. {% accordion title="StatsigOptions" %} ### Parameters {% parameter name="environment" type="string" %} Environment parameter for evaluation. {% /parameter %} {% parameter name="specsUrl" type="string" %} Custom URL for fetching feature specifications. Provide the full endpoint URL, which the SDK uses as-is. {% /parameter %} {% parameter name="idListsUrl" type="string" %} Custom URL for fetching the ID list manifest (the `get_id_lists` endpoint). Provide the full endpoint URL, which the SDK uses as-is. {% /parameter %} {% parameter name="downloadIdListFileApi" type="string" %} Origin (scheme and host only, no path) for downloading individual ID list files. Unlike `specsUrl` and `idListsUrl`, this takes an origin, not a full endpoint URL: the SDK appends each per-file path from the ID list manifest. Refer to [Routing endpoints through a forward proxy](#routing-endpoints-through-a-forward-proxy). {% /parameter %} {% parameter name="specsSyncIntervalMs" type="number" %} How often the SDK updates specifications from Statsig servers (in milliseconds). {% /parameter %} {% parameter name="fallbackToStatsig" type="boolean" %} Turn this on if you proxy `download_config_specs` / `get_id_lists` and want to fall back to the default Statsig endpoint to increase reliability. {% /parameter %} {% parameter name="logEventUrl" type="string" %} Custom URL for logging events. {% /parameter %} {% parameter name="disableAllLogging" type="boolean" %} If true, the SDK doesn't collect any logging within the session, including custom events and config check exposure events. {% /parameter %} {% parameter name="enableIdLists" type="boolean" %} Required to be `true` when using segments with more than 1000 IDs. Refer to [ID List segments](/segments/add-id-list). {% /parameter %} {% parameter name="disableUserAgentParsing" type="boolean" %} If true, the SDK doesn't parse User-Agent strings into `browserName`, `browserVersion`, `systemName`, `systemVersion`, and `appVersion` when needed for evaluation. {% /parameter %} {% parameter name="waitForUserAgentInit" type="boolean" %} When true, the SDK waits until user agent parsing data is fully loaded during initialization (~1 second), ensuring parsing is ready before any evaluations. {% /parameter %} {% parameter name="disableUserCountryLookup" type="boolean" %} If true, the SDK doesn't parse IP addresses (from `user.ip`) into country codes when needed for evaluation. {% /parameter %} {% parameter name="waitForCountryLookupInit" type="boolean" %} When true, the SDK waits for country lookup data (e.g., GeoIP or YAML files) to fully load during initialization (~1 second), ensuring IP-to-country parsing is ready at evaluation time. {% /parameter %} {% parameter name="eventLoggingFlushIntervalMs" type="number" %} How often events are flushed to Statsig servers (in milliseconds). {% /parameter %} {% parameter name="eventLoggingMaxQueueSize" type="number" %} Maximum number of events to queue before forcing a flush. {% /parameter %} {% parameter name="dataStore" type="DataStore" %} An adapter with custom storage behavior for config specs. Can also continuously fetch updates in place of the Statsig network. Refer to [Data Stores](#data-store). For an example, go to the 1P Redis implementation [statsig-node-redis](https://github.com/statsig-io/node-js-server-sdk-redis). {% /parameter %} {% parameter name="specAdaptersConfig" type="SpecAdapterConfig[]" %} Advanced settings to fetch from different sources (e.g., [statsig forward proxy](/infrastructure/api_proxy/introduction), your own proxy server, data store) or to use different network protocols (HTTP vs gRPC streaming). {% /parameter %} {% parameter name="observabilityClient" type="ObservabilityClient" %} Interface to integrate observability metrics exposed by the SDK (e.g., config propagation delay, initialization time). See [details](#observability-client). {% /parameter %} {% parameter name="persistentStorage" type="PersistentStorage" %} Interface to use persistent storage within the SDK. See [details](#persistent-storage). {% /parameter %} {% parameter name="proxyConfig" type="ProxyConfig" %} Configuration for connecting through a proxy server. {% /parameter %} {% accordion title="ProxyConfig" %} {% parameter name="proxyHost" type="string" %} Proxy server host. {% /parameter %} {% parameter name="proxyPort" type="number" %} Proxy server port. {% /parameter %} {% parameter name="proxyAuth" type="string" %} Proxy authentication in the form "username:password". {% /parameter %} {% parameter name="proxyProtocol" type="string" %} Protocol (e.g., "http", "https"). {% /parameter %} {% parameter name="caCertPath" type="string" %} Optional path to a PEM CA bundle for outbound TLS. {% /parameter %} {% /accordion %} {% /accordion %} ### Proxy and Custom Network Routing Use `proxyConfig` if your service needs a standard outbound HTTP proxy. Use `specAdaptersConfig` if you are routing spec downloads through [Statsig Forward Proxy](/infrastructure/api_proxy/introduction) or another custom spec source. ```typescript // Example usage: const options: StatsigOptions = { environment: "staging", initTimeoutMs: 3000, proxyConfig: { proxyHost: "proxy.example.com", proxyPort: 8080, // proxyAuth: set if authentication is required proxyProtocol: "https", caCertPath: "/etc/ssl/certs/corporate-ca.pem", }, }; const statsig = new Statsig("secret-key", options); await statsig.initialize(); ``` Set `caCertPath` when your environment requires a custom PEM CA bundle for outbound TLS. ### Routing endpoints through a forward proxy To send Statsig network traffic through your own proxy or CDN, override the endpoint options. These options don't all take the same kind of value: * `specsUrl` and `idListsUrl` take a **full endpoint URL**. The SDK requests each URL as-is. * `downloadIdListFileApi` takes an **origin only** (scheme and host, no path). Per-file download URLs are dynamic. Each one carries a unique file ID and signature embedded in the ID list manifest, so the SDK can't use a fixed URL. Instead, it takes each per-file path from the manifest and appends it to this origin. ```typescript const options: StatsigOptions = { // Full endpoint URLs, used as-is: specsUrl: "https://proxy.example.com/v1/download_config_specs", idListsUrl: "https://proxy.example.com/v1/get_id_lists", // Origin only. The SDK appends each manifest file path: downloadIdListFileApi: "https://proxy.example.com", }; const statsig = new Statsig("secret-key", options); await statsig.initialize(); ``` {% callout type="warning" %} Set `downloadIdListFileApi` to an origin only, such as `https://proxy.example.com`. If you include a path such as `/v1/download_id_list_file`, the SDK appends the manifest path on top of it, producing a doubled path such as `.../v1/download_id_list_file/v1/download_id_list_file/<fileId>` and a 401 response. {% /callout %} Overriding only `specsUrl` and `idListsUrl` routes the ID list manifest through your proxy, but individual ID list **file** downloads still go to the Statsig CDN. Set `downloadIdListFileApi` to route those file downloads through your proxy as well. ## Shutting Statsig down Statsig batches and periodically flushes events. To ensure all logged events are flushed before shutdown, call `shutdown()` before your app or server shuts down: ```jsx await statsig.shutdown(); ``` ## Client SDK bootstrapping | SSR If you use the Statsig client SDK in a browser or mobile app, you can bootstrap the client SDK with values from the server SDK to avoid a network request on the client. This is useful for server-side rendering (SSR) or to reduce network requests on the client. ## Client initialize response The Node Core SDK provides a method to generate a client initialize response that can be used to bootstrap client SDKs without requiring network requests. ```typescript // Get client initialize response for a user const values = statsig.getClientInitializeResponse(user, options); // Pass values to a client SDK to initialize without a network request ``` {% accordion-group %} {% accordion title="Initialize Response Options" %} The `getClientInitializeResponse` method accepts an optional `options` parameter with the following properties: ```typescript export interface ClientInitializeResponseOptions { hashAlgorithm?: string; // Algorithm used for hashing gate/experiment names (default: 'djb2') clientSdkKey?: string; // Client SDK key to use for initialization includeLocalOverrides?: boolean; // Whether to include local overrides in the response featureGateFilter?: Set<string>; // Filter to only include specific feature gates experimentFilter?: Set<string>; // Filter to only include specific experiments dynamicConfigFilter?: Set<string>; // Filter to only include specific dynamic configs layerFilter?: Set<string>; // Filter to only include specific layers paramStoreFilter?: Set<string>; // Filter to only include specific parameter stores } ``` {% /accordion %} {% accordion title="Hash Algorithm" %} The `hashAlgorithm` option specifies which algorithm to use for hashing gate and experiment names in the client initialize response. The default is `'djb2'` for better performance and smaller payload size. ```typescript // Use djb2 hashing algorithm for better performance const values = statsig.getClientInitializeResponse(user, { hashAlgorithm: 'djb2', }); ``` {% /accordion %} {% accordion title="Client SDK Key" %} The `clientSdkKey` option lets you filter the response to only the specific feature gates, experiments, dynamic configs, layers, or parameter stores that a particular client key has access to, effectively letting you apply [target apps](/sdks/target-apps). ```typescript // Specify a client SDK key const values = statsig.getClientInitializeResponse(user, { clientSdkKey: 'client-key', }); ``` {% /accordion %} {% accordion title="Filtering" %} The filter options allow you to reduce the payload size by only including specific feature gates, experiments, dynamic configs, layers, or parameter stores in the response. ```typescript // Only include specific feature gates and experiments const values = statsig.getClientInitializeResponse(user, { featureGateFilter: new Set(['my_gate_1', 'my_gate_2']), experimentFilter: new Set(['my_experiment']), }); ``` {% /accordion %} {% accordion title="Include Local Overrides" %} The `includeLocalOverrides` option determines whether to consider [local overrides](#local-overrides) you've set when evaluating each config in the response. ```typescript // Include local overrides in the response const values = statsig.getClientInitializeResponse(user, { includeLocalOverrides: true, }); ``` {% /accordion %} {% accordion title="Full Code Example" %} Below is a complete example of using the client initialize response to bootstrap a client SDK. You can parallelize or inline the initialize response data with other requests to your server to eliminate additional round trips and latency. ```typescript // Server-side code import { Statsig, StatsigUser } from '@statsig/node-core'; // Initialize the server SDK await Statsig.initialize('server-secret-key'); // In your API endpoint handler app.get('/statsig-bootstrap', (req, res) => { // Create a user object from the request const user = new StatsigUser({ userID: req.query.userID || '', email: req.query.email, ip: req.ip, userAgent: req.headers['user-agent'], }); // Generate the client initialize response with filters const values = Statsig.getClientInitializeResponse(user, { hashAlgorithm: 'djb2', featureGateFilter: new Set(['onboarding_v2', 'new_checkout']), experimentFilter: new Set(['pricing_experiment']), layerFilter: new Set(['ui_layer']), }); // Return the values to the client res.json({ statsigValues: values }); }); ``` ```typescript // Client-side code using @statsig/js-client import { Statsig } from '@statsig/js-client'; // Fetch bootstrap values from your API const response = await fetch('/statsig-bootstrap'); const { statsigValues } = await response.json(); // Initialize the client SDK with the bootstrap values await Statsig.initialize({ sdkKey: 'client-sdk-key', initializeValues: statsigValues, }); ``` {% /accordion %} {% /accordion-group %} ## SDK Event Subscriptions The Statsig SDK provides an event subscription system that lets you listen for evaluation events and lifecycle events in real time. This is useful for debugging, analytics, custom logging, and integration with external systems. ### Supported Events The SDK supports subscribing to the following evaluation events: * **`gate_evaluated`** - Fired when a feature gate is evaluated for a user * **`dynamic_config_evaluated`** - Fired when a dynamic config is retrieved for a user * **`experiment_evaluated`** - Fired when an experiment is evaluated for a user * **`layer_evaluated`** - Fired when a layer is evaluated for a user * **`specs_updated`** - Fired when the SDK updates its cached specs, including where the specs were loaded from * **`"*"`** - Subscribe to all evaluation events ### SDK Event Data Each event includes relevant context about the evaluation: * **Gate Evaluated Events** include: `gate_name`, `value` (boolean), `rule_id`, `reason` * **Dynamic Config Events** include: the full `dynamic_config` object with values and metadata * **Experiment Events** include: the full `experiment` object with variant assignment and parameters * **Layer Events** include: the full `layer` object with allocated experiment and parameters * **Specs Updated Events** include `source`, `source_api`, and `values` metadata, where `values.time` is the timestamp of the last update to the project in the Statsig console ### Use Cases Event subscriptions are particularly useful for: * **Debugging**: Monitor which features are being evaluated and their results * **Analytics**: Track feature usage patterns and user segments * **Custom Logging**: Send evaluation data to your own logging systems * **Integration**: Forward events to external analytics or monitoring tools * **Testing**: Verify that features are being evaluated as expected ### Best Practices * **Clean up subscriptions**: Always unsubscribe when you no longer need to listen for events to prevent memory leaks * **Handle event data carefully**: Event objects may contain sensitive user information depending on your configuration * **Use specific event types**: Subscribe to specific events rather than "\*" when possible for better performance * **Avoid heavy processing**: Keep event handlers lightweight to avoid impacting SDK performance ```javascript expandable const statsig = new Statsig('server-secret-key'); // Subscribe to gate evaluation events const gateSubId = statsig.subscribe('gate_evaluated', (event) => { console.log('Gate evaluated:', { gateName: event.gate_name, value: event.value, ruleId: event.rule_id, reason: event.reason }); }); // Subscribe to dynamic config evaluation events const configSubId = statsig.subscribe('dynamic_config_evaluated', (event) => { console.log('Config evaluated:', { configName: event.dynamic_config.name, values: event.dynamic_config.value }); }); // Subscribe to experiment evaluation events const experimentSubId = statsig.subscribe('experiment_evaluated', (event) => { console.log('Experiment evaluated:', { experimentName: event.experiment.name, groupName: event.experiment.group_name, parameters: event.experiment.value }); }); // Subscribe to layer evaluation events const layerSubId = statsig.subscribe('layer_evaluated', (event) => { console.log('Layer evaluated:', { layerName: event.layer.name, allocatedExperiment: event.layer.allocated_experiment_name, parameters: event.layer.value }); }); // Subscribe to specs updated events const specsUpdatedSubId = statsig.subscribe('specs_updated', (event) => { console.log('Specs updated:', { source: event.data.source, sourceApi: event.data.source_api, projectLastUpdated: event.data.values.time, }); }); // Subscribe to all events const allEventsSubId = statsig.subscribe('*', (event) => { console.log('Event received:', event.event_name, event); }); // Unsubscribe from specific event types statsig.unsubscribe('gate_evaluated'); statsig.unsubscribe('specs_updated'); // Unsubscribe using subscription ID statsig.unsubscribeById(configSubId); // Unsubscribe from all events statsig.unsubscribeAll(); ``` ## Local overrides Local Overrides let you override the values of gates, configs, experiments, and layers for testing. This is useful for local development or testing when you want to force a specific value without changing the configuration in the Statsig console. {% tabs %} {% tab title="Feature Gates" %} ```jsx // Overrides the given gate to the specified value Statsig.overrideGate("a_gate_name", true); // Optional third parameter, overrides the gate only for a given ID Statsig.overrideGate("a_gate_name", true, "userID-123"); ``` {% /tab %} {% tab title="Dynamic Configs" %} ```jsx // Overrides the given dynamic config to the provided value Statsig.overrideDynamicConfig("a_config_name", { key: "value" }); // Optional third parameter, overrides the dynamic config only for a given ID Statsig.overrideDynamicConfig("a_config_name", { key: "value" }, "userID-123"); ``` {% /tab %} {% tab title="Experiments" %} ```jsx // Overrides the given experiment to the provided value Statsig.overrideExperiment("an_experiment_name", { key: "value" }); // Optional third parameter, overrides the experiment only for a given ID Statsig.overrideExperiment("an_experiment_name", { key: "value" }, "userID-123"); // Overrides the given experiment to a particular groupname Statsig.overrideExperimentByGroupName("an_experiment_name", "a_group_name"); // Alternatively, get the Experiment object for a given groupName const groupExp = statsig.getExperimentByGroupName("pricing_experiment", "premium_group"); const premiumPrice = groupExp.getValue("price", 9.99); ``` {% /tab %} {% tab title="Layers" %} ```jsx // Overrides the given layer to the provided value Statsig.overrideLayer("a_layer_name", { key: "value" }); // Optional third parameter, overrides the layer only for a given ID Statsig.overrideLayer("a_layer_name", { key: "value" }, "userID-123"); ``` {% /tab %} {% /tabs %} ## Persistent storage The Persistent Storage interface lets you implement custom storage for user-specific configurations. This enables you to persist user assignments across sessions, ensuring consistent experiment groups when a user returns. This is useful for client-side A/B testing where users must always receive the same variant. ```typescript expandable PersistentStorageInterface.ts export interface PersistentStorage { load: (key: string) => UserPersistedValues | null; save: (key: string, config_name: string, data: StickyValues) => void; delete: (key: string, config_name: string) => void; } export interface StickyValues { value: boolean; json_value: Record<string, unknown>; rule_id: string; group_name: string | null; secondary_exposures: SecondaryExposure[]; undelegated_secondary_exposures: SecondaryExposure[]; config_delegate: string | null; explicit_parameters: string[] | null; time: number; configVersion?: number | undefined; } export type UserPersistedValues = Record<string, StickyValues>; export interface SecondaryExposure { gate: string; gateValue: string; ruleId: string; } ``` ### Usage Example ```typescript expandable PersistentStorageUsage.ts import { PersistentStorage, StickyValues, UserPersistedValues } from '@statsig/statsig-node-core'; class MyPersistentStorage implements PersistentStorage { private storage = new Map<string, UserPersistedValues>(); constructor() { this.load = this.load.bind(this); this.save = this.save.bind(this); this.delete = this.delete.bind(this); } load(key: string): UserPersistedValues | null { return this.storage.get(key) || null; } save(key: string, config_name: string, data: StickyValues): void { const existing = this.storage.get(key) || {}; existing[config_name] = data; this.storage.set(key, existing); } delete(key: string, config_name: string): void { const existing = this.storage.get(key); if (existing) { delete existing[config_name]; this.storage.set(key, existing); } } getUserPersistedValue(user: StatsigUser, idType: string): UserPersistedValues | null { const storageKey = this.getStorageKey(user, idType); if (storageKey !== null) { return this.load(storageKey); } return null; } private getStorageKey(user: StatsigUser, idType: string): string | null { const lowerCaseIdType = idType.toLowerCase(); if (lowerCaseIdType === "user_id" || lowerCaseIdType === "userid") { const id = user.userID; return id ? `${id}:userID` : null; } else if (user.customIDs) { const id = user.customIDs[idType]; return id ? `$\{id\}:${idType}` : null; } return null; } } ``` {% callout type="note" %} Persistent storage support was added in version 0.6.1 of the Node.js SDK. {% /callout %} ## Data store The Data Store interface lets you implement custom storage for Statsig configurations, enabling advanced caching strategies and integration with your preferred storage systems. ```typescript export interface DataStore { initialize?: () => Promise<void>; shutdown?: () => Promise<void>; get?: (key: string) => Promise<DataStoreResponse>; set?: (key: string, value: string, time?: number) => Promise<void>; supportPollingUpdatesFor?: (key: string) => Promise<boolean>; } export interface DataStoreResponse { result?: string; time?: number; } ``` For an example, go to the 1P implementation using Redis: [statsig-node-redis](https://github.com/statsig-io/node-js-server-sdk-redis). ## Custom output logger The Output Logger interface lets you customize how the SDK logs messages, enabling integration with your own logging system and control over log verbosity. {% accordion-group %} {% accordion title="Logger Interface" %} ```typescript interface OutputLoggerProvider { initialize?: () => void; debug?: (tag: string, message: string) => void; info?: (tag: string, message: string) => void; warn?: (tag: string, message: string) => void; error?: (tag: string, message: string) => void; shutdown?: () => void; } ``` {% /accordion %} {% accordion title="Implementation Example" %} ### Implementation Example ```typescript import { OutputLoggerProvider } from '@statsig/statsig-node-core'; class CustomOutputLogger implements OutputLoggerProvider { initialize = () => { console.log('Logger initialized'); }; debug = (tag: string, message: string) => { console.debug(`[$\{tag\}] ${message}`); }; info = (tag: string, message: string) => { console.info(`[$\{tag\}] ${message}`); }; warn = (tag: string, message: string) => { console.warn(`[$\{tag\}] ${message}`); }; error = (tag: string, message: string) => { console.error(`[$\{tag\}] ${message}`); }; shutdown = () => { console.log('Logger shutdown'); }; } ``` {% /accordion %} {% accordion title="Usage with StatsigOptions" %} ```typescript import { Statsig, StatsigOptions } from '@statsig/statsig-node-core'; const customLogger = new CustomOutputLogger(); const options: StatsigOptions = { outputLoggerProvider: customLogger, outputLogLevel: 'info', // 'none' | 'debug' | 'info' | 'warn' | 'error' }; const statsig = new Statsig('secret-key', options); await statsig.initialize(); ``` {% /accordion %} {% accordion title="Notes" %} * All methods in the `OutputLoggerProvider` interface are optional * The `tag` parameter indicates the SDK component or category generating the log message * Use `outputLogLevel` in StatsigOptions to control which log levels are actually called * The logger initializes when the Statsig client initializes and shuts down when the client shuts down {% /accordion %} {% /accordion-group %} ## Observability client The Observability Client interface lets you monitor the health of the SDK by integrating with your own observability systems. This enables you to track metrics, errors, and performance data. For more information on the metrics Statsig SDKs emit, refer to the [Monitoring documentation](/infrastructure/sdk-monitoring). ```typescript export interface ObservabilityClient { initialize?: () => void; increment?: (metricName: string, value: number, tags: Record<string, string>) => void; gauge?: (metricName: string, value: number, tags: Record<string, string>) => void; dist?: (metricName: string, value: number, tags: Record<string, string>) => void; error?: (tag: string, error: string) => void; } ``` ## FAQs ### How do I run experiments for logged out users? ### Common Problems while installing 1. **Seeing SSL Error** The binary files require certain SSL versions. ```shell // Try run this apt-get update && apt-get install libcurl4-openssl-dev -y && rm -rf /var/lib/apt/lists/* ``` 2. **Docker build failing with platform-specific dependencies** When building in Docker (Linux environment), the build may fail if your local `package-lock.json` or `yarn.lock` contains platform-specific dependencies for macOS. This happens because `npm install` on Mac pulls down Apple-specific variants, but Docker tries to use those locked dependencies on Linux. **Solution:** Either install the Linux-specific variant during your Docker build step: ```dockerfile RUN npm install @statsig/statsig-node-core-linux-x64-gnu ``` Or add both platform variants as dependencies in your `package.json`: ```json "dependencies": { "@statsig/statsig-node-core": "X.Y.Z", // Common (Required) "@statsig/statsig-node-core-darwin-arm64": "X.Y.Z", // Mac Specific "@statsig/statsig-node-core-linux-x64-gnu": "X.Y.Z" // Linux Specific } ``` ## Reference {% accordion title="All API Methods" %} * `checkGate(user: StatsigUser, gateName: string, options?: EvaluationOptions): boolean` * `getDynamicConfig(user: StatsigUser, configName: string, options?: EvaluationOptions): DynamicConfig` * `getExperiment(user: StatsigUser, experimentName: string, options?: EvaluationOptions): DynamicConfig` * `getLayer(user: StatsigUser, layerName: string, options?: EvaluationOptions): Layer` * `getFeatureGate(user: StatsigUser, gateName: string, options?: EvaluationOptions): FeatureGate` * `getParameterStore(user: StatsigUser, parameterStoreName: string, options?: EvaluationOptions): ParameterStore` * `getPrompt(user: StatsigUser, promptName: string, options?: EvaluationOptions): Prompt` * `getPromptSet(user: StatsigUser, promptSetName: string, options?: EvaluationOptions): PromptSet` * `logEvent(user: StatsigUser, eventName: string, value?: string | number | null, metadata?: Record<string, string>): void` * `forwardLogLineEvent(user: StatsigUser, level: string, message: string, metadata?: Record<string, string>): void` * `manuallyLogGateExposure(user: StatsigUser, gateName: string): void` * `manuallyLogDynamicConfigExposure(user: StatsigUser, configName: string): void` * `manuallyLogExperimentExposure(user: StatsigUser, experimentName: string): void` * `manuallyLogLayerParameterExposure(user: StatsigUser, layerName: string, parameterName: string): void` * `overrideExperimentByGroupName(experimentName: string, groupName: string, id?: string | null): void` * `getClientInitializeResponse(user: StatsigUser, options?: ClientInitializeResponseOptions): ClientInitializeResponse` * `shutdown(): Promise<void>` {% /accordion %} {% accordion title="Fields Needed Methods" %} The following methods return information about which user fields are needed for evaluation: * `getGateFieldsNeeded(gateName: string): string[]` * `getDynamicConfigFieldsNeeded(configName: string): string[]` * `getExperimentFieldsNeeded(experimentName: string): string[]` * `getLayerFieldsNeeded(layerName: string): string[]` These methods return an array of strings representing the user fields required to properly evaluate the specified gate, config, experiment, or layer. Use these methods to: * Optimize user object creation by including only necessary fields * Understand which user attributes affect a particular feature * Debug evaluation issues {% /accordion %} ================================================================================ # Python Server SDK URL: https://docs.statsig.com/server-core/python-core Updated: 2026-07-11 ================================================================================ {% callout type="tip" %} Migrating from the legacy Python SDK? Refer to our [Migration Guide](/server-core/migration-guides/python) . {% /callout %} ## Set up the SDK {% steps %} {% step title="Install the SDK" %} ## Installation ```shell pip install statsig-python-core ``` {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Keep Server Secret Keys private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} An optional `options` parameter accepts a StatsigOptions object to customize the SDK. ```python from statsig_python_core import Statsig, StatsigOptions # note, import statement has underscores while install has dashes options = StatsigOptions() options.environment = "development" statsig = Statsig("secret-key", options) statsig.initialize().wait() # If you're running this in a script, be sure to wait for shutdown at the end to flush event logs to statsig statsig.shutdown().wait() ``` `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background independently of API calls. {% callout type="warning" heading="Process forking and WSGI servers" %} Never fork processes after calling `statsig.initialize()`. Doing so puts Statsig in an undefined state and can cause a deadlock. The Python Core SDK uses internal threading and async runtime components that don't work correctly when copied across process boundaries. When a process forks after initialization, these components can become corrupted, leading to: - Deadlocks in event logging. - Hanging initialization calls. - Unpredictable SDK behavior. - Silent failures in feature evaluation. {% /callout %} ### Initializing with WSGI servers For production deployments using WSGI servers like uWSGI or Gunicorn, initialize Statsig **after** the worker processes are forked, not in the main process. {% tabs %} {% tab title="uWSGI" %} #### ✅ Correct: uWSGI example ```python expandable # app.py from statsig_python_core import Statsig, StatsigOptions from flask import Flask app = Flask(__name__) statsig = None def init_statsig(): global statsig if statsig is None: options = StatsigOptions() options.environment = "production" statsig = Statsig("your-server-secret-key", options) statsig.initialize().wait() # Initialize in each worker process @app.before_first_request def before_first_request(): init_statsig() @app.route('/') def index(): # Use statsig here return "Hello World" ``` ```ini # uwsgi.ini [uwsgi] module = app:app master = true processes = 4 # Statsig will be initialized in each worker process ``` {% /tab %} {% tab title="Gunicorn" %} #### ✅ Correct: Gunicorn example ```python # gunicorn_config.py def post_fork(server, worker): # Initialize Statsig after worker process is forked from app import init_statsig init_statsig() # as defined above # ...app.py ``` ```bash # Start Gunicorn with post-fork hook gunicorn --config gunicorn_config.py app:app ``` {% /tab %} {% tab title="FastAPI" %} #### ✅ Correct: FastAPI example ```python expandable from fastapi import FastAPI from statsig_python_core import Statsig, StatsigOptions app = FastAPI() statsig = None @app.on_event("startup") async def startup_event(): global statsig options = StatsigOptions() options.environment = "production" statsig = Statsig("your-server-secret-key", options) statsig.initialize().wait() @app.on_event("shutdown") async def shutdown_event(): if statsig: statsig.shutdown().wait() @app.get("/") async def root(): # Use statsig here return {"message": "Hello World"} ``` {% /tab %} {% /tabs %} `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ### Checking a Feature Flag/Gate After the SDK is initialized, you can fetch a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (equivalent to `return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example: ```python user = StatsigUser("a-user") if statsig.check_gate(user, "a_gate"): # Gate is on, enable new feature else: # Gate is off ``` ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and similar types) to clients based on user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The API is similar to Feature Gates, but returns a full JSON object configurable on the server from which you can fetch typed parameters. ```python # Get a dynamic config for a specific user config = statsig.get_dynamic_config(StatsigUser("my_user"), "a_config") # Access config values with type-safe getters and fallback values product_name = config.get_string("product_name", "Awesome Product v1") # returns String price = config.get_float("price", 10.0) # returns float should_discount = config.get_bool("discount", False) # returns bool quantity = config.get_integer("quantity", 1) # returns int64 # Advanced Usage: # You can disable exposure logging for this specific check options = DynamicConfigEvaluationOptions(disable_exposure_logging=True) config = statsig.get_dynamic_config(user, "a_config", options) # The config object also provides metadata about the evaluation print(config.rule_id) # The ID of the rule that served this config print(config.id_type) # The type of the evaluation (experiment, config, etc) ``` The `get_dynamic_config()` method returns a DynamicConfig object that allows you to: - Fetch typed values with fallback defaults using `get_string()`, `get_float()`, `get_boolean()`, and `get_integer()` - Access evaluation metadata through properties like `rule_id` and `id_type` - Configure evaluation behavior using `DynamicConfigEvaluationOptions` By default, Statsig automatically logs exposures when it evaluates configs. You can disable this for specific checks using the evaluation options. ### Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available. Statsig recommends [Layers](/experiments/layers-overview) because they make parameters reusable and support mutually exclusive experiments. ```Python # Values via get_layer layer = statsig.get_layer(StatsigUser("my_user"), "user_promo_experiments") title = layer.get_string("title", "Welcome to Statsig!") discount = layer.get_float("discount", 0.1) # Via get_experiment title_exp = statsig.get_experiment(StatsigUser("my_user"), "new_user_promo_title") price_exp = statsig.get_experiment(StatsigUser("my_user"), "new_user_promo_price") title = title_exp.get_string("title", "Welcome to Statsig!") discount = price_exp.get_float("discount", 0.1) ``` ### Retrieving Feature Gate Metadata In certain scenarios, you may need more information about a gate evaluation than just a boolean value. For additional metadata about the evaluation, use the Get Feature Gate API, which returns a FeatureGate object: ```python gate = statsig.get_feature_gate(user, "example_gate") print(gate.rule_id) print(gate.value) ``` ### Parameter Stores Use Parameter Stores when you want to define a parameter without deciding whether it should be a Feature Gate, Experiment, or Dynamic Config. Parameter Stores let you change the parameter type at any point in the Statsig console without a new deployment. Parameter Stores are optional, but parameterizing your application provides future flexibility and allows non-technical Statsig users to turn parameters into experiments. ```python # Get a Parameter Store by name param_store = statsig.get_parameter_store(user, "my_parameter_store") ``` ### Retrieving Parameter Values Parameter Store provides methods for retrieving values of different types with fallback defaults. ```python # String parameters string_value = param_store.get_string("string_param", "default_value") # Boolean parameters bool_value = param_store.get_bool("bool_param", False) # Numeric parameters float_value = param_store.get_float("float_param", 0.0) integer_value = param_store.get_integer("integer_param", 0) # Complex parameters default_array = ["item1", "item2"] array_value = param_store.get_array("array_param", default_array) default_map = {"key": "value"} map_value = param_store.get_map("map_param", default_map) ``` ### Evaluation Options You can disable exposure logging when retrieving a parameter store: ```python from statsig_python_core import ParameterStoreEvaluationOptions options = ParameterStoreEvaluationOptions(disable_exposure_logging=True) param_store = statsig.get_parameter_store(user, "my_parameter_store", options) ``` ### Logging an Event To track custom events, call the Log Event API. Specify the user, event name, and an optional value or metadata object: ```Python statsig.log_event( user=StatsigUser("user_id"), # Replace with your user object event_name="add_to_cart", value="SKU_12345", metadata={ "price": "9.99", "item_name": "diet_coke_48_pack" } ) ``` ### Sending Events to Log Explorer You can forward logs to Logs Explorer for convenient analysis using the Forward Log Line Event API. This lets you include custom metadata and event values with each log. ```python user = StatsigUser( user_id="a-user", custom={ "service": "my-service", "pod": "my-pod", "namespace": "my-namespace", "container": "my-container", # ...include any service-specific metadata } ) # levels: trace, debug, info, log, warn, error statsig.forward_log_line_event(user, "warn", "script failed to load", { "custom_metadata": "script_name:my-script" # ... include any event-specific metadata }) ``` ## Using shared instance To create a single Statsig instance accessible globally throughout your codebase, use the shared instance functionality, which provides a singleton pattern: ```python # Create a shared instance that can be accessed globally statsig = Statsig.new_shared("secret-key", options) statsig.initialize().wait() # Access the shared instance from anywhere in your code shared_statsig = Statsig.shared() is_feature_enabled = shared_statsig.check_gate(StatsigUser("user_id"), "feature_name") # Check if a shared instance exists if Statsig.has_shared_instance(): # Use the shared instance pass # Remove the shared instance when no longer needed Statsig.remove_shared() ``` The shared instance lets a single Statsig instance be created and accessed globally throughout your application. This is useful when multiple parts of the codebase need Statsig without passing an instance around. - `Statsig.new_shared(sdk_key, options)`: Creates a new shared instance of Statsig that can be accessed globally - `Statsig.shared()`: Returns the shared instance - `Statsig.has_shared_instance()`: Checks if a shared instance exists (useful when the shared instance may not be ready yet) - `Statsig.remove_shared()`: Removes the shared instance (useful when you want to switch to a new shared instance) {% callout type="note" %} `has_shared_instance()` and `remove_shared()` are helpful in specific scenarios but aren't required in most use cases where the shared instance is set up near the top of your application. Also note that only one shared instance can exist at a time. Attempting to create a second shared instance will result in an error. {% /callout %} ## Manual exposures By default, the SDK automatically logs an exposure event when you check a gate, get a config, get an experiment, or call get() on a parameter in a layer. To delay exposure logging (for example, to log only after the user actually uses the feature), use manual exposures. All main SDK functions (`check_gate`, `get_dynamic_config`, `get_experiment`, `get_layer`) accept an optional `disable_exposure_logging` parameter. When set to `True`, the SDK doesn't automatically log an exposure. You can then log the exposure manually at a later time: {% tabs %} {% tab title="Feature Gates" %} ```python result = statsig.check_gate(aUser, 'a_gate_name', FeatureGateEvaluationOptions(disable_exposure_logging=True)) ``` ```python statsig.manually_log_gate_exposure(aUser, 'a_gate_name') ``` {% /tab %} {% tab title="Dynamic Configs" %} ```python config = statsig.get_dynamic_config(aUser, 'a_dynamic_config_name', DynamicConfigEvaluationOptions(disable_exposure_logging=True)) ``` ```python statsig.manually_log_dynamic_config_exposure(aUser, 'a_dynamic_config_name') ``` {% /tab %} {% tab title="Experiments" %} ```python experiment = statsig.get_experiment(aUser, 'an_experiment_name', ExperimentEvaluationOptions(disable_exposure_logging=True)) ``` ```python statsig.manually_log_experiment_exposure(aUser, 'an_experiment_name') ``` {% /tab %} {% tab title="Layers" %} ```python layer = statsig.get_layer(aUser, 'a_layer_name', LayerEvaluationOptions(disable_exposure_logging=True)) paramValue = layer.get('a_param_name', 'fallback_value') ``` ```python statsig.manually_log_layer_parameter_exposure(aUser, 'a_layer_name', 'a_param_name') ``` {% /tab %} {% /tabs %} ## Statsig User The `StatsigUser` object represents a user in Statsig. You must provide a `userID` or at least one of the `customIDs` to identify the user. When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks) and to correctly measure the impact of experiments on metrics and events. As explained [here](/sdks/user#why-is-an-id-always-required-for-server-sdks), at least one identifier (userID or customID) is required to provide a consistent experience for a given user. In addition to userID, the top-level fields on StatsigUser are: email, ip, userAgent, country, locale, and appVersion. You can also pass any key-value pairs in an object or dictionary to the custom field and create targeting based on them. ### Private Attributes Private attributes are user attributes that Statsig uses for evaluation but doesn't forward to any integrations. They are useful for PII or sensitive data that you don't want to send to third-party services. ```python expandable from statsig_python_core import StatsigUser user = StatsigUser( user_id="a-user-id", email="user@example.com", ip="192.168.1.1", user_agent="Mozilla/5.0...", country="US", locale="en_US", app_version="1.0.0", custom={ # Custom fields "plan": "premium", "age": 25 }, custom_ids={ # Custom ID types "stable_id": "stable-id-123" }, private_attributes={ # Private attributes not forwarded to integrations "email": "private@example.com" } ) ``` ## Statsig Options You can pass an optional `options` parameter in addition to `sdkKey` during initialization to customize the Statsig client. {% accordion title="StatsigOptions" %} {% parameter name="specs_url" type="Optional[str]" %} Custom URL for fetching feature specifications. Provide the full endpoint URL, which the SDK uses as-is. {% /parameter %} {% parameter name="specs_sync_interval_ms" type="Optional[int]" %} How often the SDK updates specifications from Statsig servers (in milliseconds). {% /parameter %} {% parameter name="init_timeout_ms" type="Optional[int]" %} Sets the maximum timeout for initialization requests (in milliseconds). {% /parameter %} {% parameter name="log_event_url" type="Optional[str]" %} Custom URL for logging events. {% /parameter %} {% parameter name="disable_all_logging" type="Optional[bool]" %} When `true`, disables all event logging. {% /parameter %} {% parameter name="disable_network" type="Optional[bool]" %} When `true`, disables all network functions: event & exposure logging, spec downloads, and ID List downloads. Formerly called "localMode". {% /parameter %} {% parameter name="event_logging_flush_interval_ms" type="Optional[int]" %} How often events are flushed to Statsig servers (in milliseconds). {% /parameter %} {% parameter name="event_logging_max_queue_size" type="Option<u32>" %} Maximum number of events to queue before forcing a flush. - Default is `2000` - event_logging_max_queue_size \* event_logging_max_pending_batch_queue_size is the upper limit on how many events are queued - See also `event_logging_max_pending_batch_queue_size` {% /parameter %} {% parameter name="event_logging_max_pending_batch_queue_size" type="Option<u32>" %} Maximum number of event batches to hold in buffer to retry. - Default is `100`. - event_logging_max_queue_size \* event_logging_max_pending_batch_queue_size is the upper limit on how many events are queued - eg: 2000 \* 100 means the SDK can process 200k event per second before events start getting dropped - See also `event_logging_max_queue_size`. {% /parameter %} {% parameter name="enable_id_lists" type="Optional[bool]" %} Enable/disable ID list functionality. **Required to be `true` when using segments with more than 1000 IDs.** See [ID List segments](/segments/add-id-list) for more details. {% /parameter %} {% parameter name="disable_user_agent_parsing" type="Optional[bool]" %} If set to true, the SDK doesn't attempt to parse UserAgents (attached to the user object) into browserName, browserVersion, systemName, systemVersion, and appVersion at evaluation time, when needed for evaluation. {% /parameter %} {% parameter name="wait_for_user_agent_init" type="Optional[bool]" %} When set to true, the SDK waits until user agent parsing data is fully loaded during initialization. This may slow down startup by ~1 second but ensures that parsing of the user's userAgent string into fields like browserName, browserVersion, systemName, systemVersion, and appVersion is ready before any evaluations. {% /parameter %} {% parameter name="disable_country_lookup" type="Optional[bool]" %} If set to true, the SDK doesn't attempt to parse IP addresses (attached to the user object at user.ip) into Country codes at evaluation time, when needed for evaluation. {% /parameter %} {% parameter name="wait_for_country_lookup_init" type="Optional[bool]" %} When set to true, the SDK waits for country lookup data (e.g., GeoIP or YAML files) to fully load during initialization. This may slow down startup by ~1 second but ensures that IP-to-country parsing is ready at evaluation time. {% /parameter %} {% parameter name="id_lists_url" type="Optional[str]" %} Custom URL for fetching the ID list manifest (the `get_id_lists` endpoint). Provide the full endpoint URL, which the SDK uses as-is. {% /parameter %} {% parameter name="download_id_list_file_api" type="Optional[str]" %} Origin (scheme and host only, no path) for downloading individual ID list files. Unlike `specs_url` and `id_lists_url`, which take a full endpoint URL, this takes an origin only: the SDK appends each per-file path from the ID list manifest. Setting a full path (for example, one ending in `/v1/download_id_list_file`) produces a doubled path and a 401. Overriding only `specs_url` and `id_lists_url` leaves individual ID list file downloads going to the Statsig CDN; set this option to route them through your proxy. {% /parameter %} {% parameter name="id_lists_sync_interval_ms" type="Optional[int]" %} How often the SDK updates ID lists from Statsig servers (in milliseconds). {% /parameter %} {% parameter name="fallback_to_statsig_api" type="Optional[bool]" %} Whether to fall back to the Statsig API if custom endpoints fail. {% /parameter %} {% parameter name="environment" type="Optional[str]" %} Environment parameter for evaluation. {% /parameter %} {% parameter name="output_log_level" type="Optional[str]" %} Controls the verbosity of SDK logs. {% /parameter %} {% parameter name="persistent_storage" type="Optional[PersistentStorage]" %} Adapter / Interface to use persistent assignment within SDK. Refer to [Persistent Assignment](/server/concepts/persistent_assignment/) for more details. {% /parameter %} {% parameter name="observability_client" type="Optional[ObservabilityClient]" %} Adapter to monitor the health of the SDK. Refer to [SDK Monitoring](/infrastructure/sdk-monitoring) for more details. {% /parameter %} {% parameter name="data_store" type="Optional[DataStore]" %} Custom data store implementation for storing and retrieving configuration data. Used for advanced caching or storage strategies. {% /parameter %} {% parameter name="event_logging_max_pending_batch_queue_size" type="Optional[int]" %} Maximum number of batches of events to hold in buffer to retry. {% /parameter %} {% parameter name="global_custom_fields" type="Optional[Dict]" %} Custom fields to include in all events logged by the SDK. {% /parameter %} {% parameter name="config_compression_mode" type="Optional[str]" %} Compression method for exposure logging. Options: "gzip", "dictionary" {% /parameter %} {% parameter name="proxy_config" type="Optional[ProxyConfig]" %} Configuration for connecting through a proxy server. The `ProxyConfig` object has the following properties: - `proxy_host`: Optional string specifying the proxy server host - `proxy_port`: Optional number specifying the proxy server port - `proxy_auth`: Optional string for proxy authentication (format: "username:password") - `proxy_protocol`: Optional string specifying the protocol (e.g., "http", "https") - `ca_cert_path`: Optional path to a PEM CA bundle for outbound TLS {% /parameter %} {% parameter name="spec_adapter_configs" type="Optional[List[SpecAdapterConfig]]" %} Advanced configuration for customizing how the SDK fetches feature specifications. Allows you to configure multiple spec adapters with different priorities and settings. Each `SpecAdapterConfig` object has the following properties: - `adapter_type`: String specifying the adapter type (e.g., "http", "grpc") - `specs_url`: Optional custom URL for fetching specifications - `init_timeout_ms`: Optional timeout for initialization (in milliseconds) {% callout type="note" %} **Migration note:** This parameter was previously named `init_resources` in earlier versions. If you're upgrading from an older version, replace `init_resources` with `spec_adapter_configs`. {% /callout %} {% /parameter %} {% /accordion %} ### Proxy and Custom Network Routing Use `proxy_config` if your service needs a standard outbound HTTP proxy. Use `spec_adapter_configs` if you need to route spec downloads through [Statsig Forward Proxy](/infrastructure/api_proxy/introduction) or another custom spec source. ```python from statsig_python_core import ProxyConfig, Statsig, StatsigOptions proxy_config = ProxyConfig( proxy_host="proxy.example.com", proxy_port=8080, proxy_protocol="https", ca_cert_path="/etc/ssl/certs/corporate-ca.pem", # Optional ) options = StatsigOptions() options.proxy_config = proxy_config statsig = Statsig("secret-key", options) statsig.initialize().wait() ``` Set `ca_cert_path` when your environment requires a custom PEM CA bundle for outbound TLS. ### Using spec_adapter_configs with Multiple Sources ```python from statsig_python_core import StatsigOptions, SpecAdapterConfig # Configure multiple spec adapters with priority order # First source: Statsig CDN primary_adapter = SpecAdapterConfig( adapter_type="http", specs_url="https://api.statsigcdn.com/v2/download_config_specs", init_timeout_ms=3000 ) # Second source: Data Store adapter # The SDK will try this source if the primary source fails data_store_adapter = SpecAdapterConfig( adapter_type="data_store", init_timeout_ms=5000 ) options = StatsigOptions() options.spec_adapter_configs = [primary_adapter, data_store_adapter] options.environment = "production" statsig = Statsig("secret-key", options) statsig.initialize().wait() ``` ## Shutting Statsig down Statsig batches and periodically flushes events. To ensure all logged events are flushed before shutdown, call `shutdown()` before your app or server shuts down: ```python statsig.shutdown().wait() ``` ## Local overrides Local Overrides let you override the values of gates, configs, experiments, and layers for testing. This is useful for local development or testing when you want to force a specific value without changing the configuration in the Statsig console. ```python # Overrides the given gate to the specified value statsig.override_gate("a_gate_name", True) # Overrides the given dynamic config to the provided value statsig.override_dynamic_config("a_config_name", {"key": "value"}) # Overrides the given experiment to the provided value statsig.override_experiment("an_experiment_name", {"key": "value"}) # Overrides the given layer to the provided value statsig.override_layer("a_layer_name", {"key": "value"}) # Overrides the given experiment to a particular groupname, available for experiments only: statsig.override_experiment_by_group_name("an_experiment_name", "a_group_name") ``` ## Client SDK bootstrapping | SSR If you use the Statsig client SDK in a browser or mobile app, you can bootstrap the client SDK with values from the server SDK to avoid a network request on the client. This is useful for server-side rendering (SSR) or to reduce network requests on the client. ## Client initialize response The Python Core SDK provides a method to generate a client initialize response that can be used to bootstrap client SDKs without requiring network requests. ```python import json from statsig_python_core import Statsig, StatsigUser # Get client initialize response for a user response_data = statsig.get_client_initialize_response(user) response = json.loads(response_data) # Pass response to a client SDK to initialize without a network request ``` {% accordion-group %} {% accordion title="Initialize Response Parameters" %} The `get_client_initialize_response` method accepts the following parameters: ```python def get_client_initialize_response( user: StatsigUser, hash: Optional[str] = None, client_sdk_key: Optional[str] = None, include_local_overrides: Optional[bool] = None ) -> str: ``` - **`user`**: `StatsigUser` - The user to generate the initialize response for - **`hash`**: `Optional[str]` - Algorithm used for hashing gate/experiment names (default: 'djb2') - **`client_sdk_key`**: `Optional[str]` - Client SDK key to use for initialization - **`include_local_overrides`**: `Optional[bool]` - Whether to include local overrides in the response {% /accordion %} {% accordion title="Hash Algorithm" %} The `hash` parameter specifies which algorithm to use for hashing gate and experiment names in the client initialize response. The default is `'djb2'` for better performance and smaller payload size. Available options: - `'djb2'` (default) - DJB2 hashing algorithm for better performance - `'sha256'` - SHA-256 hashing algorithm - `'none'` - No hashing applied ```python # Use djb2 hashing algorithm (default) response_data = statsig.get_client_initialize_response(user, hash='djb2') # Use SHA-256 hashing algorithm response_data = statsig.get_client_initialize_response(user, hash='sha256') # Disable hashing response_data = statsig.get_client_initialize_response(user, hash='none') ``` {% /accordion %} {% accordion title="Client SDK Key" %} The `client_sdk_key` parameter lets you filter the response to only the specific feature gates, experiments, dynamic configs, layers, or parameter stores that a particular client key has access to, effectively letting you apply [target apps](/sdks/target-apps). ```python # Specify a client SDK key response_data = statsig.get_client_initialize_response( user, client_sdk_key='client-key' ) ``` {% /accordion %} {% accordion title="Local Overrides" %} The `include_local_overrides` parameter determines whether to consider [local overrides](#local-overrides) you've set when evaluating each config in the response. ```python # Include local overrides in the response response_data = statsig.get_client_initialize_response( user, include_local_overrides=True ) ``` {% /accordion %} {% accordion title="Full Code Example" %} Below is a complete example of using the client initialize response to bootstrap a client SDK. You can parallelize or inline the initialize response data with other requests to your server to eliminate additional round trips and latency. ```python # Server-side code import json from statsig_python_core import Statsig, StatsigUser, StatsigOptions from flask import Flask, request, jsonify app = Flask(__name__) # Initialize the server SDK options = StatsigOptions() statsig = Statsig('server-secret-key', options) statsig.initialize().wait() # In your API endpoint handler @app.route('/statsig-bootstrap') def statsig_bootstrap(): # Create a user object from the request user = StatsigUser( user_id=request.args.get('userID', ''), email=request.args.get('email'), ip=request.remote_addr, user_agent=request.headers.get('User-Agent') ) # Generate the client initialize response response_data = statsig.get_client_initialize_response( user, hash='djb2', client_sdk_key='client-sdk-key' ) # Parse the JSON response statsig_values = json.loads(response_data) # Return the values to the client return jsonify({'statsigValues': statsig_values}) ``` ```javascript // Client-side code using @statsig/js-client import { Statsig } from "@statsig/js-client"; // Fetch bootstrap values from your API const response = await fetch("/statsig-bootstrap"); const { statsigValues } = await response.json(); // Initialize the client SDK with the bootstrap values await Statsig.initialize({ sdkKey: "client-sdk-key", initializeValues: statsigValues, }); ``` {% /accordion %} {% accordion title="Response Format" %} The method returns a JSON string containing the client initialize response. You'll need to parse this string to access the data: ```python response_data = statsig.get_client_initialize_response(user) response = json.loads(response_data) # Access different parts of the response feature_gates = response.get('feature_gates', {}) dynamic_configs = response.get('dynamic_configs', {}) layer_configs = response.get('layer_configs', {}) ``` The response includes: - `feature_gates`: Feature gate evaluations for the user - `dynamic_configs`: Dynamic config and experiment evaluations - `layer_configs`: Layer evaluations - `has_updates`: Boolean indicating if there are updates - `time`: Timestamp of the response {% /accordion %} {% /accordion-group %} ## Persistent storage The Persistent Storage interface lets you implement custom storage for user-specific configurations. This enables you to persist user assignments across sessions, ensuring consistent experiment groups when a user returns. This is useful for client-side A/B testing where users must always receive the same variant. ```python class PersistentStorage(PersistentStorageBaseClass): def __init__(): # When you initialize, remember to call super.__init__() super().__init__() self.load_fn = self.load self.save_fn = self.save self.delete_fn = self.delete def load(self, key: str) -> Optional[UserPersistedValues]: """ Load persisted values for a user from storage Args: key: A string key that uniquely identifies a user Returns: Dictionary mapping config names to their persisted values """ pass def save(self, key: str, config_name: str, data: StickyValues): """ Save a persistent value for a user Args: key: A string key that uniquely identifies a user config_name: The name of the config/experiment data: The values to persist """ pass def delete(self, key: str, config_name: str): """ Delete a persistent value for a user Args: key: A string key that uniquely identifies a user config_name: The name of the config/experiment to delete """ pass ``` ## Data store The Data Store interface lets you implement custom storage for Statsig configurations, enabling advanced caching strategies and integration with your preferred storage systems. ```python class DataStore(DataStoreBase): def initialize(self): """ Initialize the data store. Called when the Statsig client initializes. """ pass def shutdown(self): """ Clean up resources when the Statsig client shuts down. """ pass def get(self, key: str) -> Optional[DataStoreResponse]: """ Retrieve value from the data store. Args: key: The key to retrieve the value for Returns: DataStoreResponse containing the result and time """ pass def set(self, key: str, value: str, time: Optional[int] = None): """ Store a value in the data store. Args: key: The key to store the value under value: The value to store time: Optional timestamp """ pass def support_polling_updates_for(self, key: str) -> bool: """ Whether the data store supports polling for updates for the given key. Args: key: The key to check Returns: True if polling is supported, False otherwise """ return False ``` ## Custom output logger The Output Logger Provider interface lets you customize how the SDK logs internal messages. ```python class OutputLoggerProvider(OutputLoggerProviderBase): def init(self): """ Initialize the logger. Called when the Statsig client initializes. """ pass def debug(self, tag: str, msg: str): """ Log a debug message. Args: tag: Category/component tag for the message msg: The message to log """ pass def info(self, tag: str, msg: str): """ Log an info message. Args: tag: Category/component tag for the message msg: The message to log """ pass def warn(self, tag: str, msg: str): """ Log a warning message. Args: tag: Category/component tag for the message msg: The message to log """ pass def error(self, tag: str, msg: str): """ Log an error message. Args: tag: Category/component tag for the message msg: The message to log """ pass def shutdown(self): """ Clean up resources when the Statsig client shuts down. """ pass ``` ## Observability client The Observability Client interface lets you monitor the health of the SDK by integrating with your own observability systems. This enables you to track metrics, errors, and performance data. For more information on the metrics emitted by Statsig SDKs, refer to the [Monitoring documentation](/infrastructure/sdk-monitoring). ```python class ObservabilityClient(ObservabilityClientBase): def init(self): """ Initialize the observability client. Called when the Statsig client initializes. """ pass def increment(self, metric_name: str, value: float, tags: Optional[Dict[str, str]] = None): """ Report a counter metric. Args: metric_name: The name of the metric value: The amount to increment by tags: Optional tags to associate with the metric """ pass def gauge(self, metric_name: str, value: float, tags: Optional[Dict[str, str]] = None): """ Report a gauge metric. Args: metric_name: The name of the metric value: The current value tags: Optional tags to associate with the metric """ pass def dist(self, metric_name: str, value: float, tags: Optional[Dict[str, str]] = None): """ Report a distribution metric. Args: metric_name: The name of the metric value: The value to record tags: Optional tags to associate with the metric """ pass def error(self, tag: str, error: str): """ Report an error. Args: tag: Category/component tag for the error error: The error message """ pass def should_enable_high_cardinality_for_this_tag(self, tag: str) -> bool: """ Determine if high cardinality should be enabled for a tag. Args: tag: The tag to check Returns: True if high cardinality should be enabled, False otherwise """ pass ``` ## FAQ {% accordion-group %} {% accordion title="How do I run experiments for logged out users?" %} Refer to the guide on [device level experiments](/guides/first-device-level-experiment). {% /accordion %} {% /accordion-group %} ## Reference ### API Methods - `check_gate(user: StatsigUser, gate_name: str, options: Optional[FeatureGateEvaluationOptions] = None) -> bool` - `get_dynamic_config(user: StatsigUser, config_name: str, options: Optional[DynamicConfigEvaluationOptions] = None) -> DynamicConfig` - `get_experiment(user: StatsigUser, experiment_name: str, options: Optional[ExperimentEvaluationOptions] = None) -> DynamicConfig` - `get_layer(user: StatsigUser, layer_name: str, options: Optional[LayerEvaluationOptions] = None) -> Layer` - `get_feature_gate(user: StatsigUser, gate_name: str, options: Optional[FeatureGateEvaluationOptions] = None) -> FeatureGate` - `get_parameter_store(user: StatsigUser, parameter_store_name: str, options: Optional[ParameterStoreEvaluationOptions] = None) -> ParameterStore` - `log_event(user: StatsigUser, event_name: str, value: Optional[Union[str, float]] = None, metadata: Optional[Dict[str, str]] = None) -> None` - `manually_log_gate_exposure(user: StatsigUser, gate_name: str) -> None` - `manually_log_dynamic_config_exposure(user: StatsigUser, config_name: str) -> None` - `manually_log_experiment_exposure(user: StatsigUser, experiment_name: str) -> None` - `manually_log_layer_parameter_exposure(user: StatsigUser, layer_name: str, parameter_name: str) -> None` - `override_experiment_by_group_name(experiment_name: str, group_name: str, id: Optional[str] = None) -> None` - `get_client_initialize_response(user: StatsigUser, options: Optional[ClientInitializeResponseOptions] = None) -> ClientInitializeResponse` - `shutdown() -> AsyncResult[None]` ### Fields Needed Methods The following methods return information about which user fields are needed for evaluation: - `get_gate_fields_needed(gate_name: str) -> List[str]` - `get_dynamic_config_fields_needed(config_name: str) -> List[str]` - `get_experiment_fields_needed(experiment_name: str) -> List[str]` - `get_layer_fields_needed(layer_name: str) -> List[str]` These methods return a list of strings representing the user fields that are required to properly evaluate the specified gate, config, experiment, or layer. ================================================================================ # Java Server SDK URL: https://docs.statsig.com/server-core/java-core Updated: 2026-07-11 ================================================================================ {% callout type="tip" %} Migrating from the Legacy Java SDK? Refer to the [Migration Guide](/server-core/migration-guides/java) . {% /callout %} ## Setup the SDK {% steps %} {% step title="Install the SDK" %} ## Requirements * Java 8 or higher (Java 8 support added in version 0.4.3) * Compatible with all platforms listed in the Supported OS and Architecture Combinations section, including: * macOS (x86\_64, arm64) * Windows (x86\_64) * Amazon Linux 2 and 2023 (x86\_64, arm64) ## Overview The Statsig Java SDK can be installed in two ways: **Recommended: Single JAR installation** (since version 0.4.0) * Use the "uber" JAR which contains both the core library and popular platform-specific native libraries in a single package * Simplifies dependency management and deployment across different environments **Advanced: Two-part installation** 1. The platform-independent core library 2. An OS/architecture-specific native library for your specific platform ## Installation steps ### Recommended: Using the Uber JAR (All-in-One) Since version 0.4.0, Statsig provides an "uber" JAR that contains both the core library and native libraries for popular supported platforms in a single package. This is the recommended approach for most users. {% codetabs %} ```groovy Gradle repositories { mavenCentral() } dependencies { implementation 'com.statsig:javacore:X.X.X:uber' // Uber JAR with all native libraries } ``` ```xml Maven <dependencies> <dependency> <groupId>com.statsig</groupId> <artifactId>javacore</artifactId> <version>X.X.X</version> <classifier>uber</classifier> </dependency> </dependencies> ``` {% /codetabs %} You can find the latest version on [Maven Central](https://central.sonatype.com/artifact/com.statsig/javacore). The uber JAR includes native libraries for: * Linux (x86\_64, arm64) * macOS (x86\_64, arm64) * Windows (x86\_64) This approach eliminates the need to specify the exact platform and simplifies deployment across different environments. ### Advanced: Platform-specific installation If you need more control over dependencies or want to minimize the JAR size for a specific platform, you can use the platform-specific installation approach. {% steps %} {% step title="Install Core Library" %} {% codetabs %} ```groovy Gradle repositories { mavenCentral() } dependencies { implementation 'com.statsig:javacore:X.X.X' // Replace X.X.X with the latest version } ``` ```xml Maven <dependencies> <dependency> <groupId>com.statsig</groupId> <artifactId>javacore</artifactId> <version>X.X.X</version> <!-- Replace X.X.X with the latest version --> </dependency> </dependencies> ``` {% /codetabs %} You can find the latest version on [Maven Central](https://central.sonatype.com/artifact/com.statsig/javacore). {% /step %} {% step title="Install Platform-Specific Library" %} You need to add the appropriate OS/architecture-specific dependency. Choose one of the following methods: **Method 1: Automatic Detection** Run the following code to detect your system and get the appropriate dependency: ```java import com.statsig.*; // All StatsigOptions are optional, feel free to adjust them as needed StatsigOptions options = new StatsigOptions.Builder().build(); Statsig statsig = new Statsig("your-secret-key", options); ``` You'll receive output similar to: ``` For Linux with arm64 architecture, add the following to build.gradle: implementation 'com.statsig:javacore:<version>:aarch64-unknown-linux-gnu' For Linux with x86_64 architecture, add the following to build.gradle: implementation 'com.statsig:javacore:<version>:x86_64-unknown-linux-gnu' ``` **Method 2: Manual Configuration** {% codetabs %} ```groovy Gradle dependencies { implementation 'com.statsig:javacore:X.X.X' // Core SDK (from Step 1) implementation 'com.statsig:javacore:X.X.X:YOUR-OS-ARCHITECTURE' // OS/architecture-specific dependency } ``` ```xml Maven <dependencies> <dependency> <groupId>com.statsig</groupId> <artifactId>javacore</artifactId> <version>X.X.X</version> </dependency> <dependency> <groupId>com.statsig</groupId> <artifactId>javacore</artifactId> <version>X.X.X</version> <classifier>YOUR-OS-ARCHITECTURE</classifier> </dependency> </dependencies> ``` {% /codetabs %} Replace `YOUR-OS-ARCHITECTURE` with one of the supported combinations from the Supported OS and Architecture Combinations section. {% /step %} {% /steps %} {% callout type="warning" %} **Docker Considerations for Alpine Linux** When using Alpine Linux or other musl-based Docker containers, you need to install additional compatibility packages for the native libraries to work properly. Add the following to your Dockerfile: ```dockerfile RUN apk add --no-cache libgcc gcompat ``` The Statsig Java Core SDK automatically detects musl-based systems and will use the appropriate musl-compatible native libraries (e.g., `x86_64-unknown-linux-musl`, `aarch64-unknown-linux-musl`). {% /callout %} {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Server Secret Keys should always be kept private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} An optional `options` parameter accepts a `StatsigOptions` object to customize the SDK. {% codetabs %} ```java Java import com.statsig.*; // All StatsigOptions are optional, feel free to adjust them as needed StatsigOptions options = new StatsigOptions.Builder() .setSpecsSyncIntervalMs(10000) .setEventLoggingFlushIntervalMs(10000) .setOutputLoggerLevel(OutputLogger.LogLevel.INFO) .build(); Statsig myStatsigServer = new Statsig(SECRET_KEY, options); myStatsigServer.initialize().get(); ``` ```kotlin Kotlin import com.statsig.* // All StatsigOptions are optional, feel free to adjust them as needed val options = StatsigOptions.Builder() .setSpecsSyncIntervalMs(10000) .setEventLoggingFlushIntervalMs(10000) .setOutputLoggerLevel(OutputLogger.LogLevel.INFO) .build() val myStatsigServer = Statsig(SECRET_KEY, options) myStatsigServer.initialize().get() ``` {% /codetabs %} `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independent of your API calls. {% /step %} {% /steps %} ## Getting started ### Quick start example {% steps %} {% step title="Create a new Java project" %} Create a new Gradle or Maven project with the following structure: ``` my-statsig-app/ ├── build.gradle (or pom.xml) └── src/main/java/ExampleApp.java ``` {% /step %} {% step title="Add Statsig dependency" %} {% codetabs %} ```groovy build.gradle plugins { id 'java' id 'application' } repositories { mavenCentral() } dependencies { implementation 'com.statsig:javacore:X.X.X:uber' } application { mainClass = 'ExampleApp' } ``` ```xml pom.xml <project> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>my-statsig-app</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.statsig</groupId> <artifactId>javacore</artifactId> <version>X.X.X</version> <classifier>uber</classifier> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.0.0</version> <configuration> <mainClass>ExampleApp</mainClass> </configuration> </plugin> </plugins> </build> </project> ``` {% /codetabs %} Replace `X.X.X` with the latest version from [Maven Central](https://central.sonatype.com/artifact/com.statsig/javacore). {% /step %} {% step title="Write your application code" %} {% codetabs %} ```java ExampleApp.java import com.statsig.*; public class ExampleApp { public static void main(String[] args) throws Exception { // Initialize Statsig StatsigOptions options = new StatsigOptions.Builder().build(); Statsig statsig = new Statsig("YOUR_SERVER_SECRET_KEY", options); statsig.initialize().get(); try { // Check a feature gate boolean isEnabled = statsig.checkGate("user123", "my_feature_gate"); System.out.println("Feature gate is " + (isEnabled ? "enabled" : "disabled")); // Get a config DynamicConfig config = statsig.getConfig("user123", "my_config"); System.out.println("Config value: " + config.getString("some_parameter", "default_value")); } finally { // Always shutdown Statsig when done statsig.shutdown(); } } } ``` ```kotlin ExampleApp.kt import com.statsig.* fun main() { // Initialize Statsig val options = StatsigOptions.Builder().build() val statsig = Statsig("YOUR_SERVER_SECRET_KEY", options) statsig.initialize().get() try { // Check a feature gate val isEnabled = statsig.checkGate("user123", "my_feature_gate") println("Feature gate is ${if (isEnabled) "enabled" else "disabled"}") // Get a config val config = statsig.getConfig("user123", "my_config") println("Config value: ${config.getString("some_parameter", "default_value")}") } finally { // Always shutdown Statsig when done statsig.shutdown().get() } } ``` {% /codetabs %} Replace `YOUR_SERVER_SECRET_KEY` with your actual server secret key from the [Statsig Console](https://console.statsig.com/). {% /step %} {% step title="Run the application" %} {% codetabs %} ```bash Gradle ./gradlew run ``` ```bash Maven mvn compile exec:java ``` {% /codetabs %} If everything is set up correctly, you should see output related to your feature gate and configuration. {% /step %} {% /steps %} ## Working with the SDK ### Checking a Feature Flag/Gate After the SDK is initialized, you can fetch a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are **CLOSED** or **OFF** (equivalent to `return false;`) by default. All APIs require a user object (refer to [Statsig user](#statsig-user)). To check a gate for a user: {% codetabs %} ```java Java String userID = "user_id"; boolean result = statsig.checkGate(userID, "my_feature_gate"); // with StatsigUser StatsigUser user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build(); boolean gateResult = statsig.checkGate(user, "my_feature_gate"); ``` ```kotlin Kotlin val userID = "user_id" val result = statsig.checkGate(userID, "my_feature_gate") // with StatsigUser val user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build() val gateResult = statsig.checkGate(user, "my_feature_gate") ``` {% /codetabs %} ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional user targeting. To send different values (strings, numbers, etc.) to clients based on user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The API is similar to Feature Gates, but returns a JSON object from which you can retrieve typed parameters. For example: {% codetabs %} ```java Java String userID = "user_id"; DynamicConfig config = statsig.getConfig(userID, "my_config"); String name = config.getString("name", ""); int size = config.getInt("size", 10); // with StatsigUser StatsigUser user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build(); DynamicConfig dynamicConfig = statsig.getConfig(user, "my_config"); ``` ```kotlin Kotlin val userID = "user_id" val config = statsig.getConfig(userID, "my_config") val name = config.getString("name", "") val size = config.getInt("size", 10) // with StatsigUser val user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build() val dynamicConfig = statsig.getConfig(user, "my_config") ``` {% /codetabs %} ### Getting a Layer/Experiment **Layers/Experiments** let you run A/B/n experiments. Two APIs are available, but [layers](/experiments/layers-overview) are recommended because layers make parameters reusable and support mutually exclusive experiments. {% codetabs %} ```java Java String userID = "user_id"; // Getting an Experiment Experiment experiment = statsig.getExperiment(userID, "my_experiment"); String expName = experiment.getString("experiment_param", ""); // Getting a Layer Layer layer = statsig.getLayer(userID, "my_layer"); String layerValue = layer.getString("layer_param", "default"); // with StatsigUser StatsigUser user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build(); Experiment experimentWithUser = statsig.getExperiment(user, "my_experiment"); Layer layerWithUser = statsig.getLayer(user, "my_layer"); ``` ```kotlin Kotlin val userID = "user_id" // Getting an Experiment val experiment = statsig.getExperiment(userID, "my_experiment") val expName = experiment.getString("experiment_param", "") // Getting a Layer val layer = statsig.getLayer(userID, "my_layer") val layerValue = layer.getString("layer_param", "default") // with StatsigUser val user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build() val experimentWithUser = statsig.getExperiment(user, "my_experiment") val layerWithUser = statsig.getLayer(user, "my_layer") ``` {% /codetabs %} ### Retrieving Feature Gate Metadata To retrieve more information about a gate evaluation than a boolean value, use the Get Feature Gate API, which returns a `FeatureGate` object: {% codetabs %} ```java Java String userID = "user_id"; FeatureGate gate = statsig.getFeatureGate(userID, "my_feature_gate"); System.out.println("Gate name: " + gate.name); System.out.println("Gate value: " + gate.value); System.out.println("Rule ID: " + gate.ruleID); // with StatsigUser StatsigUser user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build(); FeatureGate gateWithUser = statsig.getFeatureGate(user, "my_feature_gate"); ``` ```kotlin Kotlin val userID = "user_id" val gate = statsig.getFeatureGate(userID, "my_feature_gate") println("Gate name: ${gate.name}") println("Gate value: ${gate.value}") println("Rule ID: ${gate.ruleID}") // with StatsigUser val user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build() val gateWithUser = statsig.getFeatureGate(user, "my_feature_gate") ``` {% /codetabs %} ### Parameter stores If you want dynamic control over whether a value comes from a Feature Gate, Experiment, or Dynamic Config outside your deployment cycle, use Parameter Stores. A Parameter Store lets you define a parameter that you can change at any point in the Statsig console. Parameter Stores are optional, but parameterizing your application supports future flexibility and allows non-technical Statsig users to turn parameters into experiments. {% codetabs %} ```java Java String userID = "user_id"; ParameterStore store = statsig.getParameterStore(userID, "my_param_store"); String stringValue = store.getString("string_param", "default"); int intValue = store.getInt("int_param", 0); boolean boolValue = store.getBoolean("bool_param", false); double doubleValue = store.getDouble("double_param", 0.0); // with StatsigUser StatsigUser user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build(); ParameterStore storeWithUser = statsig.getParameterStore(user, "my_param_store"); ``` ```kotlin Kotlin val userID = "user_id" val store = statsig.getParameterStore(userID, "my_param_store") val stringValue = store.getString("string_param", "default") val intValue = store.getInt("int_param", 0) val boolValue = store.getBoolean("bool_param", false) val doubleValue = store.getDouble("double_param", 0.0) // with StatsigUser val user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build() val storeWithUser = statsig.getParameterStore(user, "my_param_store") ``` {% /codetabs %} ### Logging an Event After setting up a Feature Gate or Experiment, track custom events to measure how features or experiment groups affect user behavior. Call the Log Event API and specify the user and event name. You can also provide a value and metadata: {% codetabs %} ```java Java import java.util.HashMap; import java.util.Map; String userID = "user_id"; String eventName = "my_custom_event"; // Simple event statsig.logEvent(userID, eventName); // Event with value statsig.logEvent(userID, eventName, 10.5); // Event with metadata Map<String, String> metadata = new HashMap<>(); metadata.put("key1", "value1"); metadata.put("key2", "value2"); statsig.logEvent(userID, eventName, metadata); // Event with value and metadata statsig.logEvent(userID, eventName, 10.5, metadata); // with StatsigUser StatsigUser user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build(); statsig.logEvent(user, eventName, 10.5, metadata); ``` ```kotlin Kotlin val userID = "user_id" val eventName = "my_custom_event" // Simple event statsig.logEvent(userID, eventName) // Event with value statsig.logEvent(userID, eventName, 10.5) // Event with metadata val metadata = mapOf( "key1" to "value1", "key2" to "value2" ) statsig.logEvent(userID, eventName, metadata) // Event with value and metadata statsig.logEvent(userID, eventName, 10.5, metadata) // with StatsigUser val user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build() statsig.logEvent(user, eventName, 10.5, metadata) ``` {% /codetabs %} ### Sending events to Log Explorer You can forward logs to Logs Explorer for convenient analysis using the Forward Log Line Event API. This lets you include custom metadata and event values with each log. {% codetabs %} ```java Java import java.util.HashMap; import java.util.Map; String userID = "user_id"; Map<String, Object> payload = new HashMap<>(); payload.put("log_level", "error"); payload.put("message", "Something went wrong"); payload.put("timestamp", System.currentTimeMillis()); statsig.forwardLogLineEvent(userID, payload); // with StatsigUser StatsigUser user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build(); statsig.forwardLogLineEvent(user, payload); ``` ```kotlin Kotlin val userID = "user_id" val payload = mapOf( "log_level" to "error", "message" to "Something went wrong", "timestamp" to System.currentTimeMillis() ) statsig.forwardLogLineEvent(userID, payload) // with StatsigUser val user = StatsigUser.builder() .userID("user_id") .email("my_user@example.com") .build() statsig.forwardLogLineEvent(user, payload) ``` {% /codetabs %} ## Using shared instance To access a single Statsig instance globally throughout your codebase, use the shared instance singleton pattern: {% codetabs %} ```java Java import com.statsig.*; // Create a shared instance that can be accessed globally StatsigServer statsig = Statsig.newShared("secret-key"); statsig.initialize().get(); // Access the shared instance from anywhere in your code StatsigServer sharedStatsig = Statsig.shared(); boolean isFeatureEnabled = sharedStatsig.checkGate(user, "feature_name"); // Check if a shared instance exists if (Statsig.hasSharedInstance()) { // Use the shared instance } // Remove the shared instance when no longer needed Statsig.removeShared(); ``` ```kotlin Kotlin import com.statsig.* // Create a shared instance that can be accessed globally val statsig = Statsig.newShared("secret-key") statsig.initialize().get() // Access the shared instance from anywhere in your code val sharedStatsig = Statsig.shared() val isFeatureEnabled = sharedStatsig.checkGate(user, "feature_name") // Check if a shared instance exists if (Statsig.hasSharedInstance()) { // Use the shared instance } // Remove the shared instance when no longer needed Statsig.removeShared() ``` {% /codetabs %} * `Statsig.newShared(sdkKey, options)`: Creates a new shared instance of Statsig that can be accessed globally * `Statsig.shared()`: Returns the shared instance * `Statsig.hasSharedInstance()`: Checks if a shared instance exists (useful when you aren't sure if the shared instance is ready yet) * `Statsig.removeShared()`: Removes the shared instance (useful when you want to switch to a new shared instance) {% callout type="note" %} `hasSharedInstance()` and `removeShared()` are helpful in specific scenarios but aren't required in most use cases where the shared instance is set up near the top of your application. Also note that only one shared instance can exist at a time. Attempting to create a second shared instance will result in an error. {% /callout %} ## Manual exposures By default, the SDK automatically logs an exposure event when you check a gate, get a config, get an experiment, or call `get()` on a layer parameter. To delay exposure logging until the user actually uses the feature, use manual exposures. {% codetabs %} ```java Java String userID = "user_id"; // Check gate without logging exposure boolean result = statsig.checkGateWithExposureLoggingDisabled(userID, "my_feature_gate"); // Manually log the exposure when ready statsig.manuallyLogGateExposure(userID, "my_feature_gate"); // Works with configs too DynamicConfig config = statsig.getConfigWithExposureLoggingDisabled(userID, "my_config"); statsig.manuallyLogConfigExposure(userID, "my_config"); // And with experiments/layers Experiment experiment = statsig.getExperimentWithExposureLoggingDisabled(userID, "my_experiment"); statsig.manuallyLogExperimentExposure(userID, "my_experiment"); Layer layer = statsig.getLayerWithExposureLoggingDisabled(userID, "my_layer"); statsig.manuallyLogLayerParameterExposure(userID, "my_layer", "parameter_name"); ``` ```kotlin Kotlin val userID = "user_id" // Check gate without logging exposure val result = statsig.checkGateWithExposureLoggingDisabled(userID, "my_feature_gate") // Manually log the exposure when ready statsig.manuallyLogGateExposure(userID, "my_feature_gate") // Works with configs too val config = statsig.getConfigWithExposureLoggingDisabled(userID, "my_config") statsig.manuallyLogConfigExposure(userID, "my_config") // And with experiments/layers val experiment = statsig.getExperimentWithExposureLoggingDisabled(userID, "my_experiment") statsig.manuallyLogExperimentExposure(userID, "my_experiment") val layer = statsig.getLayerWithExposureLoggingDisabled(userID, "my_layer") statsig.manuallyLogLayerParameterExposure(userID, "my_layer", "parameter_name") ``` {% /codetabs %} ## Statsig User The `StatsigUser` object represents a user in Statsig. You must provide a `userID` or at least one of the `customIDs` to identify the user. When calling APIs that require a user, pass as much information as possible to enable advanced gate and config conditions (such as country or OS/browser checks) and to measure experiment impact accurately. As explained in [why an ID is always required for server SDKs](/sdks/user#why-is-an-id-always-required-for-server-sdks), at least one identifier (userID or customID) is required to provide a consistent experience for a given user. In addition to `userID`, the top-level fields on `StatsigUser` are: `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion`. You can also pass key-value pairs in the `custom` field for targeting. ### Private attributes Private attributes are user attributes used for evaluation but not forwarded to any integrations. Use them for PII or sensitive data that you don't want to send to third-party services. {% codetabs %} ```java Java import com.statsig.*; import java.util.HashMap; import java.util.Map; // Build a user with various fields Map<String, Object> customFields = new HashMap<>(); customFields.put("plan", "premium"); customFields.put("age", 25); Map<String, String> privateAttributes = new HashMap<>(); privateAttributes.put("internal_id", "123456"); StatsigUser user = StatsigUser.builder() .userID("user_123") .email("user@example.com") .ip("192.168.1.1") .userAgent("Mozilla/5.0...") .country("US") .locale("en_US") .appVersion("1.2.3") .custom(customFields) .privateAttributes(privateAttributes) .build(); // Use the user for evaluation boolean result = statsig.checkGate(user, "my_feature_gate"); ``` ```kotlin Kotlin import com.statsig.* // Build a user with various fields val customFields = mapOf( "plan" to "premium", "age" to 25 ) val privateAttributes = mapOf( "internal_id" to "123456" ) val user = StatsigUser.builder() .userID("user_123") .email("user@example.com") .ip("192.168.1.1") .userAgent("Mozilla/5.0...") .country("US") .locale("en_US") .appVersion("1.2.3") .custom(customFields) .privateAttributes(privateAttributes) .build() // Use the user for evaluation val result = statsig.checkGate(user, "my_feature_gate") ``` {% /codetabs %} ## Private attributes Statsig evaluates private attributes normally but doesn't log them in events sent to Statsig servers. Use them for PII or other sensitive data. {% codetabs %} ```java Java Map<String, String> privateAttributes = new HashMap<>(); privateAttributes.put("sensitive_field", "sensitive_value"); StatsigUser user = StatsigUser.builder() .userID("user_123") .privateAttributes(privateAttributes) .build(); ``` ```kotlin Kotlin val privateAttributes = mapOf( "sensitive_field" to "sensitive_value" ) val user = StatsigUser.builder() .userID("user_123") .privateAttributes(privateAttributes) .build() ``` {% /codetabs %} ## Statsig Options You can pass an optional `options` parameter in addition to `sdkKey` during initialization to customize the Statsig client. {% accordion title="StatsigOptions" %} {% parameter name="environment" type="string" %} Environment parameter for evaluation. {% /parameter %} {% parameter name="specsUrl" type="string" %} Custom URL for fetching feature specifications. {% /parameter %} {% parameter name="specsSyncIntervalMs" type="long" %} How often the SDK updates specifications from Statsig servers (in milliseconds). {% /parameter %} {% parameter name="fallbackToStatsig" type="boolean" %} Turn this on if you are proxying `download_config_specs` / `get_id_lists` and want to fall back to the default Statsig endpoint to increase reliability. {% /parameter %} {% parameter name="logEventUrl" type="string" %} Custom URL for logging events. {% /parameter %} {% parameter name="disableAllLogging" type="boolean" %} If true, the SDK doesn't collect any logging within the session, including custom events and config check exposure events. {% /parameter %} {% parameter name="enableIDLists" type="boolean" %} Required to be `true` when using segments with more than 1000 IDs. {% /parameter %} {% parameter name="disableUserAgentParsing" type="boolean" %} If true, the SDK doesn't parse User-Agent strings into `browserName`, `browserVersion`, `systemName`, `systemVersion`, and `appVersion` when needed for evaluation. {% /parameter %} {% parameter name="disableUserCountryLookup" type="boolean" %} If true, the SDK doesn't parse IP addresses (from `user.ip`) into country codes when needed for evaluation. {% /parameter %} {% parameter name="eventLoggingFlushIntervalMs" type="long" %} How often events are flushed to Statsig servers (in milliseconds). {% /parameter %} {% parameter name="eventLoggingMaxQueueSize" type="int" %} Maximum number of events to queue before forcing a flush. {% /parameter %} {% parameter name="dataStore" type="DataStore" %} An adapter with custom storage behavior for config specs. Can also continuously fetch updates in place of the Statsig network. {% /parameter %} {% parameter name="proxyConfig" type="ProxyConfig" %} Configuration for connecting through an outbound HTTP proxy. {% /parameter %} {% parameter name="specAdapterConfigs" type="List<SpecAdapterConfig>" %} Advanced configuration for fetching specs from multiple sources or protocols, including [Statsig Forward Proxy](/infrastructure/api_proxy/introduction) over gRPC websocket streaming. Each `SpecAdapterConfig` can set: * `adapterType`: one of `SpecAdapterType.DATA_STORE`, `SpecAdapterType.NETWORK_HTTP`, or `SpecAdapterType.NETWORK_GRPC_WEBSOCKET` * `specsUrl`: optional endpoint override for the adapter * `initTimeoutMs`: optional initialization timeout in milliseconds * `authenticationMode`: optional transport auth mode via `AuthenticationMode.NONE`, `AuthenticationMode.TLS`, or `AuthenticationMode.MTLS` * `caCertPath`, `clientCertPath`, `clientKeyPath`, `domainName`: optional TLS and mTLS fields for gRPC websocket connections {% /parameter %} {% parameter name="persistentStorage" type="PersistentStorage" %} Interface to use persistent storage within the SDK. {% /parameter %} {% parameter name="outputLoggerLevel" type="OutputLogger.LogLevel" %} Set the logging level for the SDK. Options: `NONE`, `ERROR`, `WARN`, `INFO`, `DEBUG`. {% /parameter %} {% parameter name="observabilityClient" type="ObservabilityClient" %} Interface to integrate observability metrics exposed by the SDK. {% /parameter %} *** {% codetabs %} ```java Java // Example usage StatsigOptions options = new StatsigOptions.Builder() .setEnvironment("staging") .setSpecsSyncIntervalMs(10000) .setEventLoggingFlushIntervalMs(5000) .setOutputLoggerLevel(OutputLogger.LogLevel.INFO) .build(); Statsig statsig = new Statsig("secret-key", options); statsig.initialize().get(); ``` ```kotlin Kotlin // Example usage val options = StatsigOptions.Builder() .setEnvironment("staging") .setSpecsSyncIntervalMs(10000) .setEventLoggingFlushIntervalMs(5000) .setOutputLoggerLevel(OutputLogger.LogLevel.INFO) .build() val statsig = Statsig("secret-key", options) statsig.initialize().get() ``` {% /codetabs %} {% /accordion %} ### Proxy and custom network routing Use `setProxyConfig(ProxyConfig)` if your service needs a standard outbound HTTP proxy for Statsig network calls. Use `setSpecAdapterConfigs(...)` if you are routing spec downloads through [Statsig Forward Proxy](/infrastructure/api_proxy/introduction). ```java ProxyConfig proxyConfig = new ProxyConfig(); proxyConfig.setProxyHost("proxy.example.com"); proxyConfig.setProxyPort(8080); proxyConfig.setProxyProtocol("https"); proxyConfig.setCaCertPath("/etc/ssl/certs/corporate-ca.pem"); // Optional StatsigOptions options = new StatsigOptions.Builder() .setProxyConfig(proxyConfig) .build(); Statsig statsig = new Statsig("secret-key", options); statsig.initialize().get(); ``` `ProxyConfig` supports `proxyHost`, `proxyPort`, `proxyAuth`, `proxyProtocol`, and `caCertPath`. Set `caCertPath` when your environment requires a custom PEM CA bundle for outbound TLS. ### Statsig Forward Proxy example ```java import java.util.Collections; SpecAdapterConfig forwardProxyConfig = new SpecAdapterConfig() .setAdapterType(SpecAdapterType.NETWORK_GRPC_WEBSOCKET) .setSpecsUrl("http://forward-proxy.internal:50051") .setAuthenticationMode(AuthenticationMode.NONE); StatsigOptions options = new StatsigOptions.Builder() .setSpecAdapterConfigs(Collections.singletonList(forwardProxyConfig)) .setFallbackToStatsigApi(true) .build(); Statsig statsig = new Statsig("secret-key", options); statsig.initialize().get(); ``` ## Shutting Statsig Down Because events are batched and periodically flushed, some events may not have been sent when your app or server shuts down. To ensure all logged events are flushed, call `shutdown()` before shutting down your app or server: {% codetabs %} ```java Java // Shutdown flushes all pending events and stops background tasks statsig.shutdown(); // Or with a timeout (blocks until shutdown completes or timeout) statsig.shutdown().get(5, TimeUnit.SECONDS); ``` ```kotlin Kotlin // Shutdown flushes all pending events and stops background tasks statsig.shutdown() // Or with a timeout (blocks until shutdown completes or timeout) statsig.shutdown().get(5, TimeUnit.SECONDS) ``` {% /codetabs %} ## Local overrides Local Overrides let you override the values of gates, configs, experiments, and layers for testing purposes, without changing the configuration in the Statsig console. {% codetabs %} ```java Java import java.util.HashMap; import java.util.Map; // Override a gate statsig.overrideGate("my_feature_gate", true); // Override a config Map<String, Object> configOverride = new HashMap<>(); configOverride.put("key", "value"); configOverride.put("number", 42); statsig.overrideConfig("my_config", configOverride); // Override an experiment Map<String, Object> experimentOverride = new HashMap<>(); experimentOverride.put("variant", "test"); statsig.overrideExperiment("my_experiment", experimentOverride); // Override an experiment to a particular groupname statsig.overrideExperimentByGroupName("my_experiment", "a_group_name"); statsig.overrideExperimentByGroupName("my_experiment", "a_group_name", "user_123"); // Override a layer Map<String, Object> layerOverride = new HashMap<>(); layerOverride.put("layer_param", "override_value"); statsig.overrideLayer("my_layer", layerOverride); // Clear all overrides statsig.clearAllOverrides(); // Clear specific override statsig.clearGateOverride("my_feature_gate"); statsig.clearConfigOverride("my_config"); ``` ```kotlin Kotlin // Override a gate statsig.overrideGate("my_feature_gate", true) // Override a config val configOverride = mapOf( "key" to "value", "number" to 42 ) statsig.overrideConfig("my_config", configOverride) // Override an experiment val experimentOverride = mapOf( "variant" to "test" ) statsig.overrideExperiment("my_experiment", experimentOverride) // Override an experiment to a particular groupname statsig.overrideExperimentByGroupName("my_experiment", "a_group_name") statsig.overrideExperimentByGroupName("my_experiment", "a_group_name", "user_123") // Override a layer val layerOverride = mapOf( "layer_param" to "override_value" ) statsig.overrideLayer("my_layer", layerOverride) // Clear all overrides statsig.clearAllOverrides() // Clear specific override statsig.clearGateOverride("my_feature_gate") statsig.clearConfigOverride("my_config") ``` {% /codetabs %} ## Persistent storage The Persistent Storage interface lets you implement custom storage for user-specific configurations. Use it to persist user assignments across sessions, ensuring consistent experiment groups when users return. This is useful for client-side A/B testing where you need users to always see the same variant. {% codetabs %} ```java Java import com.statsig.*; import java.util.HashMap; import java.util.Map; class MyPersistentStorage implements PersistentStorage { private Map<String, Map<String, StickyValues>> storage = new HashMap<>(); @Override public Map<String, StickyValues> load(String key) { // Load persisted sticky values for the given key // Key format is "{userId}:userID" or "{customId}:{idType}" return storage.get(key); } @Override public void save(String key, String configName, StickyValues data) { // Save sticky values for a specific experiment/config storage.computeIfAbsent(key, k -> new HashMap<>()).put(configName, data); } @Override public void delete(String key, String configName) { // Delete sticky values for a specific experiment/config Map<String, StickyValues> values = storage.get(key); if (values != null) { values.remove(configName); } } } // Use persistent storage StatsigOptions options = new StatsigOptions.Builder() .setPersistentStorage(new MyPersistentStorage()) .build(); Statsig statsig = new Statsig("secret-key", options); statsig.initialize().get(); ``` ```kotlin Kotlin import com.statsig.* class MyPersistentStorage : PersistentStorage { private val storage = mutableMapOf<String, MutableMap<String, StickyValues>>() override fun load(key: String): Map<String, StickyValues>? { // Load persisted sticky values for the given key // Key format is "{userId}:userID" or "{customId}:{idType}" return storage[key] } override fun save(key: String, configName: String, data: StickyValues) { // Save sticky values for a specific experiment/config storage.getOrPut(key) { mutableMapOf() }[configName] = data } override fun delete(key: String, configName: String) { // Delete sticky values for a specific experiment/config storage[key]?.remove(configName) } } // Use persistent storage val options = StatsigOptions.Builder() .setPersistentStorage(MyPersistentStorage()) .build() val statsig = Statsig("secret-key", options) statsig.initialize().get() ``` {% /codetabs %} ### Helper methods The `PersistentStorage` interface provides helper methods for working with user-based storage keys: ```java // Get persisted values for a user using the helper method Map<String, StickyValues> values = persistentStorage.getValuesForUser(user, "userID"); // Generate a storage key from a user and ID type String key = PersistentStorage.getStorageKey(user, "userID"); // Returns "{userId}:userID" String customKey = PersistentStorage.getStorageKey(user, "companyID"); // Returns "{companyId}:companyID" ``` ## Data store The Data Store interface lets you implement custom storage for Statsig configurations, enabling advanced caching strategies and integration with your preferred storage systems such as Redis. {% codetabs %} ```java Java import com.statsig.*; class MyDataStore implements DataStore { @Override public String getDataSync(String key) { // Synchronously fetch data for the given key // This is called during SDK evaluation return null; } @Override public CompletableFuture<Void> setData(String key, String data) { // Store data for the given key // Called when SDK receives updates from Statsig return CompletableFuture.completedFuture(null); } @Override public CompletableFuture<Void> initialize() { // Perform any initialization needed for your data store return CompletableFuture.completedFuture(null); } @Override public void shutdown() { // Clean up resources } } // Use data store StatsigOptions options = new StatsigOptions.Builder() .setDataStore(new MyDataStore()) .build(); Statsig statsig = new Statsig("secret-key", options); statsig.initialize().get(); ``` ```kotlin Kotlin import com.statsig.* import java.util.concurrent.CompletableFuture class MyDataStore : DataStore { override fun getDataSync(key: String): String? { // Synchronously fetch data for the given key // This is called during SDK evaluation return null } override fun setData(key: String, data: String): CompletableFuture<Void> { // Store data for the given key // Called when SDK receives updates from Statsig return CompletableFuture.completedFuture(null) } override fun initialize(): CompletableFuture<Void> { // Perform any initialization needed for your data store return CompletableFuture.completedFuture(null) } override fun shutdown() { // Clean up resources } } // Use data store val options = StatsigOptions.Builder() .setDataStore(MyDataStore()) .build() val statsig = Statsig("secret-key", options) statsig.initialize().get() ``` {% /codetabs %} ## Custom output logger The Output Logger interface lets you customize how the SDK logs messages and enables integration with your own logging system and control over log verbosity. {% codetabs %} ```java Java import com.statsig.*; class MyOutputLogger implements OutputLogger { @Override public void log(LogLevel level, String message) { // Route SDK logs to your logging system switch (level) { case ERROR: System.err.println("[ERROR] " + message); break; case WARN: System.out.println("[WARN] " + message); break; case INFO: System.out.println("[INFO] " + message); break; case DEBUG: System.out.println("[DEBUG] " + message); break; } } } // Use custom output logger StatsigOptions options = new StatsigOptions.Builder() .setOutputLogger(new MyOutputLogger()) .setOutputLoggerLevel(OutputLogger.LogLevel.INFO) .build(); Statsig statsig = new Statsig("secret-key", options); statsig.initialize().get(); ``` ```kotlin Kotlin import com.statsig.* class MyOutputLogger : OutputLogger { override fun log(level: OutputLogger.LogLevel, message: String) { // Route SDK logs to your logging system when (level) { OutputLogger.LogLevel.ERROR -> println("[ERROR] $message") OutputLogger.LogLevel.WARN -> println("[WARN] $message") OutputLogger.LogLevel.INFO -> println("[INFO] $message") OutputLogger.LogLevel.DEBUG -> println("[DEBUG] $message") else -> {} } } } // Use custom output logger val options = StatsigOptions.Builder() .setOutputLogger(MyOutputLogger()) .setOutputLoggerLevel(OutputLogger.LogLevel.INFO) .build() val statsig = Statsig("secret-key", options) statsig.initialize().get() ``` {% /codetabs %} ## Observability client The Observability Client interface lets you monitor SDK health by integrating with your own observability systems. For more information on the metrics emitted by Statsig SDKs, go to the [Monitoring documentation](/infrastructure/sdk-monitoring). {% codetabs %} ```java Java import com.statsig.*; class MyObservabilityClient implements ObservabilityClient { @Override public void emitMetric(String metricName, double value, Map<String, String> tags) { // Send metric to your monitoring system System.out.println("Metric: " + metricName + " = " + value + ", tags: " + tags); } @Override public void startTimer(String operationName) { // Start timing an operation } @Override public void endTimer(String operationName, Map<String, String> tags) { // End timing and emit duration metric } } // Use observability client StatsigOptions options = new StatsigOptions.Builder() .setObservabilityClient(new MyObservabilityClient()) .build(); Statsig statsig = new Statsig("secret-key", options); statsig.initialize().get(); ``` ```kotlin Kotlin import com.statsig.* class MyObservabilityClient : ObservabilityClient { override fun emitMetric(metricName: String, value: Double, tags: Map<String, String>) { // Send metric to your monitoring system println("Metric: $metricName = $value, tags: $tags") } override fun startTimer(operationName: String) { // Start timing an operation } override fun endTimer(operationName: String, tags: Map<String, String>) { // End timing and emit duration metric } } // Use observability client val options = StatsigOptions.Builder() .setObservabilityClient(MyObservabilityClient()) .build() val statsig = Statsig("secret-key", options) statsig.initialize().get() ``` {% /codetabs %} ## FAQ {% accordion-group %} {% accordion title="What Java versions are supported?" %} The Java Core SDK supports Java 8 and higher. Java 8 support was added in version 0.4.3. {% /accordion %} {% accordion title="Which platforms are supported?" %} The SDK supports: * Linux (x86\_64, arm64, musl variants) * macOS (x86\_64, arm64) * Windows (x86\_64) Refer to the Tested Platforms section for verified Docker images. {% /accordion %} {% accordion title="Should I use the uber JAR or platform-specific JARs?" %} The uber JAR is recommended for most use cases as it includes native libraries for all popular platforms and simplifies deployment. Use platform-specific JARs only if you need to minimize JAR size or have specific dependency requirements. {% /accordion %} {% accordion title="How do I use this with Alpine Linux?" %} For Alpine Linux (musl-based systems), install compatibility packages: ```dockerfile RUN apk add --no-cache libgcc gcompat ``` The SDK will automatically use musl-compatible native libraries. {% /accordion %} {% accordion title="How do I handle initialization in production?" %} The SDK initialization is asynchronous. Use `.get()` on the CompletableFuture to wait for initialization: ```java Statsig statsig = new Statsig("secret-key", options); statsig.initialize().get(); // Blocks until initialized ``` Always call `shutdown()` when your application terminates to flush pending events. {% /accordion %} {% accordion title="Can I use multiple Statsig instances?" %} Yes, you can create multiple Statsig instances with different configurations. You can also use the global singleton with `Statsig.getGlobalSingleton()` for convenience. {% /accordion %} {% accordion title="How do I debug SDK issues?" %} Set the output logger level to DEBUG to see detailed logs: ```java StatsigOptions options = new StatsigOptions.Builder() .setOutputLoggerLevel(OutputLogger.LogLevel.DEBUG) .build(); ``` {% /accordion %} {% /accordion-group %} ## Reference ### FeatureGate Class ```java class FeatureGate { String name; // Gate name boolean value; // Evaluation boolean result String ruleID; // Rule ID for this gate EvaluationDetails evaluationDetails; // Evaluation details String rawJson; // Raw JSON string representation } ``` ### Experiment Class ```java class Experiment { String name; // Name of the experiment String ruleID; // ID of the rule used in the experiment Map<String, JsonElement> value; // Configuration values specific to the experiment String groupName; // The group name the user falls into EvaluationDetails evaluationDetails; // Details about how the experiment was evaluated String rawJson; // Raw JSON representation of the experiment } ``` **Methods for Experiment:** ```java public String getString(String key, String fallbackValue) public boolean getBoolean(String key, Boolean fallbackValue) public double getDouble(String key, double fallbackValue) public int getInt(String key, int fallbackValue) public long getLong(String key, long fallbackValue) public Object[] getArray(String key, Object[] fallbackValue) public Map<String, Object> getMap(String key, Map<String, Object> fallbackValue) ``` ### DynamicConfig Class ```java class DynamicConfig { String name; // Name of the config String ruleID; // ID of the rule used Map<String, JsonElement> value; // Configuration values EvaluationDetails evaluationDetails; // Details about how the config was evaluated String rawJson; // Raw JSON representation } ``` **Methods for DynamicConfig:** ```java public String getString(String key, String fallbackValue) public boolean getBoolean(String key, Boolean fallbackValue) public double getDouble(String key, double fallbackValue) public int getInt(String key, int fallbackValue) public long getLong(String key, long fallbackValue) public Object[] getArray(String key, Object[] fallbackValue) public Map<String, Object> getMap(String key, Map<String, Object> fallbackValue) ``` ### Layer Class ```java class Layer { String name; // Layer name String ruleID; // Rule ID for this layer String groupName; // Group name Map<String, JsonElement> value; // Layer values String allocatedExperimentName; // Allocated experiment name EvaluationDetails evaluationDetails; // Evaluation details String rawJson; // Raw JSON string representation } ``` **Methods for Layer:** ```java public String getString(String key, String fallbackValue) public boolean getBoolean(String key, Boolean fallbackValue) public double getDouble(String key, double fallbackValue) public int getInt(String key, int fallbackValue) public long getLong(String key, long fallbackValue) public Object[] getArray(String key, Object[] fallbackValue) public Map<String, Object> getMap(String key, Map<String, Object> fallbackValue) ``` ### Fields needed methods (Enterprise only) {% callout type="info" %} This is available for Enterprise contracts. Reach out to our support team, your sales contact, or through our [Slack community](https://statsig.com/slack) to enable it. {% /callout %} These methods let you retrieve a list of user fields used in the targeting rules for gates, configs, experiments, and layers. ```java // Get user fields needed for a gate evaluation List<String> getFieldsNeededForGate(String gateName) // Get user fields needed for a dynamic config evaluation List<String> getFieldsNeededForDynamicConfig(String configName) // Get user fields needed for an experiment evaluation List<String> getFieldsNeededForExperiment(String experimentName) // Get user fields needed for a layer evaluation List<String> getFieldsNeededForLayer(String layerName) ``` **Field Mapping** The fields returned by these methods correspond to the following user properties: ```java // Field mapping between user properties and internal field names userID -> "u" email -> "e" ip -> "i" userAgent -> "ua" country -> "c" locale -> "l" appVersion -> "a" time -> "t" stableID -> "s" environment -> "en" targetApp -> "ta" // Custom fields are prefixed with "cf:" // Example: "cf:plan", "cf:age" ``` ================================================================================ # Go Server Core SDK URL: https://docs.statsig.com/server-core/go-core Updated: 2026-07-11 ================================================================================ ## Setup the SDK {% steps %} {% step title="Install the SDK" %} Using the `go get` CLI, install the latest version of the SDK: ``` go get github.com/statsig-io/statsig-go-core@latest ``` Or add a dependency on the most recent version in go.mod: ``` require ( github.com/statsig-io/statsig-go-core v0.10.2-beta ) ``` Go to the [Releases tab in GitHub](https://github.com/statsig-io/statsig-server-core/releases) for the latest versions. :::note `go get` requires a Go module project with a go.mod file. ::: {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Server Secret Keys should always be kept private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} An optional `options` parameter accepts a `StatsigOptions` object to customize the SDK. ```go expandable import ( "log" statsig "github.com/statsig-io/statsig-go-core" ) options, err := statsig.NewOptionsBuilder(). WithOutputLogLevel("DEBUG"). Build() if err != nil { log.Fatalf("failed to build options: %v", err) } s, err := statsig.NewStatsigWithOptions("secret-key", options) if err != nil { log.Fatalf("failed to create Statsig client: %v", err) } s.Initialize() ``` `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independent of your API calls. {% /step %} {% /steps %} ## Working with the SDK ### Checking a Feature Flag/Gate After the SDK is initialized, you can fetch a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are **CLOSED** or **OFF** (equivalent to `return false;`) by default. All APIs require a user object (refer to [Statsig user](#statsig-user)). To check a gate for a user: ```go user, _ := statsig.NewUserBuilderWithUserID("a-user").Build() // Basic check if s.CheckGate(user, "a_gate") { // Gate is on, enable new feature } else { // Gate is off } // With options (e.g., disable automatic exposure logging) options := &statsig.FeatureGateEvaluationOptions{DisableExposureLogging: true} s.CheckGateWithOptions(user, "a_gate", options) ``` ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional user targeting. To send different values (strings, numbers, etc.) to clients based on user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The API is similar to Feature Gates, but returns a JSON object from which you can retrieve typed parameters. For example: ```go user, _ := statsig.NewUserBuilderWithUserID("a-user").Build() // You can disable exposure logging for this specific check dynamicConfigOptions := &statsig.DynamicConfigEvaluationOptions{DisableExposureLogging: false} dynamic_config := "a_config" config := s.GetDynamicConfigWithOptions(user, dynamic_config, dynamicConfigOptions) // Access the config values str_val := config.GetString("str_val", "default_str_val") // returns String int_val := config.GetNumber("number_val", 100) // returns float64 bool_val := config.GetBool("bool_val", false) // returns bool interface_val := config.GetSlice("interface_val", []any{"default_interface_val"}) // returns []any map_val := config.GetMap("map_val", map[string]any{"default": "value"}) // returns map[string]interface{} // The config object also provides metadata about the evaluation fmt.Println(config.RuleID) // The ID of the rule that served this config fmt.Println(config.IDType) // The type of the evaluation (experiment, config, etc) ``` ### Getting a Layer/Experiment **Layers/Experiments** let you run A/B/n experiments. Two APIs are available, but [layers](/experiments/layers-overview) are recommended because layers make parameters reusable and support mutually exclusive experiments. ```go user, _ := statsig.NewUserBuilderWithUserID("a-user").Build() // Getting values with GetExperiment experimentOptions := &statsig.ExperimentEvaluationOptions{DisableExposureLogging: false} experiment := s.GetExperimentWithOptions(user, "a_test_experiment", experimentOptions) str_val := experiment.GetString("str_val", "default_str_val") // returns String int_val := experiment.GetNumber("number_val", 100) // returns float64 // Getting values with GetLayer layerOptions := &statsig.LayerEvaluationOptions{DisableExposureLogging: false} layer := s.GetLayerWithOptions(user, "a_test_experiment", layerOptions) str_val := layer.GetString("str_val", "default_str_val") // returns String int_val := layer.GetNumber("number_val", 100) // returns float64 ``` ### Retrieving Feature Gate Metadata To retrieve more information about a gate evaluation than a boolean value, use the Get Feature Gate API, which returns a `FeatureGate` object: ```go user, _ := statsig.NewUserBuilderWithUserID("a-user").Build() options := &statsig.FeatureGateEvaluationOptions{DisableExposureLogging: false} featureGate := s.GetFeatureGateWithOptions(user, "a_gate", options) ``` ### Logging an Event After setting up a Feature Gate or Experiment, track custom events to measure how features or experiment groups affect user behavior. Call the Log Event API and specify the user and event name. You can also provide a value and metadata: ```go user, _ := statsig.NewUserBuilderWithUserID("a-user").Build() event := statsig.EventPayload{ EventName: "sample_event", Value: "event", Metadata: map[string]string{ "val_1": "log val 1", }, } s.LogEvent(user, event) ``` ## Manual exposures By default, the SDK automatically logs an exposure event when you check a gate, get a config, get an experiment, or call `get()` on a layer parameter. To delay exposure logging until the user actually uses the feature, use manual exposures. All main SDK functions (`CheckGate`, `GetDynamicConfig`, `GetExperiment`, `GetLayer`) accept an optional options parameter with a `DisableExposureLogging` field. When set to `true`, the SDK doesn't automatically log an exposure event. You can then manually log the exposure using the corresponding manual exposure logging method: {% tabs %} {% tab title="Feature Gates" %} ```go user, _ := statsig.NewUserBuilderWithUserID("a-user").Build() options := &statsig.FeatureGateEvaluationOptions{DisableExposureLogging: true} result := s.CheckGateWithOptions(user, "a_gate_name", options) ``` ```go s.ManuallyLogFeatureGateExposure(user, "gate_name") ``` {% /tab %} {% tab title="Dynamic Configs" %} ```go user, _ := statsig.NewUserBuilderWithUserID("a-user").Build() options := &statsig.DynamicConfigEvaluationOptions{DisableExposureLogging: true} config := s.GetDynamicConfigWithOptions(user, "config_name", options) ``` ```go s.ManuallyLogDynamicConfigExposure(user, "config_name") ``` {% /tab %} {% tab title="Experiments" %} ```go user, _ := statsig.NewUserBuilderWithUserID("a-user").Build() options := &statsig.ExperimentEvaluationOptions{DisableExposureLogging: true} experiment := s.GetExperimentWithOptions(user, "experiment_name", options) ``` ```go s.ManuallyLogExperimentExposure(user, "experiment_name") ``` {% /tab %} {% tab title="Layers" %} ```go user, _ := statsig.NewUserBuilderWithUserID("a-user").Build() options := &statsig.LayerEvaluationOptions{DisableExposureLogging: true} layer := s.GetLayerWithOptions(user, "layer_name", options) paramValue := layer.GetString("param_name", "fallback") ``` ```go s.ManuallyLogLayerParamExposure(user, "layer_name", "param_name") ``` {% /tab %} {% /tabs %} ## Statsig User The `StatsigUser` object represents a user in Statsig. You must provide a `userID` or at least one of the `customIDs` to identify the user. When calling APIs that require a user, pass as much information as possible to enable advanced gate and config conditions (such as country or OS/browser checks) and to measure experiment impact accurately. As explained in [why an ID is always required for server SDKs](/sdks/user#why-is-an-id-always-required-for-server-sdks), at least one identifier (userID or customID) is required to provide a consistent experience for a given user. In addition to `userID`, the top-level fields on `StatsigUser` are: `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion`. You can also pass key-value pairs in the `custom` field for targeting. ### Private attributes Private attributes are user attributes used for evaluation but not forwarded to any integrations. Use them for PII or sensitive data that you don't want to send to third-party services. ## Statsig Options You can pass an optional `options` parameter in addition to `sdkKey` during initialization to customize the Statsig client. ### Parameters {% parameter name="SpecsUrl" type="string" %} Custom URL for fetching feature specifications. {% /parameter %} {% parameter name="LogEventUrl" type="string" %} Custom URL for logging events. {% /parameter %} {% parameter name="Environment" type="string" %} Environment parameter for evaluation. {% /parameter %} {% parameter name="EventLoggingFlushIntervalMs" type="number" %} How often events are flushed to Statsig servers (in milliseconds). {% /parameter %} {% parameter name="EventLoggingMaxQueueSize" type="number" %} Maximum number of events to queue before forcing a flush. {% /parameter %} {% parameter name="SpecsSyncIntervalMs" type="number" %} How often the SDK updates specifications from Statsig servers (in milliseconds). {% /parameter %} {% parameter name="OutputLogLevel" type="string" %} Controls the verbosity of SDK logs. {% /parameter %} {% parameter name="DisableCountryLookup" type="boolean" %} Disables country lookup based on IP address. Set to `true` to improve performance if country-based targeting is not needed. {% /parameter %} {% parameter name="DisableUserAgentParsing" type="boolean" %} Disables user agent parsing. Set to `true` to improve performance if device/browser-based targeting is not needed. {% /parameter %} {% parameter name="WaitForCountryLookupInit" type="boolean" %} When set to `true`, the SDK waits for country lookup data (such as GeoIP or YAML files) to fully load during initialization. This may add approximately 1 second to startup time, but ensures that IP-to-country parsing is ready at evaluation time. {% /parameter %} {% parameter name="WaitForUserAgentInit" type="boolean" %} When set to `true`, the SDK waits until user agent parsing data is fully loaded during initialization. This may add approximately 1 second to startup time, but ensures that parsing of the user's `userAgent` string into fields such as `browserName`, `browserVersion`, `systemName`, `systemVersion`, and `appVersion` is ready before any evaluations. {% /parameter %} {% parameter name="EnableIdLists" type="boolean" %} Enables downloading ID lists; set to false to skip ID list syncing. {% /parameter %} {% parameter name="IdListsUrl" type="string" %} Custom URL for fetching ID lists. {% /parameter %} {% parameter name="IdListsSyncIntervalMs" type="number" %} How often the SDK syncs ID lists (in milliseconds). {% /parameter %} {% parameter name="DisableAllLogging" type="boolean" %} When set to true, the SDK doesn't log any events or exposures. {% /parameter %} {% parameter name="DisableNetwork" type="boolean" %} When set to true, the SDK doesn't make any network requests. {% /parameter %} {% parameter name="GlobalCustomFields" type="string" %} JSON string of custom fields that should be appended to every evaluation. {% /parameter %} {% parameter name="ObservabilityClientRef" type="number" %} Internal reference for a custom observability client created through the SDK. {% /parameter %} {% parameter name="DataStoreRef" type="number" %} Internal reference for a custom data store adapter created through the SDK. {% /parameter %} {% parameter name="PersistentStorageRef" type="number" %} Internal reference for a persistent storage adapter created through the SDK. {% /parameter %} {% parameter name="InitTimeoutMs" type="number" %} Maximum time in milliseconds to wait for SDK initialization to complete. If initialization takes longer than this timeout, the SDK will continue to operate but may return default values until initialization completes. {% /parameter %} {% parameter name="FallbackToStatsigApi" type="boolean" %} When set to true, the SDK falls back to using the Statsig API directly if custom adapters (like local file adapters) fail to load configurations. {% /parameter %} ### Proxy and custom network routing The Go Server Core SDK currently documents endpoint overrides rather than a dedicated outbound proxy config. Use `WithSpecsUrl(...)`, `WithLogEventUrl(...)`, and `WithIdListsUrl(...)` to route Statsig network calls through your own endpoints or proxy layer. *** ### Example options usage ```go expandable import ( statsig "github.com/statsig-io/statsig-go-core" ) // Initialize StatsigOptions with custom parameters options, err := statsig.NewOptionsBuilder(). WithSpecsUrl("https://example.com/specsUrl"). WithLogEventUrl("https://example.com/logUrl"). WithEnvironment("production"). WithEventLoggingFlushIntervalMs(2000). WithEventLoggingMaxQueueSize(5000). WithSpecsSyncIntervalMs(1000). WithOutputLogLevel("DEBUG"). WithDisableCountryLookup(true). WithDisableUserAgentParsing(true). WithWaitForCountryLookupInit(false). WithEnableIdLists(true). WithIdListsSyncIntervalMs(60000). WithInitTimeoutMs(3000). WithFallbackToStatsigApi(false). Build() if err != nil { // handle options build error } // Pass the options object when initializing the Statsig client s, err := statsig.NewStatsigWithOptions("secret-key", options) if err != nil { // handle init error } s.Initialize() ``` ## Shutting Statsig Down Because events are batched and periodically flushed, some events may not have been sent when your app or server shuts down. To ensure all logged events are flushed, call `shutdown()` before shutting down your app or server: ```go // Method signature func (s *Statsig) Shutdown() {} // example usage s, err := statsig.NewStatsig("secret-key") s.Initialize() s.Shutdown() ``` ### Flush events ```go // Method signature func (s *Statsig) FlushEvents() {} // example usage s, err := statsig.NewStatsig("secret-key") s.Initialize() s.FlushEvents() ``` ## FAQ ##### Installation FAQs #### How do I fix undefined symbol errors/linker errors? You may need to reset your environment variables to those found in the `statsig.env` or `statsig.env.ps1` file generated during the post-install script. Running the command sets the environment variables for the current terminal session. Add the variables to your `.bashrc`, `.zshrc`, or `.ps1` file to load them automatically. ================================================================================ # Ruby Server SDK URL: https://docs.statsig.com/server/ruby Updated: 2026-07-11 ================================================================================ ## Setup the SDK {% steps %} {% step title="Install the SDK" %} If you are using Bundler, add the [gem](https://rubygems.org/gems/statsig) to your Gemfile from command line: ```shell bundle add statsig ``` or directly include it in your Gemfile and run `bundle install`: ```shell gem "statsig", ">= X.Y.Z" ``` Check out the latest versions on [https://rubygems.org/gems/statsig](https://rubygems.org/gems/statsig) {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Do NOT embed your Server Secret Key in client-side applications, or expose it in any external-facing documents. However, if you accidentally expose it, you can create a new one in the Statsig console. {% /callout %} ```ruby require 'statsig' Statsig.initialize('server-secret-key') ``` ```ruby # Or, if you want to initialize with certain options options = StatsigOptions.new({'tier' => 'staging'}, network_timeout: 5) # And a callback when the initialization network request fails def error_callback(e) puts e end ... Statsig.initialize('server-secret-key', options, method(:error_callback)) ``` ### Initializing Statsig in a Rails application If your application is using Rails, you should initialize Statsig in `config/initializers/statsig.rb`: ```ruby Statsig.initialize('server-secret-key', options) ``` ### Initializing Statsig when using Unicorn, Puma, Passenger, or Sidekiq For **Unicorn**, you should initialize Statsig within an `after_fork` hook in your `unicorn.rb` config file: ```ruby after_fork do |server,worker| Statsig.initialize('server-secret-key', options) end ``` For **Puma**, you should initialize Statsig within an `on_worker_boot` hook in your `puma.rb` config file: ```ruby on_worker_boot do Statsig.initialize('server-secret-key', options) end ``` For **Passenger**, you should initialize Statsig in your `config.ru` config file: ```ruby if defined?(PhusionPassenger) PhusionPassenger.on_event(:starting_worker_process) do |forked| Statsig.initialize('server-secret-key', options) end end ``` For **Sidekiq**, you should initialize Statsig in your `sidekiq.rb`/server configuration file: ```ruby Sidekiq.configure_server do |config| config.on(:startup) do Statsig.initialize end config.on(:shutdown) do Statsig.shutdown end end ``` If you are using Rails in combination with any of the above, initialize using the specific process lifecycle hooks exposed by the respective tool. You can initialize in multiple places to ensure the SDK is fully usable, including all background processing. `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ## Checking a Feature Flag/Gate After the SDK is initialized, you can check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (`return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example, to check a gate for a user: ```ruby user = StatsigUser.new({'userID' => 'some_user_id'}) if Statsig.check_gate(user, 'use_new_feature') # Gate is on, enable new feature else # Gate is off end ``` ## Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The Dynamic Config API is similar to Feature Gates, but returns a full JSON object configured on the server, from which you can fetch typed parameters. ```ruby config = Statsig.get_config(user, 'awesome_product_details') # The 2nd parameter is the default value to be used in case the given parameter name does not exist on # the Dynamic Config object. This can happen when there is a typo, or when the user is offline and the # value has not been cached on the client. item_name = config.get('product_name', 'Awesome Product v1'); price = config.get('price', 10.0); shouldDiscount = config.get('discount', false); # Or just get the whole json object backing this config if you prefer json = config.value ``` ## Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for faster iterations with parameter reuse. ```ruby # Values via getLayer layer = Statsig.get_layer(user, "user_promo_experiments") title = layer.get("title", "Welcome to Statsig!") discount = layer.get("discount", 0.1) # or, via getExperiment title_exp = Statsig.get_experiment(user, "new_user_promo_title") price_exp = Statsig.get_experiment(user, "new_user_promo_price") title = title_exp.get("title", "Welcome to Statsig!") discount = price_exp.get("discount", 0.1) ... price = msrp * (1 - discount) ``` ## Logging an Event To track custom events and measure how features or experiment groups affect those events, call the Log Event API. Specify the user and event name to log, and optionally provide a value and metadata object: ```ruby Statsig.log_event( user, 'add_to_cart', 'SKU_12345', { 'price' => '9.99', 'item_name' => 'diet_coke_48_pack' } ) ``` For more about identifying users, group analytics, and best practices, go to the [logging events guide](/guides/logging-events). ## Statsig User When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks), and to correctly measure the impact of your experiments on your metrics/events. At least one identifier (userID or customID) is required to provide a consistent experience for a given user. Refer to [userID requirements](/sdks/user#why-is-an-id-always-required-for-server-sdks) for more detail. In addition to `userID`, `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion` are available as top-level fields on StatsigUser. You can also pass any key-value pairs in an object/dictionary to the `custom` field to create targeting based on them. Typing on the `StatsigUser` object is lenient: you can pass numbers, strings, arrays, objects, and even enums or classes. However, evaluation operators only work on primitive types, mostly strings and numbers. The SDK attempts to cast custom field types to match the operator, but evaluation results for other types are not guaranteed. For example, an array set as a custom field is only compared as a string: there is no operator to match a value within that array. ### Private Attributes To keep sensitive user PII data out of logs, use the `privateAttributes` field on the StatsigUser object. This field accepts an object/dictionary of private user attributes. Any attribute set in `privateAttributes` is used only for evaluation/targeting and is removed from all logs before Statsig sends them to its servers. For example, if a feature gate should only pass for users with emails ending in "@statsig.com", but you don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ## Statsig Options `initialize()` takes an optional `options` parameter in addition to the secret key to customize the Statsig client. Available options: * **environment**: Hash, default `nil` * a Hash you can use to set environment variables that apply to all your users in the same session, used for targeting purposes. * The most common usage is to set the "tier" (string), and have feature gates pass/fail for specific environments. The accepted values are "production", "staging" and "development", e.g. `StatsigOptions.New({ 'tier' => 'staging' })`. * **download\_config\_specs\_url**: String, default `"https://api.statsigcdn.com/v2/download_config_specs/"` * The url used specifically to call download\_config\_specs * **log\_event\_url**: String, default `"https://statsigapi.net/v1/log_event"` * The url used specifically to call log\_event * **get\_id\_lists\_url**: String, default `"https://statsigapi.net/v1/get_id_lists"` * The url used specifically to call get\_id\_lists * **rulesets\_sync\_interval**: Number, default `10` * The interval (in seconds) to poll for changes to your Statsig configuration * **idlists\_sync\_interval**: Number, default `60` * The interval (in seconds) to poll for changes to id lists * **disable\_rulesets\_sync**: Boolean, default `false` * Disable background syncing for rulesets * **disable\_idlists\_sync**: Boolean, default `false` * Disable background syncing for id lists * **logging\_interval\_seconds**: Number, default `60` * How often to flush logs to Statsig * **logging\_max\_buffer\_size**: Number, default `1000`, can be set lower but the server drops anything over 1000 * The maximum number of events to batch before flushing logs to the server * **local\_mode**: Boolean, default `false` * Restricts the SDK to not issue any network requests and only respond with default values (or local overrides) * **bootstrap\_values**: String, default `nil` * A string that represents all rules for all feature gates, dynamic configs and experiments. It can be provided to bootstrap the Statsig server SDK at initialization in case your server runs into network issue or Statsig server is down temporarily. * **rules\_updated\_callback**: function, default `nil` * A callback function called whenever the rulesets update * **data\_store**: IDataStore, default `nil` * A class that extends IDataStore. Can be used to provide values from a common data store (like Redis) to initialize the Statsig SDK. * **idlist\_threadpool\_size**: Number, default `3` * The number of threads allocated to syncing IDLists * **logger\_threadpool\_size**: Number, default `3` * The number of threads allocated to posting event logs * **disable\_diagnostics\_logging**: Boolean, default `false` * Should diagnostics be logged. These include performance metrics for initialize * **disable\_sorbet\_logging\_handlers**: Boolean, default `false` * Statsig utilizes Sorbet (https://sorbet.org) to ensure type safety of the SDK. This includes logging to console when errors are detected. You can disable this logging by setting this flag to true. * **network\_timeout**: Number, default `nil` * Maximum number of seconds to wait for a network call before timing out * **post\_logs\_retry\_limit**: Number, default `3` * Number of times to retry sending a batch of failed log events * **post\_logs\_retry\_backoff**: Number/Function, default `nil` * The number of seconds, or a function that returns the number of seconds based on the number of retries remaining which overrides the default backoff time between retries * **user\_persistent\_storage**: IUserPersistentStorage, default `nil` * A storage adapter for persisted values. Can be used for sticky bucketing users in experiments. Implements Statsig::Interfaces::IUserPersistentStorage. ## Shutdown To gracefully shutdown the SDK and ensure all events are flushed: ```ruby Statsig.shutdown ``` ## Client SDK bootstrapping The Statsig server SDK can generate the initialization values for a client SDK. This is useful for server-side rendering (SSR) or when you want to pre-fetch values for a client. ```ruby values = Statsig.get_client_initialize_response(user); # Hash[String, Any] | Nil ``` ## Local Overrides You can override the values returned by the SDK for testing purposes, which is useful for local development when testing specific scenarios. ```ruby # Adding gate overrides Statsig.override_gate("a_gate_name", true) # Adding config overrides Statsig.override_config("a_config_name", {"key" => "value"}) ``` {% callout type="note" %} 1. These only apply locally - they don't update definitions in the Statsig console or elsewhere. 2. The local override API isn't designed to be a full mock. It's only a convenient way to override the value of the gate/config/etc. {% /callout %} ## Manual Exposures Statsig SDKs automatically log an exposure event every time a gate/experiment/config is checked. In some scenarios, you may want to control when to log an exposure. **Gates** ```ruby result = Statsig.check_gate(user, 'a_gate_name', CheckGateOptions.new(disable_log_exposure: true)) ``` ```ruby Statsig.manually_log_gate_exposure(user, 'a_gate_name') ``` **Configs** ```ruby config = Statsig.get_config(user, 'a_dynamic_config_name', GetConfigOptions.new(disable_log_exposure: true)) ``` ```ruby Statsig.manually_log_config_exposure(user, 'a_dynamic_config_name') ``` **Experiments** ```ruby experiment = Statsig.get_experiment(user, 'an_experiment_name', GetExperimentOptions.new(disable_log_exposure: true)) ``` ```ruby Statsig.manually_log_experiment_exposure(user, 'an_experiment_name') ``` **Layers** ```ruby layer = Statsig.get_layer(user, 'a_layer_name', GetLayerOptions.new(disable_log_exposure: true)) paramValue = layer.get('a_param_name', 'fallback_value') ``` ```ruby Statsig.manually_log_layer_parameter_exposure(user, 'a_layer_name', 'a_param_name') ``` ## User persistent storage User Persistent Storage is a storage adapter for running sticky experiments that persists user assignments across sessions. ### Interface ```ruby class IUserPersistentStorage def load(key) nil end def save(key, data) end end ``` ### Example Implementation ```ruby class DummyPersistentStorageAdapter < Statsig::Interfaces::IUserPersistentStorage attr_accessor :store def initialize @store = {} end def load(key) return nil unless @store&.key?(key) @store[key] end def save(key, data) @store[key] = data end end ``` ## Multi-instance usage To create multiple independent instances of the Statsig SDK (for example, to use different API keys or configurations), use the instance-based approach: ```ruby sdk_instance = StatsigDriver.new(secret_key, options, error_callback) ``` ## FAQ #### How do I run experiments for logged out users? Refer to the guide on [device level experiments](/guides/first-device-level-experiment). #### How can I mock or override the SDK for testing? Starting in `v1.12.0+`, the Ruby SDK supports `localMode` and `overrides`. Refer to [Local Overrides](#local-overrides). * `localMode` is a boolean parameter in `StatsigOptions` when initializing the SDK. It restricts all network traffic, so the SDK operates offline and only returns default or override values. #### Can I generate the initialize response for a client SDK using the Ruby server SDK? Yes. Refer to [Client SDK Bootstrapping](#client-sdk-bootstrapping). ## Reference ### Type StatsigUser ```ruby export type StatsigUser = { class StatsigUser attr_accessor :user_id attr_accessor :email attr_accessor :ip attr_accessor :user_agent attr_accessor :country attr_accessor :locale attr_accessor :app_version attr_accessor :statsig_environment attr_accessor :custom_ids # Hash of key:string value:string attr_accessor :private_attributes # Hash of key:string value:string @custom # Hash of key:string value:string def initialize(user_hash) @statsig_environment = Hash.new if user_hash.is_a?(Hash) @user_id = user_hash['userID'] || user_hash['user_id'] @user_id = @user_id.to_s unless @user_id.nil? @email = user_hash['email'] @ip = user_hash['ip'] @user_agent = user_hash['userAgent'] || user_hash['user_agent'] @country = user_hash['country'] @locale = user_hash['locale'] @app_version = user_hash['appVersion'] || user_hash['app_version'] @custom = user_hash['custom'] if user_hash['custom'].is_a? Hash @statsig_environment = user_hash['statsigEnvironment'] @private_attributes = user_hash['privateAttributes'] if user_hash['privateAttributes'].is_a? Hash custom_ids = user_hash['customIDs'] || user_hash['custom_ids'] @custom_ids = custom_ids if custom_ids.is_a? Hash end end end ``` ### Type StatsigOptions ```ruby class StatsigOptions attr_accessor :environment attr_accessor :download_config_specs_url attr_accessor :log_event_url attr_accessor :get_id_lists_url attr_accessor :rulesets_sync_interval attr_accessor :idlists_sync_interval attr_accessor :disable_rulesets_sync attr_accessor :disable_idlists_sync attr_accessor :logging_interval_seconds attr_accessor :logging_max_buffer_size attr_accessor :local_mode attr_accessor :bootstrap_values attr_accessor :rules_updated_callback attr_accessor :data_store attr_accessor :idlist_threadpool_size attr_accessor :logger_threadpool_size attr_accessor :disable_diagnostics_logging attr_accessor :disable_sorbet_logging_handlers attr_accessor :network_timeout attr_accessor :post_logs_retry_limit attr_accessor :post_logs_retry_backoff attr_accessor :user_persistent_storage def initialize( environment = nil, download_config_specs_url: nil, log_event_url: nil, get_id_lists_url: nil, rulesets_sync_interval: 10, idlists_sync_interval: 60, disable_rulesets_sync: false, disable_idlists_sync: false, logging_interval_seconds: 60, logging_max_buffer_size: 1000, local_mode: false, bootstrap_values: nil, rules_updated_callback: nil, data_store: nil, idlist_threadpool_size: 3, logger_threadpool_size: 3, disable_diagnostics_logging: false, disable_sorbet_logging_handlers: false, network_timeout: nil, post_logs_retry_limit: 3, post_logs_retry_backoff: nil, user_persistent_storage: nil ) end end ``` ### DataStore ```ruby module Statsig module Interfaces class IDataStore def init end def get(key) nil end def set(key, value) end def shutdown end end end end ``` ================================================================================ # .NET Server SDK URL: https://docs.statsig.com/server-core/dotnet-core Updated: 2026-07-11 ================================================================================ ## Setup the SDK {% steps %} {% step title="Install the SDK" %} ## Installation ```bash dotnet add package Statsig.Dotnet ``` Or add the package reference to your `.csproj` file: ```xml <PackageReference Include="Statsig.Dotnet" Version="X.X.X" /> ``` ### Requirements * **.NET 8.0** or later * **Windows, macOS, or Linux** (x64 and ARM64 supported) {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Server Secret Keys should always be kept private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} An optional `options` parameter accepts a `StatsigOptions` object to customize the SDK. ```csharp using Statsig; var statsig = new Statsig.Statsig("server-secret-key"); await statsig.Initialize(); ``` You can also provide custom options: ```csharp var options = new StatsigOptionsBuilder() .SetSpecsSyncIntervalMs(10000) .SetDisableAllLogging(false) .Build(); var statsig = new Statsig("server-secret-key", options); await statsig.Initialize(); ``` For shared instance usage: ```csharp var sharedStatsig = Statsig.NewShared("server-secret-key", options); await sharedStatsig.Initialize(); var statsig = Statsig.Shared(); ``` `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independent of your API calls. {% /step %} {% /steps %} ## Working with the SDK ### Checking a Feature Flag/Gate After the SDK is initialized, you can fetch a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are **CLOSED** or **OFF** (equivalent to `return false;`) by default. All APIs require a user object (refer to [Statsig user](#statsig-user)). To check a gate for a user: ```csharp var user = new StatsigUserBuilder() .SetUserID("user_123") .SetEmail("user@example.com") .Build(); var gateValue = statsig.CheckGate(user, "new_feature_gate"); if (gateValue) { // Gate is on, enable new feature } else { // Gate is off } ``` You can also disable exposure logging for this evaluation: ```csharp var options = new EvaluationOptions(disableExposureLogging: true); var gateValue = statsig.CheckGate(user, "new_feature_gate", options); ``` ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional user targeting. To send different values (strings, numbers, etc.) to clients based on user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The API is similar to Feature Gates, but returns a JSON object from which you can retrieve typed parameters. For example: ```csharp var user = new StatsigUserBuilder() .SetUserID("user_123") .Build(); var config = statsig.GetDynamicConfig(user, "product_config"); var productName = config.Get<string>("product_name", "Default Product"); var price = config.Get<double>("price", 9.99); var isEnabled = config.Get<bool>("enabled", false); var features = config.Get<List<string>>("features", new List<string>()); Console.WriteLine($"Config Name: {config.Name}"); Console.WriteLine($"Group Name: {config.GroupName}"); Console.WriteLine($"Rule ID: {config.RuleID}"); ``` ### Getting a Layer/Experiment **Layers/Experiments** let you run A/B/n experiments. Two APIs are available, but [layers](/experiments/layers-overview) are recommended because layers make parameters reusable and support mutually exclusive experiments. ```csharp var user = new StatsigUserBuilder() .SetUserID("user_123") .Build(); var experiment = statsig.GetExperiment(user, "button_color_test"); var buttonColor = experiment.Get<string>("color", "blue"); var fontSize = experiment.Get<int>("font_size", 14); var showBorder = experiment.Get<bool>("show_border", true); Console.WriteLine($"Experiment Name: {experiment.Name}"); Console.WriteLine($"Group Name: {experiment.GroupName}"); Console.WriteLine($"Rule ID: {experiment.RuleID}"); Console.WriteLine($"Button Color: {buttonColor}"); ``` ```csharp var user = new StatsigUserBuilder() .SetUserID("user_123") .Build(); var layer = statsig.GetLayer(user, "user_prefs_layer"); var theme = layer.Get<string>("theme", "light"); var language = layer.Get<string>("language", "en"); var notifications = layer.Get<bool>("notifications_enabled", true); Console.WriteLine($"Layer Name: {layer.Name}"); Console.WriteLine($"Allocated Experiment: {layer.AllocatedExperimentName}"); Console.WriteLine($"Group Name: {layer.GroupName}"); Console.WriteLine($"Rule ID: {layer.RuleID}"); ``` The SDK automatically logs layer parameter access as exposure events unless you disable this behavior with `EvaluationOptions`. ### Logging an Event After setting up a Feature Gate or Experiment, track custom events to measure how features or experiment groups affect user behavior. Call the Log Event API and specify the user and event name. You can also provide a value and metadata: ```csharp var user = new StatsigUserBuilder() .SetUserID("user_123") .Build(); statsig.LogEvent(user, "button_clicked"); statsig.LogEvent(user, "purchase_completed", 29.99); statsig.LogEvent(user, "page_view", "homepage", new Dictionary<string, string> { ["referrer"] = "google", ["campaign"] = "summer_sale" }); statsig.LogEvent(user, "video_watched", 120, new Dictionary<string, string> { ["video_id"] = "abc123", ["quality"] = "1080p" }); ``` The `LogEvent` method supports multiple overloads: * `LogEvent(user, eventName)` * `LogEvent(user, eventName, stringValue, metadata)` * `LogEvent(user, eventName, intValue, metadata)` * `LogEvent(user, eventName, doubleValue, metadata)` ### Retrieving Feature Gate Metadata To retrieve more information about a gate evaluation than a boolean value, use the Get Feature Gate API, which returns a `FeatureGate` object: ```csharp var user = new StatsigUserBuilder() .SetUserID("user_123") .Build(); var gate = statsig.GetFeatureGate(user, "new_feature_gate"); Console.WriteLine($"Gate Name: {gate.Name}"); Console.WriteLine($"Gate Value: {gate.Value}"); Console.WriteLine($"Rule ID: {gate.RuleID}"); Console.WriteLine($"ID Type: {gate.IDType}"); if (gate.EvaluationDetails != null) { Console.WriteLine($"Config Sync Time: {gate.EvaluationDetails.ConfigSyncTime}"); Console.WriteLine($"Init Time: {gate.EvaluationDetails.InitTime}"); Console.WriteLine($"Reason: {gate.EvaluationDetails.Reason}"); } ``` ### Parameter Stores If you want dynamic control over whether a value comes from a Feature Gate, Experiment, or Dynamic Config outside your deployment cycle, use Parameter Stores. A Parameter Store lets you define a parameter that you can change at any point in the Statsig console. Parameter Stores are optional, but parameterizing your application supports future flexibility and allows non-technical Statsig users to turn parameters into experiments. ```csharp # Get a Parameter Store by name param_store = statsig.getParameterStore(user, "my_parameter_store") ``` ### Retrieving Parameter Values Parameter Store provides methods for retrieving values of different types with fallback defaults. {% accordion title="Param Store Methods" %} * **GetBool(string, default(bool))**: Pulls key value of type boolean * **GetString(string, "")**: Pulls key value of type string * **GetLong(string, default(long))**: Pulls key value of type long * **GetDouble(string, default(double))**: Pulls key value of type double * **GetList(string, new List\<>())**: Pulls key value of type list * **GetDictionary(string, new Dictionary\<string, object>())**: Pulls key value of type Dictionary {% /accordion %} ### Evaluation options You can disable exposure logging when retrieving a parameter store: ```csharp var store = statsig.GetParameterStore(user, name!, options); if (store == null) { throw new Exception($"Parameter store {name} not found"); } var x = store.GetBool(paramName, default(bool)); var y = store.GetString(paramName, ""); var z = store.GetDictionary(paramName, new Dictionary<string, object>()); ``` ## Using shared instance To access a single Statsig instance globally throughout your codebase, use the shared instance singleton pattern: ```csharp var sharedStatsig = Statsig.NewShared("server-secret-key"); await sharedStatsig.Initialize(); // Later, anywhere in your codebase var statsig = Statsig.Shared(); // Use the shared instance var result = statsig.CheckGate(user, "my_gate"); ``` The shared instance is useful for: * Singleton pattern usage across your application * Dependency injection scenarios * Avoiding multiple SDK instances Clean up the shared instance on shutdown: ```csharp var statsig = Statsig.Shared(); await statsig.FlushEvents(); await statsig.Shutdown(); Statsig.RemoveSharedInstance(); ``` ## Manual exposures By default, the SDK automatically logs an exposure event when you check a gate, get a config, get an experiment, or call `get()` on a layer parameter. To delay exposure logging until the user actually uses the feature, use manual exposures. All main SDK functions (`CheckGate`, `GetDynamicConfig`, `GetExperiment`, `GetLayer`) accept an optional `EvaluationOptions` parameter. When `disableExposureLogging` is set to `true`, the SDK doesn't automatically log an exposure event. You can then manually log the exposure using the corresponding manual exposure logging method: {% tabs %} {% tab title="Feature Gates" %} ```csharp var result = statsig.CheckGate(user, "a_gate_name", new EvaluationOptions(disableExposureLogging: true)); ``` ```csharp statsig.ManuallyLogGateExposure(user, "a_gate_name"); ``` {% /tab %} {% tab title="Dynamic Configs" %} ```csharp var config = statsig.GetDynamicConfig(user, "a_dynamic_config_name", new EvaluationOptions(disableExposureLogging: true)); ``` ```csharp statsig.ManuallyLogDynamicConfigExposure(user, "a_dynamic_config_name"); ``` {% /tab %} {% tab title="Experiments" %} ```csharp var experiment = statsig.GetExperiment(user, "an_experiment_name", new EvaluationOptions(disableExposureLogging: true)); ``` ```csharp statsig.ManuallyLogExperimentExposure(user, "an_experiment_name"); ``` {% /tab %} {% tab title="Layers" %} ```csharp var layer = statsig.GetLayer(user, "a_layer_name", new EvaluationOptions(disableExposureLogging: true)); var paramValue = layer.Get("a_param_name", "fallback_value"); ``` ```csharp statsig.ManuallyLogLayerParameterExposure(user, "a_layer_name", "a_param_name"); ``` {% /tab %} {% /tabs %} ## Statsig User The `StatsigUser` object represents a user in Statsig. You must provide a `userID` or at least one of the `customIDs` to identify the user. When calling APIs that require a user, pass as much information as possible to enable advanced gate and config conditions (such as country or OS/browser checks) and to measure experiment impact accurately. As explained in [why an ID is always required for server SDKs](/sdks/user#why-is-an-id-always-required-for-server-sdks), at least one identifier (userID or customID) is required to provide a consistent experience for each user. In addition to `userID`, the top-level fields on `StatsigUser` are: `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion`. You can also pass key-value pairs in the `custom` field for targeting. ### Private attributes Private attributes are user attributes used for evaluation but not forwarded to any integrations. Use them for PII or sensitive data that you don't want to send to third-party services. `StatsigUser` represents the user context for feature flag evaluation. Use `StatsigUserBuilder` to create user instances: ```csharp var user = new StatsigUserBuilder() .SetUserID("user_123") .SetEmail("user@example.com") .SetIP("192.168.1.1") .SetUserAgent("Mozilla/5.0...") .SetCountry("US") .SetLocale("en-US") .SetAppVersion("1.2.3") .SetCustomIDs(new Dictionary<string, string> { ["employee_id"] = "emp_456", ["team_id"] = "team_789" }) .AddCustomID("department_id", "dept_123") .SetCustomProperties(new Dictionary<string, object> { ["subscription_tier"] = "premium", ["account_age_days"] = 365, ["is_beta_user"] = true }) .AddCustomProperty("last_login", DateTime.UtcNow) .SetPrivateAttributes(new Dictionary<string, object> { ["internal_user_score"] = 0.85, ["risk_level"] = "low" }) .AddPrivateAttribute("pii_hash", "abc123def456") .Build(); ``` ## Builder methods {% accordion title="Builder Methods" %} * **SetUserID(string)**: Set the primary user ID * **SetEmail(string)**: Set user email * **SetIP(string)**: Set user IP address * **SetUserAgent(string)**: Set browser user agent * **SetCountry(string)**: Set user country * **SetLocale(string)**: Set user locale * **SetAppVersion(string)**: Set app version * **SetCustomIDs(Dictionary\<string, string>)**: Set all custom IDs * **AddCustomID(string, string)**: Add a single custom ID * **SetCustomProperties(Dictionary\<string, object>)**: Set all custom properties * **AddCustomProperty(string, object)**: Add a single custom property * **SetPrivateAttributes(Dictionary\<string, object>)**: Set all private attributes * **AddPrivateAttribute(string, object)**: Add a single private attribute * **Build()**: Create the StatsigUser instance {% /accordion %} Dispose of StatsigUser instances when done: ```csharp using var user = new StatsigUserBuilder() .SetUserID("user_123") .Build(); ``` ## Statsig Options You can pass an optional `options` parameter in addition to `sdkKey` during initialization to customize the Statsig client. {% accordion title="StatsigOptions" %} `StatsigOptions` can be configured using the `StatsigOptionsBuilder` pattern: ```csharp var options = new StatsigOptionsBuilder() .SetSpecsURL("https://custom-api.statsig.com/v1/download_config_specs") .SetLogEventURL("https://custom-api.statsig.com/v1/rgstr") .SetEnvironment("production") .SetSpecsSyncIntervalMs(30000) .SetEventLoggingMaxQueueSize(1000) .SetWaitForCountryLookupInit(true) .SetWaitForUserAgentInit(true) .SetDisableCountryLookup(false) .SetDisableUserAgentParsing(false) .SetDisableAllLogging(false) .SetInitTimeoutMs(3000) .SetFallbackToStatsigApi(false) .SetEnableIDLists(true) .SetIDListsURL("https://custom-api.statsig.com/v1/get_id_lists") .SetIDListsSyncIntervalMs(60000) .SetGlobalCustomFields(new Dictionary<string, object> { ["app_version"] = "1.2.3", ["build_number"] = "456" }) .Build(); var statsig = new Statsig("server-secret-key", options); ``` ## Available options * **SetSpecsURL(string)**: Override the default specs download endpoint * **SetLogEventURL(string)**: Override the default event logging endpoint * **SetEnvironment(string)**: Set the environment tier (e.g., "production", "staging") * **SetSpecsSyncIntervalMs(int)**: How often to sync configuration specs (default: 10000ms) * **SetEventLoggingMaxQueueSize(int)**: Maximum events to queue before flushing * **SetWaitForCountryLookupInit(bool)**: Wait for country lookup initialization * **SetWaitForUserAgentInit(bool)**: Wait for user agent parsing initialization * **SetDisableCountryLookup(bool)**: Disable automatic country detection * **SetDisableUserAgentParsing(bool)**: Disable user agent parsing * **SetDisableAllLogging(bool)**: Disable all event logging * **SetInitTimeoutMs(int)**: Maximum time in milliseconds to wait for SDK initialization (default: 3000ms) * **SetFallbackToStatsigApi(bool)**: Fallback to Statsig API when custom adapters fail (default: false) * **SetEnableIDLists(bool)**: Enable ID list targeting * **SetIDListsURL(string)**: Override the default ID lists endpoint * **SetIDListsSyncIntervalMs(int)**: How often to sync ID lists (default: 60000ms) * **SetGlobalCustomFields(Dictionary\<string, object>)**: Global custom fields for all events * **SetSpecAdapterConfig(SpecAdapterConfig)**: Configure a custom spec source such as [Statsig Forward Proxy](/infrastructure/api_proxy/introduction) * **SetProxyConfig(ProxyConfig)**: Configuration for connecting through a proxy server {% /accordion %} ### Proxy and custom network routing The `.NET` Server Core SDK uses `SetProxyConfig(ProxyConfig)` for a standard outbound HTTP proxy. If you are routing config sync through [Statsig Forward Proxy](/infrastructure/api_proxy/introduction) or another custom spec source, use `SetSpecAdapterConfig(SpecAdapterConfig)`. If you only need custom Statsig endpoints, use `SetSpecsURL`, `SetLogEventURL`, and `SetIDListsURL`. ```csharp var proxyConfig = new ProxyConfig { ProxyHost = "proxy.example.com", ProxyPort = 8080, ProxyAuth = "username:password", // Optional ProxyProtocol = "http", // Optional: "http" or "https" CaCertPath = "/etc/ssl/certs/corporate-ca.pem" // Optional }; var options = new StatsigOptionsBuilder() .SetProxyConfig(proxyConfig) .Build(); var statsig = new Statsig("server-secret-key", options); ``` ### ProxyConfig properties * **ProxyHost** (string): The hostname or IP address of the proxy server * **ProxyPort** (int): The port number of the proxy server * **ProxyAuth** (string, optional): Authentication credentials in the format "username:password" * **ProxyProtocol** (string, optional): The protocol to use for the proxy connection ("http" or "https") * **CaCertPath** (string, optional): Path to a PEM CA bundle for outbound TLS ### Statsig Forward Proxy example ```csharp var specAdapterConfig = new SpecAdapterConfig( adapterType: "network_grpc_websocket", specsUrl: "http://forward-proxy.internal:50051" ); var options = new StatsigOptionsBuilder() .SetSpecAdapterConfig(specAdapterConfig) .SetFallbackToStatsigApi(true) .Build(); ``` {% accordion title="EvaluationOptions" %} `EvaluationOptions` allows you to customize the behavior of feature flag evaluations: ```csharp var options = new EvaluationOptions(disableExposureLogging: true); var gateValue = statsig.CheckGate(user, "feature_gate", options); var config = statsig.GetDynamicConfig(user, "product_config", options); var experiment = statsig.GetExperiment(user, "button_test", options); var layer = statsig.GetLayer(user, "user_prefs_layer", options); ``` ## Options * **DisableExposureLogging**: When `true`, prevents automatic exposure event logging for this evaluation. Useful when you want to evaluate a feature flag without affecting analytics or experiment results. ## Use cases * **Internal Tools**: Check flag values for debugging without affecting user metrics * **Conditional Logic**: Evaluate flags as part of complex logic where exposure should be logged manually later When exposure logging is disabled, you can manually log exposures later using the manual exposure methods: ```csharp var options = new EvaluationOptions(disableExposureLogging: true); var gateValue = statsig.CheckGate(user, "feature_gate", options); if (shouldLogExposure) { statsig.ManuallyLogGateExposure(user, "feature_gate"); } ``` {% /accordion %} ## Shutting Statsig Down Because events are batched and periodically flushed, some events may not have been sent when your app or server shuts down. To ensure all logged events are flushed, call `shutdown()` before shutting down your app or server. ```csharp await statsig.FlushEvents(); await statsig.Shutdown(); statsig.Dispose(); ``` For shared instances: ```csharp var statsig = Statsig.Shared(); await statsig.FlushEvents(); await statsig.Shutdown(); Statsig.RemoveSharedInstance(); ``` ## Methods * **FlushEvents()**: Immediately flush any pending events to Statsig servers * **Shutdown()**: Gracefully shutdown the SDK, flushing events and cleaning up resources * **Dispose()**: Release native resources (implements IDisposable) Call `FlushEvents()` before `Shutdown()` to ensure all events are sent. Always call `Dispose()` or use `using` statements to clean up resources. ## Client SDK Bootstrapping | SSR If you use the Statsig client SDK in a browser or mobile app, you can bootstrap the client SDK with values from the server SDK to avoid a network request on the client. This is useful for server-side rendering (SSR) or to reduce the number of network requests on the client. ```csharp var user = new StatsigUserBuilder() .SetUserID("user_123") .Build(); var initResponse = statsig.GetClientInitializeResponse(user); var options = new ClientInitResponseOptions { HashAlgorithm = "sha256", ClientSDKKey = "client-sdk-key", IncludeLocalOverrides = false }; var customInitResponse = statsig.GetClientInitializeResponse(user, options); ``` The `GetClientInitializeResponse` method returns a JSON string containing the initialization data needed by client-side SDKs. This enables server-side rendering and reduces client initialization time. ## ClientInitResponseOptions * **HashAlgorithm**: Hash algorithm for response integrity (default: "djb2") * **ClientSDKKey**: Client SDK key to include in response * **IncludeLocalOverrides**: Whether to include local overrides in the response (default: false) ### Working with IP or UserAgent values The server SDK doesn't automatically use `ip` or `userAgent` for gate evaluation because it doesn't have access to request headers. To use derived attributes such as Browser Name/Version, OS Name/Version, and Country, manually set the `ip` and `userAgent` fields on the user object when calling `GetClientInitializeResponse`. ### Working with IDs To ensure accurate config evaluation, the server SDK needs access to all user attributes that the client SDK uses. Pass all of these attributes to the server SDK, using cookies if needed to ensure they are attached on first requests. If the user objects on the client and server aren't identical, modern SDKs throw an `InvalidBootstrap` warning. Client SDKs also auto-generate a `StableID`. Manage the lifecycle of this ID to keep it consistent between client and server. Managing this with a cookie is often the simplest approach; refer to [Keeping StableID Consistent](/client/javascript-sdk-stable-id#keeping-stableid-consistent). If `StableID` differs between client and server, a `BootstrapStableIDMismatch` warning appears, and checks with that warning don't contribute to experiment analyses. ### getClientInitializeResponse and the legacy JS SDK If you are migrating from the legacy JS Client, you will need to make some updates to how your server SDK generates values. The default hashing algorithm was changed from `sha256` to `djb2` for performance and size reasons. ## Local overrides Local Overrides let you override the values of gates, configs, experiments, and layers for testing purposes without changing the configuration in the Statsig console. ```csharp statsig.OverrideGate("test_gate", true); statsig.OverrideDynamicConfig("test_config", new Dictionary<string, object> { ["color"] = "red", ["size"] = 42, ["enabled"] = true }); statsig.OverrideExperiment("test_experiment", new Dictionary<string, object> { ["variant"] = "treatment", ["multiplier"] = 1.5 }); statsig.OverrideExperimentByGroupName("test_experiment", "treatment_group"); statsig.OverrideLayer("test_layer", new Dictionary<string, object> { ["theme"] = "dark", ["font_size"] = 16 }); statsig.OverrideParameterStore("testing123", new Dictionary<string, object>() { ["brush_color"] = "blue", ["monochromatic"] = true, ["weight"] = 42, ["gradient"] = 3.14, ["pen_sizes"] = [1, 2, 3, 4, 5], ["artwork"] = new Dictionary<string, object>() { ["nesting"] = "treatment" } }); ``` You can also specify a user ID for targeted overrides: ```csharp statsig.OverrideGate("test_gate", true, "user_123"); statsig.OverrideDynamicConfig("test_config", new Dictionary<string, object> { ["special_feature"] = true }, "user_123"); ``` Local overrides are useful for: * Testing specific configurations during development * QA testing with known values * Debugging feature flag behavior * Integration testing with predictable results Overrides persist for the lifetime of the Statsig instance and affect all evaluations unless you provide a specific user ID. ## Persistent storage The Persistent Storage interface lets you implement custom storage for user-specific configurations. Use it to persist user assignments across sessions, ensuring consistent experiment groups when users return. This is useful for client-side A/B testing where users must always see the same variant. ```csharp using System.Collections.Generic; using Statsig; // Implement PersistentStorage to control how user stickiness is stored. public class MyPersistentStorage : PersistentStorage { private readonly Dictionary<string, Dictionary<string, StickyValues>> _store = new(); public override IDictionary<string, StickyValues> Load(string key) { // Load persisted values for this user from your backing store. return _store.TryGetValue(key, out var configs) ? new Dictionary<string, StickyValues>(configs) : new Dictionary<string, StickyValues>(); } public override void Save(string key, string configName, StickyValues data) { // Persist the sticky assignment (database, Redis, etc.). if (!_store.TryGetValue(key, out var configs)) { configs = new Dictionary<string, StickyValues>(); _store[key] = configs; } configs[configName] = data; } public override void Delete(string key, string configName) { // Remove the persisted value for this config/user. if (_store.TryGetValue(key, out var configs)) { configs.Remove(configName); } } } ``` ## Data store The Data Store interface lets you implement custom storage for Statsig configurations, enabling advanced caching strategies and integration with your preferred storage systems such as Redis. {% codetabs %} ```csharp Csharp using System.Threading.Tasks; using Statsig; public class MyDataStore : DataStore { public Task Initialize() { // Perform any initialization needed for your data store. return Task.CompletedTask; } public Task Shutdown() { // Clean up resources. return Task.CompletedTask; } public DataStoreResponse Get(string key) { // Retrieve data for the given key. // This is called during SDK evaluation. return Task.FromResult<DataStoreResponse?>(null); } public void Set(string key, string value, long? time = null) { // Store data for the given key. // Called when SDK receives updates from Statsig. return Task.CompletedTask; } public bool SupportsPollingUpdatesFor(string key) { // Return true if your store supports polling updates for this key, false otherwise. return Task.FromResult(false).CompletedTask; } } // Use data store var options = new StatsigOptionsBuilder() .SetDataStore(new MyDataStore()) .Build(); var statsig = new Statsig("server-secret-key", options); await statsig.Initialize(); ``` {% /codetabs %} ## Performance benefits The .NET Core SDK uses Statsig's high-performance Rust evaluation engine through FFI bindings: * Native Rust evaluation engine handles all rule processing * .NET wrapper provides familiar C# APIs and type safety * Automatic memory management between .NET and Rust boundaries * Thread-safe operations across the FFI boundary ## Async/await support All network operations are fully async: ```csharp await statsig.Initialize(); await statsig.FlushEvents(); await statsig.Shutdown(); ``` Evaluation methods are synchronous for optimal performance: ```csharp var result = statsig.CheckGate(user, "gate_name"); ``` ## Thread safety The Statsig instance is thread-safe and can be used concurrently across multiple threads. Use the shared instance singleton pattern for application-wide usage: ```csharp var sharedStatsig = Statsig.NewShared("server-secret-key"); await sharedStatsig.Initialize(); var statsig = Statsig.Shared(); ``` ================================================================================ # C++ Server SDK URL: https://docs.statsig.com/server-core/cpp-core Updated: 2026-07-11 ================================================================================ ## Setup the SDK {% steps %} {% step title="Install the SDK" %} ## Installation All core logic is written in Rust. Statsig attaches prebuilt binaries as assets with each release, so a Rust environment isn't required. The CMakeLists.txt file handles the build configuration. If you encounter any installation or build issues, contact us. ```CMake FetchContent_Declare( Statsig GIT_REPOSITORY https://github.com/statsig-io/statsig-cpp-core.git GIT_TAG 0.12.2-rc.1 ) FetchContent_MakeAvailable(Statsig) target_include_directories(CppApp PRIVATE ${statsig_SOURCE_DIR}/include) target_include_directories(CppApp PRIVATE ${statsig_SOURCE_DIR}/src) target_link_libraries(CppApp PRIVATE Statsig) ``` {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Server Secret Keys should always be kept private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} An optional `options` parameter accepts a `StatsigOptions` object to customize the SDK. ```cpp #include <statsig/statsig.h> statsig_cpp_core::StatsigOptionsBuilder optionsBuilder; optionsBuilder.environment = "development"; optionsBuilder.specs_url = api_v2 + "/download_config_specs"; optionsBuilder.log_event_url = api + "/log_event"; optionsBuilder.id_lists_url = api + "/get_id_lists"; statsig_cpp_core::StatsigOptions options = optionsBuilder.build(); Statsig statsig("server-secret-key", options); statsig.initializeBlocking().wait(); ``` `initialize` performs a network request. After `initializeBlocking` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independent of your API calls. {% /step %} {% /steps %} ## Tested platforms {% callout type="note" %} The upstream Statsig docs source doesn't yet include a `testedPlatforms` snippet for C++ Core. Use the [statsig-cpp-core](https://github.com/statsig-io/statsig-cpp-core) release assets and your target Linux distribution when validating Docker images. Add a tested-platforms table here when the snippet is published upstream. {% /callout %} ## Working with the SDK ### Checking a Feature Flag/Gate After the SDK is initialized, you can fetch a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are **CLOSED** or **OFF** (equivalent to `return false;`) by default. All APIs require a user object (refer to [Statsig user](#statsig-user)). To check a gate for a user: ```cpp if (statsig.checkGate(user, "a_gate")) { // Gate is on, enable new feature } else { // Gate is off } ``` You can also disable exposure logging for this evaluation: ```cpp FeatureGateEvaluationOptions options; options.disable_exposure_logging = true; bool gateValue = statsig.check_gate(user, "a_gate", options); ``` ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional user targeting. To send different values (strings, numbers, etc.) to clients based on user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The API is similar to Feature Gates, but returns a JSON object from which you can retrieve typed parameters. For example: ```cpp // Get a dynamic config for a specific user DynamicConfig config = statsig.getDynamicConfig(user, "a_config"); // Access config values (We will provide accessors) std::string product_name = config.value.get("product_name", "Awesome Product v1"); double price = config.value.get("price", 10.0); // Access evaluation details such as rule id statsig_cpp_core::EvaluationDetails detail = config.details; std::cout << config.rule_id << std::endl; // The ID of the rule that served this config std::cout << config.id_type << std::endl; // The type of the evaluation (experiment, config, etc) // Advanced Usage: // You can disable exposure logging for this specific check DynamicConfigEvaluationOptions options; options.disable_exposure_logging = true; config = statsig.getDynamicConfig(user, "a_config", options); ``` ### Getting a Layer/Experiment **Layers/Experiments** let you run A/B/n experiments. Two APIs are available, but [layers](/experiments/layers-overview) are recommended because layers make parameters reusable and support mutually exclusive experiments. ```cpp // Get a experiment for a specific user statsig_cpp_core::Experiment exp = statsig.getExpriment(user, "an_experiment"); // Access config values (We will provide accessors) auto product_name = exp.value.get("product_name", "Awesome Product v1"); auto price = exp.value.get("price", 10.0); // Access evaluation details such as rule id statsig_cpp_core::EvaluationDetails detail = exp.details; std::cout << exp.rule_id << std::endl; // The ID of the rule that served this config std::cout << exp.id_type << std::endl; // The type of the evaluation (experiment, config, etc) // Advanced Usage: // You can disable exposure logging for this specific check ExperimentEvaluationOptions options; options.disable_exposure_logging = true; config = statsig.getExperiment(user, "an_experiment", options); ``` ### Retrieving Feature Gate Metadata To retrieve more information about a gate evaluation than a boolean value, use the Get Feature Gate API, which returns a `FeatureGate` object: ```cpp FeatureGate gate = statsig.getFeatureGate(user, "example_gate"); std::cout << gate.rule_id << std::endl; std::cout << gate.value << std::endl; ``` The `get_feature_gate()` method returns a `FeatureGate` object with: * `value`: The boolean gate value * `rule_id`: The ID of the rule that served this gate * `id_type`: The type of the evaluation * `evaluation_details`: Additional metadata about the evaluation ### Logging an Event After setting up a Feature Gate or Experiment, track custom events to measure how features or experiment groups affect user behavior. Call the Log Event API and specify the user and event name. You can also provide a value and metadata: ```cpp statsig.log_event( user, "add_to_cart", { {"price", "9.99"}, {"item_name", "diet_coke_48_pack"} } ); ``` The `log_event` method supports multiple overloads: * `log_event(user, event_name)` * `log_event(user, event_name, string_value)` * `log_event(user, event_name, string_value, metadata)` Statsig is adding support for numerical value and metadata. ## Statsig User The `StatsigUser` object represents a user in Statsig. You must provide a `userID` or at least one of the `customIDs` to identify the user. When calling APIs that require a user, pass as much information as possible to enable advanced gate and config conditions (such as country or OS/browser checks) and to measure experiment impact accurately. As explained in [why an ID is always required for server SDKs](/sdks/user#why-is-an-id-always-required-for-server-sdks), at least one identifier (userID or customID) is required to provide a consistent experience for each user. In addition to `userID`, the top-level fields on `StatsigUser` are: `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion`. You can also pass key-value pairs in the `custom` field to use for targeting. ### Private attributes Private attributes are user attributes used for evaluation but not forwarded to any integrations. Use them for PII or sensitive data that you don't want to send to third-party services. ```cpp statsig_cpp_core::UserBuilder builder; builder.setUserID(j["userID"]); builder.setCustomIDs(j["customIDs"]); builder.setCountry(j["customIDs"]); statsig_cpp_core::User user = builder.build(); // UserBuilder also supports deserialize from json ``` ## Statsig Options You can pass an optional `options` parameter in addition to `sdkKey` during initialization to customize the Statsig client. ### Available options ```cpp std::optional<std::string> specs_url; std::optional<std::string> id_lists_url; std::optional<std::string> log_event_url; std::optional<std::string> output_log_level; std::optional<std::string> environment; bool enable_id_lists = false; bool disable_all_logging = false; bool disable_country_lookup = false; bool disable_network = false; ``` ### Proxy and custom network routing The C++ Server Core SDK currently documents endpoint overrides rather than a dedicated outbound proxy config. Use `specs_url`, `log_event_url`, and `id_lists_url` to route Statsig network calls through your own endpoints or proxy layer. ### Example usage ```cpp statsig_cpp_core::StatsigOptionsBuilder optionsBuilder; optionsBuilder.environment = "development"; optionsBuilder.specs_url = api_v2 + "/download_config_specs"; optionsBuilder.log_event_url = api + "/log_event"; optionsBuilder.id_lists_url = api + "/get_id_lists"; statsig_cpp_core::StatsigOptions options = optionsBuilder.build(); ``` ## Shutting Statsig Down Because events are batched and periodically flushed, some events may not have been sent when your app or server shuts down. To ensure all logged events are flushed, call `shutdown()` before your application exits. ```cpp statsig.shutdownBlocking(); ``` The `shutdown()` method: * Flushes any pending events to Statsig servers * Cleans up resources * Waits for all operations to complete ## Reference ### API methods * `check_gate(user: StatsigUser, gate_name: str, options: Optional[FeatureGateEvaluationOptions] = None) -> bool` * `get_dynamic_config(user: StatsigUser, config_name: str, options: Optional[DynamicConfigEvaluationOptions] = None) -> DynamicConfig` * `get_experiment(user: StatsigUser, experiment_name: str, options: Optional[ExperimentEvaluationOptions] = None) -> DynamicConfig` * `get_layer(user: StatsigUser, layer_name: str, options: Optional[LayerEvaluationOptions] = None) -> Layer` * `get_feature_gate(user: StatsigUser, gate_name: str, options: Optional[FeatureGateEvaluationOptions] = None) -> FeatureGate` * `log_event(user: StatsigUser, event_name: str, value: Optional[Union[str, float]] = None, metadata: Optional[Dict[str, str]] = None) -> None` * `shutdown() -> AsyncResult[None]` ### Fields needed methods The following methods return information about which user fields are needed for evaluation. * `get_gate_fields_needed(gate_name: str) -> List[str]` * `get_dynamic_config_fields_needed(config_name: str) -> List[str]` * `get_experiment_fields_needed(experiment_name: str) -> List[str]` * `get_layer_fields_needed(layer_name: str) -> List[str]` These methods return a list of strings representing the user fields required to evaluate the specified gate, config, experiment, or layer. ================================================================================ # Rust Server SDK URL: https://docs.statsig.com/server-core/rust-core Updated: 2026-07-11 ================================================================================ ## Set up the SDK {% steps %} {% step title="Install the SDK" %} To use the SDK, add the Statsig Rust package to your Cargo.toml file: ```toml [dependencies] statsig-rust = "X.Y.Z" # Replace with the latest version ``` Or, you can use the cargo command: ```shell cargo add statsig-rust ``` You can find the latest version and documentation at [crates.io/crates/statsig-rust](https://crates.io/crates/statsig-rust). {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Keep Server Secret Keys private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} An optional `options` parameter accepts a StatsigOptions object to customize the SDK. ```rust use statsig_rust::{Statsig, StatsigOptions}; use std::sync::Arc; // Simple initialization let statsig = Statsig::new("server-secret-key", None); statsig.initialize().await?; // Or with StatsigOptions let mut options = StatsigOptions::default(); options.environment = Some("development".to_string()); let statsig = Statsig::new("server-secret-key", Some(Arc::new(options))); statsig.initialize().await?; // Don't forget to shutdown when done statsig.shutdown().await?; ``` `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ### Checking a Feature Flag/Gate After the SDK is initialized, you can fetch a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (equivalent to `return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example: ```rust use statsig_rust::{Statsig, StatsigUserBuilder}; let user = StatsigUserBuilder::new_with_user_id("a-user".to_string()).build(); if statsig.check_gate(&user, "a_gate") { // Gate is on, enable new feature } else { // Gate is off } ``` ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and similar types) to clients based on user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The API is similar to Feature Gates, but returns a full JSON object configurable on the server from which you can fetch typed parameters. ```rust use statsig_rust::{Statsig, StatsigUserBuilder, DynamicConfigEvaluationOptions}; use std::sync::Arc; // Get a dynamic config for a specific user let user = StatsigUserBuilder::new_with_user_id("my_user".to_string()).build(); let config = statsig.get_dynamic_config(&user, "a_config"); // Access config values with type-safe getters and fallback values let product_name = config.get_string("product_name", "Awesome Product v1"); // returns String let price = config.get_double("price", 10.0); // returns f64 let should_discount = config.get_bool("discount", false); // returns bool let quantity = config.get_int("quantity", 1); // returns i64 // Advanced Usage: // You can disable exposure logging for this specific check let mut options = DynamicConfigEvaluationOptions::default(); options.disable_exposure_logging = Some(true); let config = statsig.get_dynamic_config_with_options(&user, "a_config", &options); // The config object also provides metadata about the evaluation println!("{}", config.rule_id); // The ID of the rule that served this config println!("{}", config.id_type); // The type of the evaluation (experiment, config, etc) ``` ### Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available. Statsig recommends [Layers](/experiments/layers-overview) because they make parameters reusable and support mutually exclusive experiments. ```rust use statsig_rust::{Statsig, StatsigUserBuilder}; // Values via get_layer let user = StatsigUserBuilder::new_with_user_id("my_user".to_string()).build(); let layer = statsig.get_layer(&user, "user_promo_experiments"); let title = layer.get_string("title", "Welcome to Statsig!"); let discount = layer.get_double("discount", 0.1); // Via get_experiment let title_exp = statsig.get_experiment(&user, "new_user_promo_title"); let price_exp = statsig.get_experiment(&user, "new_user_promo_price"); let title = title_exp.get_string("title", "Welcome to Statsig!"); let discount = price_exp.get_double("discount", 0.1); ``` ### Parameter Stores Use Parameter Stores when you want to define a parameter without deciding whether it should be a Feature Gate, Experiment, or Dynamic Config. Parameter Stores let you change the parameter type at any point in the Statsig console without a new deployment. Parameter Stores are optional, but parameterizing your application provides future flexibility and allows non-technical Statsig users to turn parameters into experiments. ```jsx let param_store = statsig.get_parameter_store("my_parameters"); let param_store_value = param_store.get(&user, "my_parameter_value", false); //false is fallback value println!("param_store_value: {}", param_store_value); ``` ### Logging an Event To track custom events, call the Log Event API. Specify the user, event name, and an optional value or metadata object: ```rust use statsig_rust::{Statsig, StatsigUserBuilder}; use std::collections::HashMap; use crate::evaluation::dynamic_value::DynamicValue; // Create a user let user = StatsigUserBuilder::new_with_user_id("user_id".to_string()).build(); // Create metadata hashmap let mut metadata = HashMap::new(); metadata.insert("price".to_string(), "9.99".into()); metadata.insert("item_name".to_string(), "diet_coke_48_pack".into()); // Log the event statsig.log_event( &user, "add_to_cart", Some("SKU_12345".into()), // value as DynamicValue Some(metadata) ); ``` Learn more about identifying users, group analytics, and best practices for logging events in the [logging events guide](/guides/logging-events). ### Retrieving Feature Gate Metadata In certain scenarios, you may need more information about a gate evaluation than just a boolean value. For additional metadata about the evaluation, use the Get Feature Gate API, which returns a FeatureGate object: ```rust use statsig_rust::{Statsig, StatsigUserBuilder}; // Create a user let user = StatsigUserBuilder::new_with_user_id("user_id".to_string()).build(); // Get a feature gate let gate = statsig.get_feature_gate(&user, "example_gate"); // Access gate properties println!("{}", gate.rule_id); println!("{}", gate.value); // Boolean value of the gate ``` ## Using shared instance To create a single Statsig instance accessible globally throughout your codebase, use the shared instance functionality, which provides a singleton pattern: ```rust // Create a shared instance that can be accessed globally let statsig = Statsig::new_shared("server-secret-key", None).unwrap(); statsig.initialize().await?; // Access the shared instance from anywhere in your code let shared_statsig = Statsig::shared(); let is_feature_enabled = shared_statsig.check_gate(&user, "feature_name"); // Check if a shared instance exists if Statsig::has_shared_instance() { // Use the shared instance } // Remove the shared instance when no longer needed Statsig::remove_shared(); ``` The shared instance lets a single Statsig instance be created and accessed globally throughout your application. This is useful when multiple parts of the codebase need Statsig without passing an instance around. * `Statsig::new_shared(sdk_key, options)`: Creates a new shared instance of Statsig that can be accessed globally * `Statsig::shared()`: Returns the shared instance * `Statsig::has_shared_instance()`: Checks if a shared instance exists (useful when the shared instance may not be ready yet) * `Statsig::remove_shared()`: Removes the shared instance (useful when you want to switch to a new shared instance) {% callout type="note" %} `has_shared_instance()` and `remove_shared()` are helpful in specific scenarios but aren't required in most use cases where the shared instance is set up near the top of your application. Also note that only one shared instance can exist at a time. Attempting to create a second shared instance will result in an error. {% /callout %} ## Manual exposures By default, the SDK automatically logs an exposure event when you check a gate, get a config, get an experiment, or call get() on a parameter in a layer. To delay exposure logging (for example, to log only after the user actually uses the feature), use manual exposures. All main SDK functions (`check_gate`, `get_dynamic_config`, `get_experiment`, `get_layer`) accept an options parameter with a `disable_exposure_logging` field. When set to `true`, the SDK doesn't automatically log an exposure. You can then log the exposure manually at a later time: {% tabs %} {% tab title="Feature Gates" %} ```rust result = statsig.check_gate_with_options(&user, 'a_gate_name', FeatureGateEvaluationOptions {disable_exposure_logging: true}); ``` ```rust statsig.manually_log_gate_exposure(&user, 'a_gate_name') ``` {% /tab %} {% tab title="Dynamic Configs" %} ```rust config = statsig.get_dynamic_config_with_options(&user, 'a_dynamic_config_name', DynamicConfigEvaluationOptions {disable_exposure_logging: true}); ``` ```rust statsig.manually_log_dynamic_config_exposure(&user, 'a_dynamic_config_name') ``` {% /tab %} {% tab title="Experiments" %} ```rust experiment = statsig.get_experiment_with_options(&user, 'an_experiment_name', ExperimentEvaluationOptions {disable_exposure_logging: true}); ``` ```rust statsig.manually_log_experiment_exposure(&user, 'an_experiment_name') ``` {% /tab %} {% tab title="Layers" %} ```rust layer = statsig.get_layer_with_options(&user, 'a_layer_name', LayerEvaluationOptions {disable_exposure_logging: true}); paramValue = layer.get('a_param_name', 'fallback_value') ``` ```rust statsig.manually_log_layer_parameter_exposure(&user, 'a_layer_name', 'a_param_name') ``` {% /tab %} {% /tabs %} ## Statsig User The `StatsigUser` object represents a user in Statsig. You must provide a `userID` or at least one of the `customIDs` to identify the user. When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks) and to correctly measure the impact of experiments on metrics and events. As explained [here](/sdks/user#why-is-an-id-always-required-for-server-sdks), at least one identifier (userID or customID) is required to provide a consistent experience for a given user. In addition to userID, the top-level fields on StatsigUser are: email, ip, userAgent, country, locale, and appVersion. You can also pass any key-value pairs in an object or dictionary to the custom field and create targeting based on them. ### Private Attributes Private attributes are user attributes that Statsig uses for evaluation but doesn't forward to any integrations. They are useful for PII or sensitive data that you don't want to send to third-party services. ```rust use statsig_rust::StatsigUserBuilder; use std::collections::HashMap; // Create a user with just a user ID let user = StatsigUserBuilder::new_with_user_id("user-123".to_string()) .build(); // Or create a user with custom IDs let mut custom_ids = HashMap::new(); custom_ids.insert("employee_id".to_string(), "emp-456".to_string()); let user_with_custom_ids = StatsigUserBuilder::new_with_custom_ids(custom_ids) .build(); // Create a user with several properties let mut custom_fields = HashMap::new(); custom_fields.insert("plan".to_string(), "premium".into()); custom_fields.insert("age".to_string(), 25.into()); let user = StatsigUserBuilder::new_with_user_id("user-123".to_string()) .email(Some("user@example.com".to_string())) .ip(Some("192.168.1.1".to_string())) .user_agent(Some("Mozilla/5.0...".to_string())) .country(Some("US".to_string())) .locale(Some("en-US".to_string())) .app_version(Some("1.0.0".to_string())) .custom(Some(custom_fields)) .build(); // Private Attributes (not forwarded to integrations) let mut private_attrs = HashMap::new(); private_attrs.insert("internal_id".to_string(), "emp-123".into()); let user_with_private = StatsigUserBuilder::new_with_user_id("user-123".to_string()) .email(Some("user@example.com".to_string())) .private_attributes(Some(private_attrs)) .build(); ``` ## Statsig Options You can pass an optional `options` parameter in addition to `sdkKey` during initialization to customize the Statsig client. {% accordion title="StatsigOptions" %} {% parameter name="data_store" type="Option<Arc<dyn DataStoreTrait>>" %} External data store for Statsig values. {% /parameter %} {% parameter name="disable_all_logging" type="Option<bool>" %} When true, disables all event logging. {% /parameter %} {% parameter name="disable_network" type="Option<bool>" %} When `true`, disables all network functions: event & exposure logging, spec downloads, and ID List downloads. Formerly called "localMode". {% /parameter %} {% parameter name="enable_id_lists" type="Option<bool>" %} Enable/disable ID list functionality. **Required to be `true` when using segments with more than 1000 IDs.** See [ID List segments](/segments/add-id-list) for more details. {% /parameter %} {% parameter name="disable_user_agent_parsing" type="Option<bool>" %} If set to true, the SDK doesn't attempt to parse UserAgents (attached to the user object) into browserName, browserVersion, systemName, systemVersion, and appVersion at evaluation time, when needed for evaluation. {% /parameter %} {% parameter name="wait_for_user_agent_init" type="Option<bool>" %} When set to true, the SDK waits until user agent parsing data is fully loaded during initialization. This may slow down startup by ~1 second but ensures that parsing of the user's userAgent string into fields like browserName, browserVersion, systemName, systemVersion, and appVersion is ready before any evaluations. {% /parameter %} {% parameter name="disable_user_country_lookup" type="Option<bool>" %} If set to true, the SDK doesn't attempt to parse IP addresses (attached to the user object at user.ip) into Country codes at evaluation time, when needed for evaluation. {% /parameter %} {% parameter name="wait_for_country_lookup_init" type="Option<bool>" %} When set to true, the SDK waits for country lookup data (e.g., GeoIP or YAML files) to fully load during initialization. This may slow down startup by ~1 second but ensures that IP-to-country parsing is ready at evaluation time. {% /parameter %} {% parameter name="environment" type="Option<String>" %} Environment parameter for evaluation. {% /parameter %} {% parameter name="event_logging_adapter" type="Option<Arc<dyn EventLoggingAdapter>>" %} Custom adapter for event logging. {% /parameter %} {% parameter name="event_logging_flush_interval_ms" type="Option<u32>" %} How often events are flushed to Statsig servers (in milliseconds). {% /parameter %} {% parameter name="event_logging_max_queue_size" type="Option<u32>" %} Maximum number of events to queue before forcing a flush. * Default is `2000` * event\_logging\_max\_queue\_size \* event\_logging\_max\_pending\_batch\_queue\_size is the upper limit on how many events are queued * See also `event_logging_max_pending_batch_queue_size` {% /parameter %} {% parameter name="event_logging_max_pending_batch_queue_size" type="Option<u32>" %} Maximum number of event batches to hold in buffer to retry. * Default is `100`. * event\_logging\_max\_queue\_size \* event\_logging\_max\_pending\_batch\_queue\_size is the upper limit on how many events are queued * eg: 2000 \* 100 means the SDK can process 200k event per second before events start getting dropped * See also `event_logging_max_queue_size`. {% /parameter %} {% parameter name="fallback_to_statsig_api" type="Option<bool>" %} Whether to fall back to the Statsig API if custom endpoints fail. {% /parameter %} {% parameter name="id_lists_adapter" type="Option<Arc<dyn IdListsAdapter>>" %} Custom adapter for ID lists. {% /parameter %} {% parameter name="id_lists_sync_interval_ms" type="Option<u32>" %} How often the SDK updates ID lists from Statsig servers (in milliseconds). {% /parameter %} {% parameter name="id_lists_url" type="Option<String>" %} Custom URL for fetching ID lists. {% /parameter %} {% parameter name="init_timeout_ms" type="Option<u64>" %} Sets the maximum timeout for initialization requests (in milliseconds). {% /parameter %} {% parameter name="log_event_url" type="Option<String>" %} Custom URL for logging events. {% /parameter %} {% parameter name="observability_client" type="Option<Weak<dyn ObservabilityClient>>" %} Client for collecting observability data. {% /parameter %} {% parameter name="output_log_level" type="Option<LogLevel>" %} Controls the verbosity of SDK logs. {% /parameter %} {% parameter name="override_adapter" type="Option<Arc<dyn OverrideAdapter>>" %} Custom adapter for overrides. {% /parameter %} {% parameter name="spec_adapters_config" type="Option<Vec<SpecAdapterConfig>>" %} Configuration for specification adapters. {% /parameter %} {% parameter name="specs_adapter" type="Option<Arc<dyn SpecsAdapter>>" %} Custom adapter for specifications. {% /parameter %} {% parameter name="specs_sync_interval_ms" type="Option<u32>" %} How often the SDK updates specifications from Statsig servers (in milliseconds). {% /parameter %} {% parameter name="specs_url" type="Option<String>" %} Custom URL for fetching feature specifications. {% /parameter %} {% parameter name="global_custom_fields" type="Option<HashMap<String, DynamicValue>>" %} Global custom fields to include with all evaluations. {% /parameter %} {% parameter name="proxy_config" type="Option<ProxyConfig>" %} Configuration for connecting through a proxy server. The `ProxyConfig` struct has the following properties: * `proxy_host`: Option\<String> - Specifies the proxy server host * `proxy_port`: Option\<u16> - Specifies the proxy server port * `proxy_auth`: Option\<String> - For proxy authentication (format: `"username:password"`) * `proxy_protocol`: Option\<String> - Specifies the protocol (e.g., `"http"`, `"https"`) * `ca_cert_path`: Option\<String> - Optional path to a PEM CA bundle for outbound TLS {% /parameter %} ### Example Usage ```rust use statsig_rust::{Statsig, StatsigOptions}; use std::sync::Arc; // Initialize StatsigOptions with custom parameters let mut options = StatsigOptions::default(); options.environment = Some("development".to_string()); options.init_timeout_ms = Some(3000); options.disable_all_logging = Some(false); options.enable_id_lists = Some(true); options.output_log_level = Some(LogLevel::Info); // LogLevel enum, not a string // Pass the options object into Statsig::new() let statsig = Statsig::new("server-secret-key", Some(Arc::new(options))); statsig.initialize().await?; // Or, use the builder pattern for a more fluent interface let options = StatsigOptions::builder() .environment(Some("development".to_string())) .init_timeout_ms(Some(3000)) .disable_all_logging(Some(false)) .enable_id_lists(Some(true)) .specs_sync_interval_ms(Some(30000)) // Configure proxy settings .proxy_config(Some(ProxyConfig { proxy_host: Some("proxy.example.com".to_string()), proxy_port: Some(8080), proxy_protocol: Some("https".to_string()), proxy_auth: None, // Use Some("username:password".to_string()) if authentication is required ca_cert_path: Some("/etc/ssl/certs/corporate-ca.pem".to_string()), })) .build(); // Pass the options object into Statsig::new() let statsig = Statsig::new("server-secret-key", Some(Arc::new(options))); statsig.initialize().await?; ``` {% /accordion %} ### Proxy and Custom Network Routing Use `proxy_config` if your service needs a standard outbound HTTP proxy. If you need to route config sync through [Statsig Forward Proxy](/infrastructure/api_proxy/introduction) or another custom source, use `spec_adapters_config` or `specs_url` instead. Set `ca_cert_path` when your environment requires a custom PEM CA bundle for outbound TLS. ## Shutting Statsig down Statsig batches and periodically flushes events. To ensure all logged events are flushed before shutdown, call `shutdown()` before your app or server shuts down: ```rust statsig.shutdown().await?; ``` Alternatively, you can manually flush events without shutting down: ```rust // Manually flush events to the server statsig.flush_events().await; ``` ## SDK Event Subscriptions The Statsig SDK provides an event subscription system that lets you listen for evaluation events and lifecycle events in real time. This is useful for debugging, analytics, custom logging, and integration with external systems. ### Supported Events The SDK supports subscribing to the following evaluation events: * **`gate_evaluated`** - Fired when a feature gate is evaluated for a user * **`dynamic_config_evaluated`** - Fired when a dynamic config is retrieved for a user * **`experiment_evaluated`** - Fired when an experiment is evaluated for a user * **`layer_evaluated`** - Fired when a layer is evaluated for a user * **`specs_updated`** - Fired when the SDK updates its cached specs, including where the specs were loaded from * **`"*"`** - Subscribe to all evaluation events ### SDK Event Data Each event includes relevant context about the evaluation: * **Gate Evaluated Events** include: `gate_name`, `value` (boolean), `rule_id`, `reason` * **Dynamic Config Events** include: the full `dynamic_config` object with values and metadata * **Experiment Events** include: the full `experiment` object with variant assignment and parameters * **Layer Events** include: the full `layer` object with allocated experiment and parameters * **Specs Updated Events** include `source`, `source_api`, and `values` metadata, where `values.time` is the timestamp of the last update to the project in the Statsig console ### Use Cases Event subscriptions are particularly useful for: * **Debugging**: Monitor which features are being evaluated and their results * **Analytics**: Track feature usage patterns and user segments * **Custom Logging**: Send evaluation data to your own logging systems * **Integration**: Forward events to external analytics or monitoring tools * **Testing**: Verify that features are being evaluated as expected ### Best Practices * **Clean up subscriptions**: Always unsubscribe when you no longer need to listen for events to prevent memory leaks * **Handle event data carefully**: Event objects may contain sensitive user information depending on your configuration * **Use specific event types**: Subscribe to specific events rather than "\*" when possible for better performance * **Avoid heavy processing**: Keep event handlers lightweight to avoid impacting SDK performance ```rust use statsig_rust::{Statsig, StatsigUserBuilder, sdk_event_emitter::SdkEvent}; let statsig = Statsig::new("server-secret-key", None)?; statsig.initialize().await?; // Subscribe to gate evaluation events let gate_sub_id = statsig.subscribe(SdkEvent::GATE_EVALUATED, |event| { if let SdkEvent::GateEvaluated { gate_name, value, rule_id, reason } = event { println!("Gate evaluated: {} = {}, rule: {}, reason: {}", gate_name, value, rule_id, reason); } }); // Subscribe to dynamic config evaluation events let config_sub_id = statsig.subscribe(SdkEvent::DYNAMIC_CONFIG_EVALUATED, |event| { if let SdkEvent::DynamicConfigEvaluated { dynamic_config } = event { println!("Config evaluated: {}", dynamic_config.name); } }); // Subscribe to experiment evaluation events let experiment_sub_id = statsig.subscribe(SdkEvent::EXPERIMENT_EVALUATED, |event| { if let SdkEvent::ExperimentEvaluated { experiment } = event { println!("Experiment evaluated: {} -> {}", experiment.name, experiment.group_name); } }); // Subscribe to layer evaluation events let layer_sub_id = statsig.subscribe(SdkEvent::LAYER_EVALUATED, |event| { if let SdkEvent::LayerEvaluated { layer } = event { println!("Layer evaluated: {}", layer.name); } }); let specs_updated_sub_id = statsig.subscribe(SdkEvent::SPECS_UPDATED, |event| { if let SdkEvent::SpecsUpdated { source, source_api, values, } = event { println!( "Specs updated from {} via {} (project last edited at {})", source, source_api, values.time ); } }); // Subscribe to all events let all_events_sub_id = statsig.subscribe(SdkEvent::ALL, |event| { println!("Event received: {}", event.get_name()); }); // Unsubscribe from specific event types statsig.unsubscribe(SdkEvent::GATE_EVALUATED); statsig.unsubscribe(SdkEvent::SPECS_UPDATED); // Unsubscribe using subscription ID statsig.unsubscribe_by_id(&config_sub_id); // Unsubscribe from all events statsig.unsubscribe_all(); ``` ## Local overrides Local Overrides let you override the values of gates, configs, experiments, and layers for testing. This is useful for local development or testing when you want to force a specific value without changing the configuration in the Statsig console. ```Rust // Overrides the given gate to the specified value statsig.override_gate("test_gate", true, None); // Overrides the given dynamic config to the provided value statsig.override_dynamic_config("test_1", my_map.clone(), None); //my_map is HashMap<String, Value> // Overrides the given experiment to the provided value statsig.override_experiment("test_xp_1", my_map.clone(), None); //my_map is HashMap<String, Value> // Overrides the given experiment to a particular groupname, available for experiments only statsig.override_experiment_by_group_name("test_xp_1", "a_group_name", None); // Overrides the given layer to the provided value statsig.override_layer("user_promo_experiments", my_map.clone(), None); //my_map is HashMap<String, Value> //Alternatively, get the Experiment object for a given groupName let group_exp = statsig.get_experiment_by_group_name("pricing_experiment", "premium_group"); let premium_price = group_exp.get_double("price", 9.99); ``` ## Persistent storage The Persistent Storage interface lets you implement custom storage for user-specific configurations. This enables you to persist user assignments across sessions, ensuring consistent experiment groups when a user returns. This is useful for client-side A/B testing where users must always receive the same variant. ```rust pub trait PersistentStorageTrait: Send + Sync { fn load(&self, key: &str) -> Result<Option<UserPersistedValues>, PersistentStorageErrorEnum>; fn save(&self, key: &str, config_name: &str, data: &StickyValues) -> Result<(), PersistentStorageErrorEnum>; fn delete(&self, key: &str, config_name: &str) -> Result<(), PersistentStorageErrorEnum>; } ``` ## Data store The Data Store interface lets you implement custom storage for Statsig configurations, enabling advanced caching strategies and integration with your preferred storage systems. ```rust pub trait DataStoreTrait: Send + Sync { fn initialize(&self) -> Result<(), DataStoreErrorEnum>; fn shutdown(&self) -> Result<(), DataStoreErrorEnum>; fn get(&self, key: &str) -> Result<Option<DataStoreResponse>, DataStoreErrorEnum>; fn set(&self, key: &str, value: &str, time: Option<u64>) -> Result<(), DataStoreErrorEnum>; fn support_polling_updates_for(&self, key: &str) -> Result<bool, DataStoreErrorEnum>; } pub struct DataStoreResponse { pub result: String, pub time: Option<u64>, } ``` ## Custom output logger The Output Logger interface lets you customize how the SDK logs messages, enabling integration with your own logging system and control over log verbosity. ```rust pub trait OutputLogProvider: Send + Sync { fn initialize(&self); fn debug(&self, tag: &str, msg: String); fn info(&self, tag: &str, msg: String); fn warn(&self, tag: &str, msg: String); fn error(&self, tag: &str, msg: String); fn shutdown(&self); } ``` ## Observability client The Observability Client interface lets you monitor the health of the SDK by integrating with your own observability systems. This enables you to track metrics, errors, and performance data. For more information on the metrics emitted by Statsig SDKs, refer to the [Monitoring documentation](/infrastructure/sdk-monitoring). ```rust pub trait ObservabilityClient: Send + Sync { fn init(&self); fn increment(&self, metric_name: &str, value: f64, tags: &HashMap<String, String>); fn gauge(&self, metric_name: &str, value: f64, tags: &HashMap<String, String>); fn dist(&self, metric_name: &str, value: f64, tags: &HashMap<String, String>); fn error(&self, tag: &str, error: &str); } ``` ## Fields Needed Methods (Enterprise Only) {% callout type="info" %} This is available for Enterprise contracts. Reach out to our support team, your sales contact, or through our [Slack community](https://statsig.com/slack) if you want this enabled. {% /callout %} These methods return an array of strings representing the user fields referenced in the targeting rules or conditions of the specified feature. Use them to understand which user properties influence a particular feature's behavior. ```rust // Get fields needed for a gate let fields_needed: Vec<String> = statsig.get_fields_needed_for_gate("gate_name"); // Get fields needed for a dynamic config let fields_needed: Vec<String> = statsig.get_fields_needed_for_dynamic_config("config_name"); // Get fields needed for an experiment let fields_needed: Vec<String> = statsig.get_fields_needed_for_experiment("experiment_name"); // Get fields needed for a layer let fields_needed: Vec<String> = statsig.get_fields_needed_for_layer("layer_name"); ``` ### Field Mapping The fields returned by these methods correspond to the following user properties: ```rust // Field mapping between user properties and internal field names let field_mapping = std::collections::HashMap::from([ ("userID", "u"), ("email", "e"), ("ip", "i"), ("userAgent", "ua"), ("country", "c"), ("locale", "l"), ("appVersion", "a"), ("time", "t"), ("stableID", "s"), ("environment", "en"), ("targetApp", "ta"), ]); // Custom fields are prefixed with "cf:" // Example: fields.add("cf:" + field_name); ``` ## FAQ {% accordion-group %} {% accordion title="How do I run experiments for logged out users?" %} Refer to the guide on [device level experiments](/guides/first-device-level-experiment) {% /accordion %} {% /accordion-group %} ## Reference ### Fields Needed Methods (Enterprise Only) ```rust // Get user fields needed for a gate evaluation pub fn get_fields_needed_for_gate(&self, gate_name: &str) -> Vec<String> // Get user fields needed for a dynamic config evaluation pub fn get_fields_needed_for_dynamic_config(&self, config_name: &str) -> Vec<String> // Get user fields needed for an experiment evaluation pub fn get_fields_needed_for_experiment(&self, experiment_name: &str) -> Vec<String> // Get user fields needed for a layer evaluation pub fn get_fields_needed_for_layer(&self, layer_name: &str) -> Vec<String> ``` {% callout type="info" %} This is available for Enterprise contracts. Reach out to our support team, your sales contact, or through our [Slack community](https://statsig.com/slack) if you want this enabled. {% /callout %} #### Field Mapping The fields returned by these methods correspond to the following user properties: ``` // Field mapping between user properties and internal field names const fieldMapping = { userID: 'u', email: 'e', ip: 'i', userAgent: 'ua', country: 'c', locale: 'l', appVersion: 'a', time: 't', stableID: 's', environment: 'en', targetApp: 'ta', }; // Custom fields are prefixed with "cf:" // Example: fields.add('cf:' + field); ``` ================================================================================ # Elixir Server SDK URL: https://docs.statsig.com/server-core/elixir-core Updated: 2026-07-11 ================================================================================ ## Setup the SDK {% steps %} {% step title="Install the SDK" %} To use the SDK, add the following to your `mix.exs`: ```elixir {:statsig_elixir, "~> 0.8.0"}, ``` The SDK is written using `rustler_precompiled`, which downloads a precompiled binary by default. You can also build the Rust code within your project by cloning [statsig-server-core](https://github.com/statsig-io/statsig-server-core) ```bash cd statsig-server-core/statsig-elixir/ # set the environment variable FORCE_STATSIG_NATIVE_BUILD="true" mix compile ``` {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Server Secret Keys should always be kept private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} An optional `options` parameter accepts a `StatsigOptions` object to customize the SDK. `Statsig.ex` uses GenServer to manage the Statsig instance (which is written in Rust). Add Statsig to your Supervision Tree: ```elixir # Initializing, with StatsigOptions sdk_key = "secret-key******" # your secret key statsig_options = %StatsigOptions{enable_id_lists: true} # Add to your supervision tree statsig_spec = %{id: Statsig, start: {Statsig, :start_link, [sdk_key, statsig_options]}} children = [ # Other Apps statsig_spec ] res = Supervisor.start_link(children, opts) # Or directly initialize the GenServer {:ok,_} = Statsig.start_link(sdk_key, statsig_options) Statsig.initialize() ``` `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independent of your API calls. {% /step %} {% /steps %} ## Working with the SDK ### Checking a Feature Flag/Gate After the SDK is initialized, you can fetch a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are **CLOSED** or **OFF** (equivalent to `return false;`) by default. All APIs require a user object (refer to [Statsig user](#statsig-user)). To check a gate for a user: ```elixir user = %StatsigUser{ user_id: "test_user_123" } {:ok, check_gate} = Statsig.check_gate("test_public", user) # check_gate will be a boolean ``` ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional user targeting. To send different values (strings, numbers, etc.) to clients based on user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The API is similar to Feature Gates, but returns a JSON object from which you can retrieve typed parameters. For example: ```elixir # Get a dynamic config for a specific user user = %StatsigUser{ user_id: "test_user_123" } {:ok, config} = Statsig.get_dynamic_config("a_config", user) # Access the values in the dynamic config: param_value = DynamicConfig.get_param_value(config, "header_text") # Disable exposure options = %Statsig.DynamicConfigEvaluationOptions{ disable_exposure_logging = true } {:ok, config} = Statsig.get_dynamic_config("a_config", user, options) ``` ### Getting a Layer/Experiment **Layers/Experiments** let you run A/B/n experiments. Two APIs are available, but [layers](/experiments/layers-overview) are recommended because layers make parameters reusable and support mutually exclusive experiments. ```elixir # Values via get_layer user = %StatsigUser{ user_id: "test_user_123" } {:ok, layer} = Statsig.get_layer("user_promo_experiments", user) {:ok, title_string_value} = Layer.get(layer, "title", "Welcome to Statsig!") {:ok, discount_float_value} = Layer.get(layer, "discount", 0.1) # Via get_experiment {:ok, experiment} = Statsig.get_experiment("user_promo_experiment", user) title_exp = Experiment.get_param_value(experiment, "new_user_promo_title") # Disable exposure options = %Statsig.ExperimentEvaluationOptions{ disable_exposure_logging = true } {:ok, experiment} = Statsig.get_experiment("user_promo_experiment", user, options) ``` {% callout type="note" %} If you are using layer to get value -- get param value. It will return primitive types: boolean, string, and numbers, for more complex type, SDK will return json serialized values. {% /callout %} ### Retrieving Feature Gate Metadata To retrieve more information about a gate evaluation than a boolean value, use the Get Feature Gate API, which returns a `FeatureGate` object: ```elixir {:ok, feature_gate} = Statsig.get_feature_gate(user, "example_gate") # access the value, or the name off of the feature_gate object options = %Statsig.FeatureGateEvaluationOptions{ disable_exposure_logging = true } {:ok, feature_gate} = Statsig.get_feature_gate(user, "example_gate", options) ``` ### Parameter Stores If you want dynamic control over whether a value comes from a Feature Gate, Experiment, or Dynamic Config outside your deployment cycle, use Parameter Stores. A Parameter Store lets you define a parameter that you can change at any point in the Statsig console. Parameter Stores are optional, but parameterizing your application supports future flexibility and allows non-technical Statsig users to turn parameters into experiments. {% callout type="note" %} *Parameter stores are not yet available for this sdk. Need it now? Let us know in Slack.* {% /callout %} ### Logging an Event After setting up a Feature Gate or Experiment, track custom events to measure how features or experiment groups affect user behavior. Call the Log Event API and specify the user and event name. You can also provide a value and metadata: ```elixir Statsig.log_event(user, "test_event", 1, %{"metadata_1" => "value"}) ``` ### Sending events to Log Explorer You can forward logs to Logs Explorer for analysis using the Forward Log Line Event API. This lets you include custom metadata and event values with each log. {% callout type="note" %} Sending events to Log Explorer is not yet available for this sdk. Need it now? Let us know in Slack. {% /callout %} ## Statsig User The `StatsigUser` object represents a user in Statsig. You must provide a `userID` or at least one of the `customIDs` to identify the user. When calling APIs that require a user, pass as much information as possible to enable advanced gate and config conditions (such as country or OS/browser checks) and to measure experiment impact accurately. As explained in [why an ID is always required for server SDKs](/sdks/user#why-is-an-id-always-required-for-server-sdks), at least one identifier (userID or customID) is required to provide a consistent experience for a given user. In addition to `userID`, the top-level fields on `StatsigUser` are: `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion`. You can also pass key-value pairs in the `custom` field for targeting. ### Private attributes Private attributes are user attributes used for evaluation but not forwarded to any integrations. Use them for PII or sensitive data that you don't want to send to third-party services. ```elixir user = %Statsig.User{ user_id: "a-user-id", email: "user@example.com", ip: "192.168.1.1", user_agent: "Mozilla/5.0...", country: "US", locale: "en_US", app_version: "1.0.0", custom: %{ # Custom fields "plan" => "premium", "age" => 25 }, custom_ids: %{ # Custom ID types "stable_id" => "stable-id-123" }, private_attributes: %{ # Private attributes not forwarded to integrations "email" => "private@example.com" } } ``` ## Statsig Options You can pass an optional `options` parameter in addition to `sdkKey` during initialization to customize the Statsig client. {% accordion title="StatsigOptions" %} {% parameter name="environment" type="string" %} The environment you're operating in (e.g., Production) {% /parameter %} {% parameter name="output_log_level" type="string" %} The type of logs you'd like exposed. (e.g., Debug) {% /parameter %} {% parameter name="init_timeout_ms" type="number" %} How long it should take for initialization to time out. {% /parameter %} {% parameter name="event_logging_flush_interval_ms" type="number" %} How often events should flush to the Statsig servers. {% /parameter %} {% parameter name="event_logging_max_queue_size" type="Option<u32>" %} Maximum number of events to queue before forcing a flush. * Default is `2000` * event\_logging\_max\_queue\_size \* event\_logging\_max\_pending\_batch\_queue\_size is the upper limit on how many events are queued * See also `event_logging_max_pending_batch_queue_size` {% /parameter %} {% parameter name="event_logging_max_pending_batch_queue_size" type="Option<u32>" %} Maximum number of event batches to hold in buffer to retry. * Default is `100`. * event\_logging\_max\_queue\_size \* event\_logging\_max\_pending\_batch\_queue\_size is the upper limit on how many events are queued * eg: 2000 \* 100 means the SDK can process 200k event per second before events start getting dropped * See also `event_logging_max_queue_size`. {% /parameter %} {% parameter name="log_event_url" type="string" %} The URL events should be logged to {% /parameter %} {% parameter name="specs_sync_interval_ms" type="number" %} How often specs should sync from the Statsig servers {% /parameter %} {% parameter name="specs_url" type="string" %} The URL Statsig should download specs from {% /parameter %} {% parameter name="spec_adapter_configs" type="list" %} Advanced configuration for fetching specs from custom sources, including [Statsig Forward Proxy](/infrastructure/api_proxy/introduction). {% /parameter %} {% parameter name="enable_id_lists" type="boolean" %} Enable ID list download. **Required to be `true` when using segments with more than 1000 IDs.** See [ID List segments](/segments/add-id-list) for more details. {% /parameter %} {% parameter name="id_lists_url" type="string" %} The URL ID lists should be downloaded from {% /parameter %} {% parameter name="id_lists_sync_interval_ms" type="number" %} How often ID lists should be synced. {% /parameter %} {% parameter name="wait_for_country_lookup_init" type="boolean" %} Block initialization call until country lookup is initialized {% /parameter %} {% parameter name="wait_for_user_agent_init" type="boolean" %} Block initialization call until user\_agent is initialized {% /parameter %} {% parameter name="disable_all_logging" type="boolean" %} When `true`, disables all event logging. {% /parameter %} {% parameter name="disable_country_lookup" type="boolean" %} To improve memory usage, disable using country lookup. {% /parameter %} {% parameter name="disable_network" type="boolean" %} Turn off all network requests including get\_dcs and log\_events {% /parameter %} {% parameter name="disable_user_agent_parsing" type="boolean" %} To improve memory usage, disable using user agent parsing. {% /parameter %} *** ```elixir # Initialize StatsigOptions with custom parameters statsig_options = %StatsigOptions{ enable_id_lists: true } # Pass the options object into statsig.initialize() {:ok, _} = Statsig.start_link(sdk_key, statsig_options) Statsig.initialize() ``` {% /accordion %} ### Proxy and Custom Network Routing The Elixir Server Core SDK doesn't currently document a dedicated outbound HTTP proxy config. For custom network routing, use `spec_adapter_configs` for [Statsig Forward Proxy](/infrastructure/api_proxy/introduction) or set `specs_url`, `log_event_url`, and `id_lists_url` directly. ```elixir statsig_options = %StatsigOptions{ spec_adapter_configs: [ %Statsig.SpecAdapterConfig{ adapter_type: "network_grpc_websocket", specs_url: "http://forward-proxy.internal:50051", authentication_mode: "none" } ], fallback_to_statsig_api: true } ``` ## Shutting Statsig Down Because events are batched and periodically flushed, some events may not have been sent when your app or server shuts down. To ensure all logged events are flushed, call `shutdown()` before your app or server shuts down: ```elixir Statsig.shutdown() ``` ## Client SDK Bootstrapping | SSR If you are using the Statsig client SDK in a browser or mobile app, you can bootstrap the client SDK with values from the server SDK to avoid a network request on the client. This is useful for server-side rendering (SSR) or when you want to reduce the number of network requests on the client. ```elixir # Get client initialize response for a user {:ok, response} = Statsig.get_client_init_response_as_string(user) # Pass values to a client SDK to initialize without a network request ``` ## Persistent storage The Persistent Storage interface lets you implement custom storage for user-specific configurations. Use it to persist user assignments across sessions, ensuring consistent experiment groups when users return. This is useful for client-side A/B testing where users must always see the same variant. {% callout type="note" %} Not supported at this time. {% /callout %} ## Data store The Data Store interface lets you implement custom storage for Statsig configurations, enabling advanced caching strategies and integration with your preferred storage systems. {% callout type="note" %} Not supported at this time. {% /callout %} ## Custom output logger The Output Logger interface lets you customize how the SDK logs messages, enabling integration with your own logging system and control over log verbosity. {% callout type="note" %} Not supported at this time. {% /callout %} ## Observability client The Observability Client interface lets you monitor SDK health by integrating with your own observability systems, enabling tracking of metrics, errors, and performance data. For more information on the metrics emitted by Statsig SDKs, go to the [Monitoring documentation](/infrastructure/sdk-monitoring). {% callout type="note" %} Not supported at this time. {% /callout %} ## FAQ {% accordion-group %} {% accordion title="How do I run experiments for logged out users?" %} Refer to the guide on [device level experiments](/guides/logging-events#device-level-events) {% /accordion %} {% /accordion-group %} ================================================================================ # PHP Server SDK URL: https://docs.statsig.com/server-core/php-core Updated: 2026-07-11 ================================================================================ {% callout type="tip" %} Migrating from the legacy PHP SDK? Refer to the [Migration Guide](/server-core/migration-guides/php) . {% /callout %} ## Set up the SDK {% steps %} {% step title="Install the SDK" %} ## Installation ### 1. Install and Add as a Dependency Install the PHP Core SDK using Composer: ```shell composer require statsig/statsig-php-core ``` ### 2. PHP Configuration Requirements The PHP Core SDK uses the FFI extension to interface with the Rust core. Enable FFI in your PHP configuration by setting `ffi.enable=true` in your php.ini file. For more information about this setting, go to the [PHP manual for ffi.enable](https://www.php.net/manual/en/ffi.configuration.php#ini.ffi.enable). ### 3. Add Scripts & Cron Job Add post-install and post-update scripts in composer.json: ```json // composer.json { "name": "awesome-php-project", ... "scripts": { ... "post-install-cmd": [ "cd vendor/statsig/statsig-php-core && php post-install.php" ], "post-update-cmd": [ "cd vendor/statsig/statsig-php-core && php post-install.php" ] } } ``` Add a script to sync your Statsig configs and flush your events. For example files, go to [Statsig's GitHub](https://github.com/daniel-statsig/statsig-php-core-slim-example/tree/main/bin). Set up cron jobs to run these scripts periodically: ```shell */10 * * * * /usr/bin/php /var/www/example.com/bin/StatsigSyncConfig.php 1>> /dev/null 2>&1 */1 * * * * /usr/bin/php /var/www/example.com/bin/StatsigFlushEvents.php 1>> /dev/null 2>&1 ``` Run the StatsigSyncConfig.php cron job at least once before proceeding. {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Keep Server Secret Keys private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} An optional `options` parameter accepts a StatsigOptions object to customize the SDK. Add your client secret key to the environment using a .env file or on the command line: ```shell export STATSIG_SECRET_KEY=secret-123456789 ``` ```php // At the top of your file use Statsig\Statsig; use Statsig\StatsigOptions; use Statsig\StatsigLocalFileEventLoggingAdapter; use Statsig\StatsigLocalFileSpecsAdapter; //In the case of slim framework, in container builder definitions: Statsig::class => function (ContainerInterface $c) { $sdk_key = getenv("STATSIG_SECRET_KEY"); $options = new StatsigOptions( null, null, new StatsigLocalFileSpecsAdapter($sdk_key, "/tmp"), new StatsigLocalFileEventLoggingAdapter($sdk_key, "/tmp") ); $statsig = new Statsig($sdk_key, $options); $statsig->initialize(); return $statsig; }, ``` {% callout type="warning" %} `StatsigLocalFile` Adapters rely on cron jobs and files. If you see errors around file access, ensure your cron job has run at least one time before using Statsig. Go to [Add Scripts & Cron Job](#2-add-scripts--cron-job) {% /callout %} `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ### Checking a Feature Flag/Gate After the SDK is initialized, you can fetch a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (equivalent to `return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example: ```php use Statsig\Statsig; use Statsig\StatsigUserBuilder; $user = StatsigUserBuilder::withUserID('my_user')->build(); $passed = $statsig->checkGate($user, 'my_gate'); ``` ### Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and similar types) to clients based on user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The API is similar to Feature Gates, but returns a full JSON object configurable on the server from which you can fetch typed parameters. ```php $user = StatsigUserBuilder::withUserID('my_user')->build(); $config = $statsig->getDynamicConfig($user, 'my_config'); ``` ### Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Statsig provides two APIs and recommends [Layers](/experiments/layers-overview) because they make parameters reusable and support mutually exclusive experiments. ```php $user = StatsigUserBuilder::withUserID('my_user')->build(); $xp = $statsig->getExperiment($user, 'an_experiment'); ``` ### Retrieving Feature Gate metadata In certain scenarios, you may need more information about a gate evaluation than just a boolean value. For additional metadata about the evaluation, use the Get Feature Gate API, which returns a FeatureGate object: ```php $gate = $statsig->getFeatureGate($user, "example_gate"); ``` ### Parameter Stores Use Parameter Stores when you want to define a parameter without deciding whether it should be a Feature Gate, Experiment, or Dynamic Config. Parameter Stores let you change the parameter type at any point in the Statsig console without a new deployment. Parameter Stores are optional, but parameterizing your application provides future flexibility and allows non-technical Statsig users to turn parameters into experiments. *Parameter stores aren't available for this SDK yet. Need it now? Reach out in [Slack](https://statsig.com/slack).* ### Logging an Event To track custom events, call the Log Event API. Specify the user, event name, and an optional value or metadata object: ```php $user = StatsigUserBuilder::withUserID('my_user')->build(); $statsig->logEvent($user, 'an_experiment'); ``` ### Sending events to Log Explorer You can forward logs to Logs Explorer for convenient analysis using the Forward Log Line Event API. This lets you include custom metadata and event values with each log. Log Explorer event forwarding isn't available for this SDK yet. Need it now? Reach out in [Slack](https://statsig.com/slack). ## Using shared instance To create a single Statsig instance accessible globally throughout your codebase, use the shared instance functionality, which provides a singleton pattern: ```php use Statsig\Statsig; // Initialize the shared instance Statsig::initializeShared('your-server-secret-key', $options); // Access the shared instance from anywhere in your code $user = StatsigUserBuilder::withUserID('my_user')->build(); $gate = Statsig::shared()->checkGate($user, 'my_gate'); // Shutdown the shared instance when your application closes Statsig::shared()->shutdown(); ``` ## Statsig User The `StatsigUser` object represents a user in Statsig. You must provide a `userID` or at least one of the `customIDs` to identify the user. When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks) and to correctly measure the impact of experiments on metrics and events. As explained [here](/sdks/user#why-is-an-id-always-required-for-server-sdks), at least one identifier (userID or customID) is required to provide a consistent experience for a given user. In addition to userID, the top-level fields on StatsigUser are: email, ip, userAgent, country, locale, and appVersion. You can also pass any key-value pairs in an object or dictionary to the custom field and create targeting based on them. ### Private Attributes Private attributes are user attributes that Statsig uses for evaluation but doesn't forward to any integrations. They are useful for PII or sensitive data that you don't want to send to third-party services. ```php use Statsig\StatsigUser; $user = new StatsigUser([ 'userID' => 'a-user-id', 'email' => 'user@example.com', 'ip' => '192.168.1.1', 'userAgent' => 'Mozilla/5.0...', 'country' => 'US', 'locale' => 'en_US', 'appVersion' => '1.0.0', 'custom' => [ // Custom fields 'plan' => 'premium', 'age' => 25 ], 'customIDs' => [ // Custom ID types 'stableID' => 'stable-id-123' ], 'privateAttributes' => [ // Private attributes not forwarded to integrations 'email' => 'private@example.com' ] ]); ``` Set the `privateAttributes` parameter in the `StatsigUser` constructor to define which attributes are private and shouldn't be forwarded to any third-party integrations. The `privateAttributes` parameter is a key-value dictionary where keys are attribute names and values are the private values. In the example user object above, the key `"email"` appears both in the top-level `email` field and in `privateAttributes`. These are distinct: you can have a non-private value in the top-level `email` field and a private value in `private_attributes`, or vice versa. ```php $user = new StatsigUser([ 'userID' => 'a-user-id', 'email' => 'non_private@example.com', 'privateAttributes' => [ 'email' => 'private@example.com' ] ]); ``` ## Statsig Options You can pass an optional `options` parameter in addition to `sdkKey` during initialization to customize the Statsig client. {% accordion title="StatsigOptions" %} {% parameter name="specs_url" type="string" %} Custom URL for fetching feature specifications. {% /parameter %} {% parameter name="log_event_url" type="string" %} Custom URL for logging events. {% /parameter %} {% parameter name="specs_adapter" type="StatsigSpecsAdapter" %} An adapter with custom storage behavior for config specs. For example, use `StatsigLocalFileSpecsAdapter` to store configs in the local filesystem. {% /parameter %} {% parameter name="event_logging_adapter" type="StatsigEventLoggingAdapter" %} An adapter with custom event logging behavior. For example, use `StatsigLocalFileEventLoggingAdapter` to store events in the local filesystem. {% /parameter %} {% parameter name="environment" type="string" %} Environment parameter for evaluation. {% /parameter %} {% parameter name="event_logging_flush_interval_ms" type="int" %} How often events are flushed to Statsig servers (in milliseconds). {% /parameter %} {% parameter name="event_logging_max_queue_size" type="Option<u32>" %} Maximum number of events to queue before forcing a flush. * Default is `2000` * event\_logging\_max\_queue\_size \* event\_logging\_max\_pending\_batch\_queue\_size is the upper limit on how many events are queued * Refer to `event_logging_max_pending_batch_queue_size` {% /parameter %} {% parameter name="event_logging_max_pending_batch_queue_size" type="Option<u32>" %} Maximum number of event batches to hold in buffer to retry. * Default is `100`. * event\_logging\_max\_queue\_size \* event\_logging\_max\_pending\_batch\_queue\_size is the upper limit on how many events are queued * eg: 2000 \* 100 means the SDK can process 200k event per second before events start getting dropped * Refer to `event_logging_max_queue_size`. {% /parameter %} {% parameter name="specs_sync_interval_ms" type="int" %} How often the SDK updates specifications from Statsig servers (in milliseconds). {% /parameter %} {% parameter name="output_log_level" type="string" %} Controls the verbosity of SDK logs. {% /parameter %} {% parameter name="disable_country_lookup" type="bool" %} Disables country lookup based on IP address. Set to `true` to improve performance if you don't need country-based targeting. {% /parameter %} {% parameter name="disable_user_agent_parsing" type="bool" %} Disables user agent parsing. Set to `true` to improve performance if you don't need device or browser-based targeting. {% /parameter %} {% parameter name="init_timeout_ms" type="int" %} Maximum time in milliseconds to wait for SDK initialization to complete. If initialization takes longer than this timeout, the SDK continues to operate but may return default values until it completes. {% /parameter %} {% parameter name="fallback_to_statsig_api" type="bool" %} When set to true, the SDK falls back to the Statsig API directly if custom adapters (like local file adapters) fail to load configurations. {% /parameter %} {% parameter name="enable_id_lists" type="bool" %} Enable/disable ID list functionality. **Required to be `true` when using segments with more than 1000 IDs.** Refer to [ID List segments](/segments/add-id-list) for more details. {% /parameter %} {% parameter name="proxy_config" type="ProxyConfig" %} Configuration for connecting through a proxy server. {% /parameter %} {% accordion title="ProxyConfig" %} {% parameter name="proxyHost" type="string" %} The hostname or IP address of the proxy server (e.g., `"proxy.example.com"` or `"192.168.1.100"`). {% /parameter %} {% parameter name="proxyPort" type="int" %} The port number of the proxy server (e.g., `8080`, `3128`, `1080`). {% /parameter %} {% parameter name="proxyAuth" type="string" %} Authentication credentials for the proxy server in the format `"username:password"`. Required only if the proxy requires authentication. {% /parameter %} {% parameter name="proxyProtocol" type="string" %} The protocol to use for the proxy connection. Supported values: `"http"`, `"https"`, `"socks5"`. {% /parameter %} {% /accordion %} {% /accordion %} ### Proxy and custom network routing The PHP Server Core SDK supports a dedicated `proxy_config` for a standard outbound HTTP proxy. If you only need to route Statsig traffic to different endpoints, use `specs_url`, `log_event_url`, and `id_lists_url`. *** ### Performance recommendations If you experience performance issues, particularly with long initialization times, disable user agent parsing and country lookup to improve performance: * Set `disable_user_agent_parsing: true` if you don't need device or browser-based targeting. * Set `disable_country_lookup: true` if you don't need country-based targeting. These optimizations were added in response to performance issues identified in [PR #1119](https://github.com/statsig-io/private-statsig-server-core/pull/1119). *** ### Example usage ```php use Statsig\Statsig; use Statsig\StatsigOptions; use Statsig\ProxyConfig; use Statsig\StatsigLocalFileSpecsAdapter; use Statsig\StatsigLocalFileEventLoggingAdapter; // Create proxy configuration $proxyConfig = new ProxyConfig( proxyHost: "proxy.example.com", proxyPort: 8080, proxyAuth: "username:password", // Optional, only if the proxy requires authentication proxyProtocol: "http" ); // Initialize StatsigOptions with custom parameters $options = new StatsigOptions( specs_url: null, log_event_url: null, specs_adapter: new StatsigLocalFileSpecsAdapter($sdk_key, "/tmp"), event_logging_adapter: new StatsigLocalFileEventLoggingAdapter($sdk_key, "/tmp"), environment: "development", event_logging_flush_interval_ms: 60000, event_logging_max_queue_size: 1000, specs_sync_interval_ms: 600000, output_log_level: "INFO", disable_country_lookup: true, // For better performance wait_for_country_lookup_init: false, wait_for_user_agent_init: false, enable_id_lists: false, disable_network: false, id_lists_url: null, id_lists_sync_interval_ms: null, disable_all_logging: false, init_timeout_ms: 3000, fallback_to_statsig_api: false, use_third_party_ua_parser: null, persistent_storage: null, proxy_config: $proxyConfig // Add proxy configuration ); // Pass the options object into Statsig constructor $statsig = new Statsig($sdk_key, $options); $statsig->initialize(); ``` {% callout type="warning" %} When using `StatsigLocalFile` Adapters, ensure your cron job has run at least one time before using Statsig. Go to [Add Scripts & Cron Job](#2-add-scripts--cron-job) {% /callout %} ## Custom adapters ### SpecsAdapterBase - Custom configuration sources The `SpecsAdapterBase` lets you fetch Statsig configurations from custom sources instead of (or in addition to) Statsig's servers. This is useful when you want to: * Store configurations in your own database or cache (e.g., Redis, Memcached) * Implement custom caching strategies * Use Statsig in environments with restricted network access * Reduce latency by serving configs from a local source #### Implementation To create a custom specs adapter, extend the `SpecsAdapterBase` class and implement the required methods. ```php <?php use Statsig\SpecsAdapterBase; use Statsig\SpecsUpdateListener; class RedisSpecsAdapter extends SpecsAdapterBase { private $redis; private $listener; public function __construct($redis) { parent::__construct(); $this->redis = $redis; } public function setup(SpecsUpdateListener $listener): void { $this->listener = $listener; } public function start(): void { // Fetch initial specs when SDK starts $this->refreshSpecsFromRedis(); // Optionally, trigger a background job or set up a timer $this->refreshSpecsFromRedis(); } private function fetchSpecsFromRedis() { try { $specs = $this->redis->get('statsig_config_specs'); return $specs ?: null; } catch (Exception $e) { error_log("Failed to fetch specs from Redis: " . $e->getMessage()); return null; } } private function refreshSpecsFromRedis() { $specs = $this->fetchSpecsFromRedis(); if ($specs && $this->listener) { $timestamp = intval(microtime(true) * 1000); $this->listener->didReceiveSpecsUpdate($specs, "Redis", $timestamp); } } } ``` #### Usage ```php use Statsig\Statsig; use Statsig\StatsigOptions; $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $specsAdapter = new RedisSpecsAdapter($redis); $options = new StatsigOptions( specs_adapter: $specsAdapter ); $statsig = new Statsig('your-server-secret-key', $options); $statsig->initialize(); ``` #### Key methods * **`setup(SpecsUpdateListener $listener)`**: Called during initialization to provide the listener for spec updates * **`start()`**: Called when the SDK starts. Fetch and provide initial configuration specs * **`shutdown()`**: Called when the SDK shuts down. Clean up resources * **`scheduleBackgroundSync()`**: Called to schedule periodic updates of configuration specs The `SpecsUpdateListener` provides: * **`didReceiveSpecsUpdate(string $specs, string $source, int $timestamp)`**: Notify the SDK of new specs * **`getCurrentSpecsInfo()`**: Get information about current specs *** ### EventLoggingAdapterBase - Custom event destinations The `EventLoggingAdapterBase` lets you send events to custom destinations instead of or in addition to Statsig's servers. This is useful when you want to: * Send events to your existing analytics platform * Store events in a database for custom analysis * Forward events to multiple destinations * Implement custom batching or retry logic #### Implementation To create a custom event logging adapter, extend the `EventLoggingAdapterBase` class and implement the required methods. ```php <?php use Statsig\EventLoggingAdapterBase; use Statsig\LogEventRequest; class AnalyticsEventAdapter extends EventLoggingAdapterBase { private $analyticsClient; private $isStarted = false; public function __construct($analyticsClient) { parent::__construct(); $this->analyticsClient = $analyticsClient; } public function start(): void { $this->isStarted = true; // Initialize analytics client connection if needed $this->analyticsClient->connect(); } public function logEvents(LogEventRequest $request): bool { if (!$this->isStarted) { return false; } try { $events = $request->payload->events; $metadata = $request->payload->statsig_metadata; foreach ($events as $event) { // Transform Statsig event to analytics platform format $analyticsEvent = [ 'event_name' => $event['eventName'], 'user_id' => $event['user']['userID'] ?? null, 'timestamp' => $event['time'], 'properties' => array_merge( $event['metadata'] ?? [], ['statsig_metadata' => $metadata] ) ]; // Send to analytics platform $this->analyticsClient->track($analyticsEvent); } return true; } catch (Exception $e) { error_log("Failed to log events to analytics platform: " . $e->getMessage()); return false; } } public function shutdown(): void { $this->isStarted = false; // Clean up analytics client connection $this->analyticsClient->disconnect(); } } ``` #### Usage ```php use Statsig\Statsig; use Statsig\StatsigOptions; $analyticsClient = new MyAnalyticsClient('api-key'); $eventAdapter = new AnalyticsEventAdapter($analyticsClient); $options = new StatsigOptions( event_logging_adapter: $eventAdapter ); $statsig = new Statsig('your-server-secret-key', $options); $statsig->initialize(); // Events now go to your custom analytics platform $statsig->logEvent($user, 'button_clicked', ['button_id' => 'signup']); ``` #### Key methods * **`start()`**: Called when the SDK starts. Initialize connections or resources * **`logEvents(LogEventRequest $request): bool`**: Process and send events. Return true on success, false on failure * **`shutdown()`**: Called when the SDK shuts down. Clean up resources The `LogEventRequest` contains: * **`event_count`**: Number of events in the request * **`retries`**: Number of retry attempts for this request * **`payload`**: `LogEventPayload` with events and metadata The `LogEventPayload` contains: * **`events`**: Array of event objects with user data, event names, and metadata * **`statsig_metadata`**: SDK metadata including version and environment information ## Shutting Statsig down Statsig batches and periodically flushes events. To ensure all logged events are flushed before shutdown, call `shutdown()` before your app or server shuts down: ```php // Method signature public function shutdown(): void // example usage try { $statsig->shutdown(); echo "Statsig instance has been successfully shutdown.\n"; } catch (Exception $e) { error_log($e->getMessage()); } ``` Alternatively, if you operate in a serverless environment or cloud function and want to keep Statsig running in case the function is recycled, flush the logs to Statsig servers. To wait for logs to post before resolving, use: ```php // Method signature public function flushEvents(): void // example usage try { $statsig->flushEvents(); echo "All events have been successfully flushed.\n"; } catch (Exception $e) { echo "Failed to flush events: " . $e->getMessage() . "\n"; } ``` ## Local overrides Local Overrides let you override the values of gates, configs, experiments, and layers for testing. This is useful for local development or testing when you want to force a specific value without changing the configuration in the Statsig console. ```php $statsig->overrideGate("a_gate_name", true); $statsig->overrideDynamicConfig("a_config_name", [ "key" => "value", ]); $statsig->overrideExperiment("an_experiment_name", [ "key" => "value", ]); $statsig->overrideExperimentByGroupName("an_experiment_name", "a_group_name"); $statsig->overrideLayer("a_layer_name", [ "key" => "value", ]); ``` You can also pass a third argument to scope an override to a specific ID: ```php $statsig->overrideExperimentByGroupName("an_experiment_name", "a_group_name", "user_123"); ``` ## Persistent storage The Persistent Storage interface lets you implement custom storage for experiment assignments. This ensures consistent user experiences across sessions by persisting experiment assignments. For more information, go to the [Persistent Assignment](/server/concepts/persistent_assignment) documentation. ```php expandable MyPersistentStorage.php use Statsig\PersistentStorage; use Statsig\StickyValues; class MyPersistentStorage extends PersistentStorage { private array $storage = []; public function load(string $key): ?array { return $this->storage[$key] ?? null; } public function save(string $key, string $config_name, StickyValues $data): void { $this->storage[$key] ??= []; $this->storage[$key][$config_name] = $data->toArray(); } public function delete(string $key, string $config_name): void { unset($this->storage[$key][$config_name]); } } ``` ```php expandable PersistentStorageUsage.php $persistent_storage = new MyPersistentStorage(); $options = new StatsigOptions( persistent_storage: $persistent_storage ); $statsig = new Statsig("secret-key", $options); $statsig->initialize(); $persisted_user = new StatsigUser("test-123"); $values = $persistent_storage->getValuesForUser($persisted_user, "userID") ?? []; $experiment = $statsig->getExperiment( $persisted_user, "active_experiment", ["user_persisted_values" => $values], ); ``` ## Data store The Data Store interface lets you implement custom storage for Statsig configurations, enabling advanced caching strategies and integration with your preferred storage systems. {% callout type="note" %} Not supported at this time. {% /callout %} ## Custom output logger The Output Logger interface lets you customize how the SDK logs messages, enabling integration with your own logging system and control over log verbosity. {% callout type="note" %} Not supported at this time. {% /callout %} ## Observability client The Observability Client interface lets you monitor the health of the SDK by integrating with your own observability systems. This enables you to track metrics, errors, and performance data. For more information on the metrics Statsig SDKs emit, refer to the [Monitoring](/infrastructure/sdk-monitoring) documentation. {% callout type="note" %} Not supported at this time. {% /callout %} ## Notes on beta version The PHP SDK requires an adapter for both logging and saving config specs, given the stateless nature of PHP. [The example repository](https://github.com/daniel-statsig/statsig-php-core-slim-example) provides simple file-based adapters. More mature implementations may use a different, more performant caching approach. For help with setup, reach out on [Slack](https://statsig.com/slack). ## FAQ {% accordion-group %} {% accordion title="How do I run experiments for logged out users?" %} Refer to the guide on [device level experiments](/guides/first-device-level-experiment). {% /accordion %} {% /accordion-group %} ================================================================================ # Legacy Server SDKs URL: https://docs.statsig.com/server-core/legacy-sdks Updated: 2026-07-11 ================================================================================ Statsig is transitioning to the Statsig Server Core architecture, which unifies all Server SDKs around a performance-optimized core package. This model delivers improved performance and greater feature consistency. Most customers continue to use legacy SDKs, which remain supported except for major feature additions. ## Timelines End-of-life dates have been announced for some Legacy Server SDKs. [Server Core SDKs](/server-core) often evaluate 5-10x faster than legacy SDKs and are more feature complete. Go to the docs for existing SDKs below: * [Node](/server/nodejsServerSDK) * [Python](/server/pythonSDK) * [Elixir/Erlang](/server/erlang) (End of Life April 30th, 2026) * [Java](/server/java) * [Rust](/server/rust) (End of Life April 30th, 2026) * [Go](/server/go) * [PHP](/server/php) * [.NET](/server/dotnet) ================================================================================ # C++ Server SDK URL: https://docs.statsig.com/server/cpp Updated: 2026-07-11 ================================================================================ ## Set up the SDK {% steps %} {% step title="Install the SDK" %} If you are using CMake, add the following to a `.cmake` file: ```cmake FetchContent_Declare(statsig GIT_REPOSITORY https://github.com/statsig-io/cpp-server-sdk.git GIT_TAG v0.1.0 ) FetchContent_MakeAvailable(statsig) ``` And include the following in your `CMakeLists.txt` file: ```cmake cmake_minimum_required(VERSION 3.11) include(FetchContent) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/statsig.cmake) ``` Check out the latest versions on [https://github.com/statsig-io/cpp-server-sdk/releases/latest](https://github.com/statsig-io/cpp-server-sdk/releases/latest) {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Don't embed your Server Secret Key in client-side applications or expose it in any external-facing documents. If you accidentally expose it, you can create a new one in the Statsig console. {% /callout %} ```cpp #include <statsig.h> statsig::initialize('server-secret-key'); // Or, if you want to initialize with certain options statsig::Options options; options.localMode = true statsig::initialize('server-secret-key', options) ``` `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ## Checking a Feature Flag/Gate After the SDK is initialized, you can fetch a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (equivalent to `return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example: ```cpp statsig::User user; user.userID = "some_user_id" if (statsig::checkGate(user, 'use_new_feature')) { // Gate is on, enable new feature } else { // Gate is off } ``` ## Reading a Dynamic Config Feature Gates are useful for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and similar types) to clients based on user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The API is similar to Feature Gates, but returns a full JSON object configurable on the server from which you can fetch typed parameters. ```cpp statsig::DynamicConfig config = statsig::get_config(user, 'awesome_product_details') auto item_name = config.value['product_name']; auto price = config.value['price']; auto shouldDiscount = config.value['discount']; ``` ## Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available. Statsig recommends [Layers](/experiments/layers-overview) because they enable quicker iterations with parameter reuse. ```cpp // Values via getLayer statsig::Layer layer = statsig::getLayer(user, "user_promo_experiments") auto title = layer.get("title", "Welcome to Statsig!") auto discount = layer.get("discount") // or, via getExperiment statsig::DynamicConfig title_exp = statsig::getExperiment(user, "new_user_promo_title") statsig::DynamicConfig price_exp = statsig::getExperiment(user, "new_user_promo_price") title = title_exp.value["title"] discount = price_exp.value["discount"] ... price = msrp * (1 - discount) ``` ## Logging an Event To track custom events, call the Log Event API. Specify the user, event name, and an optional value or metadata object: ```cpp statsig::logEvent(user, 'add_to_cart') ``` Learn more about identifying users, group analytics, and best practices for logging events in the [logging events guide](/guides/logging-events). ## Statsig User When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks) and to correctly measure the impact of experiments on metrics and events. As explained [here](/sdks/user#why-is-an-id-always-required-for-server-sdks), at least one identifier (userID or customID) is required to provide a consistent experience for a given user. In addition to `userID`, the top-level fields on StatsigUser are: `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion`. You can also pass any key-value pairs in an object or dictionary to the `custom` field and create targeting based on them. {% callout type="note" %} While typing is lenient on the `StatsigUser` object, evaluation operators only work on primitive types (mostly strings and numbers). The SDK attempts to cast custom field types to match the operator, but evaluation results for non-primitive types are not guaranteed. For example, an array set as a custom field is compared only as a string. {% /callout %} ### Private Attributes The `StatsigUser` object also has a `privateAttributes` field, which is a dictionary for setting private user attributes. The SDK uses attributes in `privateAttributes` only for evaluation and targeting, and removes them from logs before sending them to the Statsig server. For example, if a feature gate should pass only for users with emails ending in "@statsig.com" but you don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes`. ## Statsig Options `initialize()` takes an optional `options` parameter in addition to the secret key to customize the Statsig client. Available options include: * **api** string, default `"https://statsigapi.net/v1"` * The base url to use for network requests from the SDK * **rulesetsSyncIntervalMs**: int, default `10000` * The interval to poll for changes to your gate and config definition changes * **loggingIntervalMs**: int, default `60000` * The default interval to flush logs to Statsig servers * **loggingMaxBufferSize**: int, default `1000`, can be set lower but anything over 1000 will be dropped on the server * The maximum number of events to batch before flushing logs to the server * **localMode**: bool, default `false` * Restricts the SDK to not issue any network requests and only respond with default values (or local overrides) {% callout type="info" %} ID Lists are not supported in the C++ server SDK {% /callout %} ## Shutdown To gracefully shut down the SDK and ensure all events are flushed: ```cpp statsig::shutdown() ``` ## Local overrides You can override the values the SDK returns for testing. This is useful for local development when you want to test specific scenarios. ```cpp // Adding gate overrides statsig::overrideGate("a_gate_name", true) // Adding config overrides std::unordered_map<std::string, JSON::any> overrideValue = { {"overridden key", "overridden field"}, }; statsig::overrideConfig("a_config_name", overrideValue) ``` ## FAQ #### How do I run experiments for logged out users? Refer to the guide on [device level experiments](/guides/first-device-level-experiment). ## Reference ### User ```cpp struct User { std::string userID; std::string email; std::string ipAddress; std::string userAgent; std::string country; std::string locale; std::string appVersion; std::unordered_map<std::string, JSON::any> custom; std::unordered_map<std::string, JSON::any> privateAttribute; std::unordered_map<std::string, std::string> statsigEnvironment; std::unordered_map<std::string, std::string> customIDs; }; inline bool operator==(User const &a, User const &b) { return a.userID == b.userID && a.email == b.email && a.ipAddress == b.ipAddress && a.userAgent == b.userAgent && a.country == b.country && a.locale == b.locale && a.appVersion == b.appVersion && a.custom == b.custom && a.privateAttribute == b.privateAttribute && a.statsigEnvironment == b.statsigEnvironment && a.customIDs == b.customIDs; }; ``` ### Options ```cpp struct Options { std::string api; bool localMode; int rulesetsSyncIntervalMs; int loggingIntervalMs; int loggingMaxBufferSize; Options() : api("https://statsigapi.net"), localMode(false), rulesetsSyncIntervalMs(10 * 1000), loggingIntervalMs(60 * 1000), loggingMaxBufferSize(1000){}; }; ``` ================================================================================ # Legacy Go Server SDK URL: https://docs.statsig.com/server/go Updated: 2026-07-11 ================================================================================ {% callout type="warning" %} This page covers the legacy Go SDK. For new implementations, use the [Go Core SDK](/server-core/go-core) built on the Server Core framework. {% /callout %} ## Setup the SDK {% steps %} {% step title="Install the SDK" %} Using the `go get` CLI: ```bash go get github.com/statsig-io/go-sdk ``` Or, add a dependency on the most recent version of the SDK in go.mod: ```go require ( github.com/statsig-io/go-sdk v1.26.0 ) ``` Refer to the [Releases tab in GitHub](https://github.com/statsig-io/go-sdk/releases) for the latest versions. {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Do NOT embed your Server Secret Key in client-side applications, or expose it in any external-facing documents. If you accidentally expose it, you can create a new one in the Statsig console. {% /callout %} ```go import ( statsig "github.com/statsig-io/go-sdk" ) statsig.Initialize("server-secret-key") // Or, if you want to initialize with certain options statsig.InitializeWithOptions("server-secret-key", &Options{Environment: Environment{Tier: "staging"}}) ``` `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ## Checking a Feature Flag/Gate After the SDK is initialized, you can check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (`return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example, to check a gate for a user: ```go user := statsig.User{UserID: "some_user_id"} feature := statsig.CheckGate(user, "use_new_feature") if feature { // Gate is on, enable new feature } else { // Gate is off } ``` ## Retrieving Feature Gate Metadata When you need more than a boolean value from a gate evaluation, use the Get Feature Gate API, which returns a FeatureGate object with additional evaluation metadata: ```go user := statsig.User{UserID: "some_user_id"} feature := statsig.GetGate(user, "use_new_feature") if feature.Value { // Gate is on, enable new feature fmt.Print(feature.EvaluationDetails.Reason) } ``` ## Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The Dynamic Config API is similar to Feature Gates, but returns a full JSON object configured on the server, from which you can fetch typed parameters. ```go config := statsig.GetConfig(user, "awesome_product_details") // The 2nd parameter is the default value to be used in case the given parameter name does not exist on // the Dynamic Config object. This can happen when there is a typo, or when the user is offline and the // value has not been cached on the client. itemName := config.GetString("product_name", "Awesome Product v1"); double price = config.GetNumber("price", 10.0); bool shouldDiscount = config.GetBool("discount", false); // Or just get the whole json object backing this config if you prefer json := config.Value ``` ## Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for faster iterations with parameter reuse. ```go // Values via getLayer layer := Statsig.GetLayer(user, "user_promo_experiments"); promoTitle := layer.GetString("title", "Welcome to Statsig!"); discount := layer.GetDouble("discount", 0.1); // or, via getExperiment titleExperiment := Statsig.GetExperiment(user, "new_user_promo_title"); priceExperiment := Statsig.GetExperiment(user, "new_user_promo_price"); promoTitle := titleExperiment.GetString("title", "Welcome to Statsig!"); discount := priceExperiment.GetNumber("discount", 0.1); ... price := msrp * (1 - discount); // getting the layer name that an experiment belongs to userPromoLayer := Statsig.GetExperimentLayer("new_user_promo_title"); ``` ## Logging an Event To track custom events and measure how features or experiment groups affect those events, call the Log Event API. Specify the user and event name to log, and optionally provide a value and metadata object: ```go statsig.LogEvent(Event{ User: user, EventName: "add_to_cart", Value: "SKU_12345", Metadata: map[string]string{"price": "9.99","item_name": "diet_coke_48_pack"}, }) ``` For more about identifying users, group analytics, and best practices, go to the [logging events guide](/guides/logging-events). ## Statsig User When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks), and to correctly measure the impact of your experiments on your metrics/events. At least one identifier (userID or customID) is required to provide a consistent experience for a given user. Refer to [userID requirements](/sdks/user#why-is-an-id-always-required-for-server-sdks) for more detail. In addition to `userID`, `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion` are available as top-level fields on StatsigUser. You can also pass any key-value pairs in an object/dictionary to the `custom` field to create targeting based on them. Typing on the `StatsigUser` object is lenient: you can pass numbers, strings, arrays, objects, and even enums or classes. However, evaluation operators only work on primitive types, mostly strings and numbers. The SDK attempts to cast custom field types to match the operator, but evaluation results for other types are not guaranteed. For example, an array set as a custom field is only compared as a string: there is no operator to match a value within that array. ### Private Attributes To keep sensitive user PII data out of logs, use the `privateAttributes` field on the StatsigUser object. This field accepts an object/dictionary of private user attributes. Any attribute set in `privateAttributes` is used only for evaluation/targeting and is removed from all logs before Statsig sends them to its servers. For example, if a feature gate should only pass for users with emails ending in "@statsig.com", but you don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ## Statsig Options `initialize()` takes an optional `options` parameter in addition to the secret key to customize the Statsig client. Specify optional parameters when initializing using `InitializeWithOptions()`: ```go type Options struct { API string `json:"api"` Environment Environment `json:"environment"` LocalMode bool `json:"localMode"` ConfigSyncInterval time.Duration IDListSyncInterval time.Duration BootstrapValues string RulesUpdatedCallback func(rules string, time int64) } ``` - **Environment**: default nil. An object used to set environment variables that apply to all users in the same session, for targeting purposes. The most common use is to set the environment tier (string) so feature gates pass or fail for specific environments. Accepted values are "production", "staging", and "development". - **LocalMode**: default false. Restricts the SDK to not issue any network requests and only respond with default values (or local overrides). - **ConfigSyncInterval**: default 10 seconds. The interval for polling gate/experiment/config changes. - **IDListSyncInterval**: default 1 minute. The interval for polling ID list changes. - **BootstrapValues**: default nil. A string representing all rules for all feature gates, dynamic configs, and experiments. Provide this to bootstrap the Statsig server SDK at initialization if your server encounters a network issue or Statsig servers are temporarily unavailable. - **RulesUpdatedCallback**: default nil. A callback invoked whenever the rulesets are updated. Called with a JSON string representing the rulesets and a timestamp for when the rules were updated. - **UserPersistentStorage**: IUserPersistentStorage, default nil. A persistent storage adapter for running sticky experiments. - **DisableIdList**: default false. Disables fetching the ID list during initialization and background polling for both network and data adapter. ### Client initialize response options When using `getClientInitializeResponse()`, you can specify additional options through the `GCIROptions` struct: ```go type GCIROptions struct { IncludeLocalOverrides bool ClientKey string TargetAppID string HashAlgorithm string IncludeConfigType bool ConfigTypesToInclude []ConfigType } ``` - **IncludeLocalOverrides**: default false. When set to true, the client initialize response includes local overrides. Useful for testing local configuration changes without affecting other users. - **ClientKey**: default empty string. The client SDK key for the initialize response. This key identifies the client application and determines which configurations it receives. Required when generating a client initialize response for client SDKs. - **TargetAppID**: default empty string. Filters configurations (feature gates, dynamic configs, experiments, and layers) to those targeted to this application ID. Useful in multi-tenant or multi-application environments. If not specified, the SDK attempts to determine the target app ID from the provided client key. - **HashAlgorithm**: default empty string. The hashing algorithm used to generate stable IDs in the client. Common values are "djb2" (default if not specified) and "sha256". This should match the hashing algorithm used by the client SDK. - **IncludeConfigType**: default false (deprecated). When set to true, the response includes the type of each configuration. This option is deprecated and may be removed in a future version. - **ConfigTypesToInclude**: default empty array. An array of configuration types to include in the response. Possible values include FeatureGateType, DynamicConfigType, ExperimentType, and LayerType. If empty, all configuration types are included (subject to other filtering options). ## Shutdown To gracefully shutdown the SDK and ensure all events are flushed: ```go statsig.Shutdown() ``` ## Local Overrides You can override the values returned by the SDK for testing purposes, which is useful for local development when testing specific scenarios. ```go func OverrideGate(gate string, val bool) func OverrideConfig(config string, val map[string]interface{}) ``` ## Client SDK bootstrapping The Statsig server SDK can generate the initialization values for a client SDK. This is useful for server-side rendering (SSR) or when you want to pre-fetch values for a client. ```go user := statsig.User{UserID: "some_user_id"} options := statsig.GCIROptions{} options.ClientKey = "client-YOUR_CLIENT_KEY" result := statsig.GetClientInitializeResponseWithOptions(user, &options) // You can then pass 'result' into a Statsig Client SDK ``` ## Data Store A data store synchronizes configuration/value downloads across multiple SDK instances and bootstraps the SDK in offline environments. ### Interface ```go type IDataAdapter interface { /** * Returns the data stored for a specific key */ Get(key string) string /** * Updates data stored for each key */ Set(key string, value string) /** * Startup tasks to run before any get/set calls can be made */ Initialize() /** * Cleanup tasks to run when statsig is shutdown */ Shutdown() /** * Determines whether the SDK should poll for updates from * the data adapter (instead of Statsig network) for the given key */ ShouldBeUsedForQueryingUpdates(key string) bool } ``` ### Example Implementation ```go type dataAdapterExample struct { store map[string]string mu sync.RWMutex } func (d *dataAdapterExample) Get(key string) string { d.mu.RLock() defer d.mu.RUnlock() return d.store[key] } func (d *dataAdapterExample) Set(key string, value string) { d.mu.Lock() defer d.mu.Unlock() d.store[key] = value } func (d *dataAdapterExample) Initialize() {} func (d *dataAdapterExample) Shutdown() {} func (d *dataAdapterExample) ShouldBeUsedForQueryingUpdates(key string) bool { return false } ``` ## User persistent storage User Persistent Storage is a storage adapter for running sticky experiments that persists user assignments across sessions. ### Interface ```go type IUserPersistentStorage interface { /** * Returns the data stored for a specific key */ Load(key string) (string, bool) /** * Updates data stored for a specific key */ Save(key string, data string) } ``` ### Example Implementation ```go type userPersistentStorageExample struct { store map[string]string loadCalled int saveCalled int } func (d *userPersistentStorageExample) Load(key string) (string, bool) { d.loadCalled++ val, ok := d.store[key] return val, ok } func (d *userPersistentStorageExample) Save(key string, value string) { d.saveCalled++ d.store[key] = value } ``` ## Multi-instance usage To create multiple independent instances of the Statsig SDK (for example, to use different API keys or configurations), use the instance-based approach: ```go sdkInstance := NewClientWithOptions(sdkKey, &Options{}) ``` ## FAQ #### How do I run experiments for logged out users? Refer to the guide on [device level experiments](/guides/first-device-level-experiment). #### How can I mock Statsig in tests? Use the [Local Override](#local-overrides) APIs in v1.3.0+, in combination with the `LocalMode` option in `StatsigOptions`, to force gate/config values in test environments and remove network access to Statsig servers. For example: ```go c := NewClientWithOptions(secret, &Options{LocalMode: true}) user := User{ UserID: "123", } gateDefault := c.CheckGate(user, "any_gate") // "any_gate" is false by default c.OverrideGate("any_gate", true) // "any_gate" is now true ``` Refer to [https://github.com/statsig-io/go-sdk/blob/main/overrides_test.go](https://github.com/statsig-io/go-sdk/blob/main/overrides_test.go) ## Reference ### StatsigUser ```go // User specific attributes for evaluating Feature Gates, Experiments, and DynamicConfigs // // Learn more why a UserID or a customID is required: /sdks/user#why-is-an-id-always-required-for-server-sdks // PrivateAttributes are only used for user targeting/grouping in feature gates, dynamic configs, // experiments and etc; they are omitted in logs. type User struct { UserID string `json:"userID"` Email string `json:"email,omitempty"` IpAddress string `json:"ip,omitempty"` // Many jurisdictions categorize this as PII; verify whether you should log this. UserAgent string `json:"userAgent,omitempty"` Country string `json:"country,omitempty"` Locale string `json:"locale,omitempty"` AppVersion string `json:"appVersion,omitempty"` Custom map[string]interface{} `json:"custom,omitempty"` PrivateAttributes map[string]interface{} `json:"privateAttributes,omitempty"` StatsigEnvironment map[string]string `json:"statsigEnvironment,omitempty"` CustomIDs map[string]string `json:"customIDs"` } ``` ### StatsigOptions ```go // Advanced options for configuring the Statsig SDK type Options struct { API string `json:"api"` APIOverrides APIOverrides `json:"api_overrides"` Environment Environment `json:"environment"` LocalMode bool `json:"localMode"` ConfigSyncInterval time.Duration IDListSyncInterval time.Duration LoggingInterval time.Duration LoggingMaxBufferSize int BootstrapValues string RulesUpdatedCallback func(rules string, time int64) InitTimeout time.Duration DataAdapter IDataAdapter OutputLoggerOptions OutputLoggerOptions StatsigLoggerOptions StatsigLoggerOptions EvaluationCallbacks EvaluationCallbacks DisableCDN bool // Disables use of CDN for downloading config specs UserPersistentStorage IUserPersistentStorage IPCountryOptions IPCountryOptions UAParserOptions UAParserOptions } type APIOverrides struct { DownloadConfigSpecs string `json:"download_config_specs"` GetIDLists string `json:"get_id_lists"` LogEvent string `json:"log_event"` } type EvaluationCallbacks struct { GateEvaluationCallback func(name string, result bool, exposure *ExposureEvent) ConfigEvaluationCallback func(name string, result DynamicConfig, exposure *ExposureEvent) ExperimentEvaluationCallback func(name string, result DynamicConfig, exposure *ExposureEvent) LayerEvaluationCallback func(name string, param string, result DynamicConfig, exposure *ExposureEvent) ExposureCallback func(name string, exposure *ExposureEvent) IncludeDisabledExposures bool } type OutputLoggerOptions struct { LogCallback func(message string, err error) EnableDebug bool DisableInitDiagnostics bool DisableSyncDiagnostics bool } type StatsigLoggerOptions struct { DisableInitDiagnostics bool DisableSyncDiagnostics bool DisableApiDiagnostics bool DisableAllLogging bool } type IPCountryOptions struct { Disabled bool // Fully disable IP to country lookup LazyLoad bool // Load in background EnsureLoaded bool // Wait until loaded when needed } type UAParserOptions struct { Disabled bool // Fully disable UA parser LazyLoad bool // Load in background EnsureLoaded bool // Wait until loaded when needed } // See /guides/using-environments type Environment struct { Tier string `json:"tier"` Params map[string]string `json:"params"` } // options for getClientInitializeResponse type GCIROptions struct { IncludeLocalOverrides bool ClientKey string HashAlgorithm string //supports "sha256", "djb2", "none", default "sha256" } ``` ### Event ```go type Event struct { EventName string `json:"eventName"` User User `json:"user"` Value string `json:"value"` Metadata map[string]string `json:"metadata"` Time int64 `json:"time"` } ``` ### FeatureGate ```go type FeatureGate struct { Name string `json:"name"` Value bool `json:"value"` RuleID string `json:"rule_id"` IDType string `json:"id_type"` GroupName string `json:"group_name"` EvaluationDetails *EvaluationDetails `json:"evaluation_details"` } ``` ### DynamicConfig ```go type DynamicConfig struct { Name string `json:"name"` Value map[string]interface{} `json:"value"` RuleID string `json:"rule_id"` IDType string `json:"id_type"` GroupName string `json:"group_name"` EvaluationDetails *EvaluationDetails `json:"evaluation_details"` AllocatedExperimentName string `json:"allocated_experiment_name"` GetString(key string, fallback string) string GetNumber(key string, fallback float64) float64 GetBool(key string, fallback bool) bool GetSlice(key string, fallback []interface{}) []interface{} GetMap(key string, fallback map[string]interface{}) map[string]interface{} } ``` ================================================================================ # Legacy Node.js Server SDK URL: https://docs.statsig.com/server/nodejsServerSDK Updated: 2026-07-11 ================================================================================ ## Setup the SDK {% steps %} {% step title="Install the SDK" %} The Node.js SDK is hosted [here](https://www.npmjs.com/package/statsig-node). You can install the SDK using NPM or Yarn: {% codetabs %} ```bash npm npm install statsig-node ``` ```bash yarn yarn add statsig-node ``` {% /codetabs %} {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Do NOT embed your Server Secret Key in client-side applications, or expose it in any external-facing documents. However, if you accidentally expose it, you can create a new one in the Statsig console. {% /callout %} ```javascript const Statsig = require("statsig-node"); await Statsig.initialize( "server-secret-key", { environment: { tier: "staging" } } // optional, if not set, for >v6.0.0, sdk will default to be production ); ``` `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ## Checking a Feature Flag/Gate After the SDK is initialized, you can check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (`return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example, to check a gate for a user: ```javascript const user = { userID: '12345', email: '12345@gmail.com', ... }; const showNewDesign = Statsig.checkGate(user, 'new_homepage_design'); if (showNewDesign) { // show new design here } else { // show old design here } ``` ## Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The Dynamic Config API is similar to Feature Gates, but returns a full JSON object configured on the server, from which you can fetch typed parameters. ```javascript const config = Statsig.getConfig(user, "awesome_product_details"); // The 2nd parameter is the default value to be used in case the given parameter name does not exist on // the Dynamic Config object. This can happen when there is a typo, or when the user is offline and the // value has not been cached on the client. const itemName = config.get("product_name", "Awesome Product v1"); const price = config.get("price", 10.0); const shouldDiscount = config.get("discount", false); ``` ## Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for faster iterations with parameter reuse. ```javascript // Values via getLayer const layer = Statsig.getLayer(user, "user_promo_experiments"); const promoTitle = layer.get("title", "Welcome to Statsig!"); const discount = layer.get("discount", 0.1); // or, via getExperiment const promoExperiment = Statsig.getExperiment(user, "new_user_promo"); const promoTitle = promoExperiment.get("title", "Welcome to Statsig!"); const discount = promoExperiment.get("discount", 0.1); ... const price = msrp * (1 - discount); ``` ## Retrieving Feature Gate Metadata When you need more than a boolean value from a gate evaluation, use the Get Feature Gate API, which returns a FeatureGate object with additional evaluation metadata: ```javascript const gate = Statsig.getFeatureGate(user, 'new_homepage_design'); console.log(gate.name); // 'new_homepage_design' console.log(gate.value); // true or false console.log(gate.ruleID); // rule ID that was evaluated console.log(gate.evaluationDetails); // evaluation metadata ``` ## Logging an Event To track custom events and measure how features or experiment groups affect those events, call the Log Event API. Specify the user and event name to log, and optionally provide a value and metadata object: ```javascript Statsig.logEvent(user, "add_to_cart", "SKU_12345", { price: "9.99", item_name: "diet_coke_48_pack", }); ``` For more about identifying users, group analytics, and best practices, go to the [logging events guide](/guides/logging-events). ## Statsig User When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks), and to correctly measure the impact of your experiments on your metrics/events. At least one identifier (userID or customID) is required to provide a consistent experience for a given user. Refer to [userID requirements](/sdks/user#why-is-an-id-always-required-for-server-sdks) for more detail. In addition to `userID`, `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion` are available as top-level fields on StatsigUser. You can also pass any key-value pairs in an object/dictionary to the `custom` field to create targeting based on them. Typing on the `StatsigUser` object is lenient: you can pass numbers, strings, arrays, objects, and even enums or classes. However, evaluation operators only work on primitive types, mostly strings and numbers. The SDK attempts to cast custom field types to match the operator, but evaluation results for other types are not guaranteed. For example, an array set as a custom field is only compared as a string: there is no operator to match a value within that array. ### Private Attributes To keep sensitive user PII data out of logs, use the `privateAttributes` field on the StatsigUser object. This field accepts an object/dictionary of private user attributes. Any attribute set in `privateAttributes` is used only for evaluation/targeting and is removed from all logs before Statsig sends them to its servers. For example, if a feature gate should only pass for users with emails ending in "@statsig.com", but you don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ## Statsig Options `initialize()` takes an optional `options` parameter in addition to the secret key to customize the Statsig client: {% parameter name="api" type="string" %} The base url to use for all network requests. Defaults to the statsig API. {% /parameter %} {% parameter name="environment" type="StatsigEnvironment" %} An object you can use to set environment variables that apply to all your users in the same session, used for targeting purposes. The most common usage is to set the environment tier ('production', 'staging' or 'development'), e.g. `{ tier: 'staging' }`, and have feature gates pass/fail for specific environments. Since v6.0.0 the default environment tier is production. {% /parameter %} {% parameter name="bootstrapValues" type="string" %} A string that represents all rules for all feature gates, dynamic configs and experiments. It can be provided to bootstrap the Statsig server SDK at initialization in case your server runs into network issue or Statsig server is down temporarily. {% /parameter %} {% parameter name="rulesUpdatedCallback" type="function" %} A callback function that's called whenever the rules update; it's called with a JSON string (used as is for `bootstrapValues` mentioned above) and a timestamp, like below: ``` options.rulesUpdatedCallback(specsString, timeStamp) ``` {% /parameter %} {% parameter name="logger" type="LoggerInterface" %} The logger interface to use for printing to stdout/stderr {% /parameter %} {% parameter name="localMode" type="boolean" %} Disables all network access, so the SDK will only return default (or overridden) values. Useful in testing. {% /parameter %} {% parameter name="initTimeoutMs" type="number" %} Sets a maximum time to wait for the config download network request to resolve before considering the SDK initialized and resolving the call to `initialize()` {% /parameter %} {% parameter name="dataAdapter" type="IDataAdapter" %} An adapter with custom storage behavior for config specs. Can be used to bootstrap Statsig server (takes priority over `bootstrapValues`). Can also be used to continuously fetch updates in place of the Statsig network. Refer to [Data Stores](/server/concepts/data_store). For example, go to the 1P implementation using Redis [statsig-node-redis](https://github.com/statsig-io/node-js-server-sdk-redis). {% /parameter %} {% parameter name="UserPersistentStorage" type="IUserPersistentStorage" %} A persistent storage adapter for running sticky experiments. Refer to [examples](/server/nodejsServerSDK#user-persistent-storage). {% /parameter %} {% parameter name="rulesetsSyncIntervalMs" type="number" %} Sets the polling interval for the SDK to ask Statsig backend for changes on the rulesets. {% /parameter %} {% parameter name="idListsSyncIntervalMs" type="number" %} Sets the polling interval for the SDK to ask Statsig backend for changes on the ID Lists. {% /parameter %} {% parameter name="loggingIntervalMs" type="number" %} Sets the interval for the SDK to periodically flush all logging events to Statsig backend. {% /parameter %} {% parameter name="loggingMaxBufferSize" type="number" %} Sets the maximum number of events the SDK's logger will batch before flushing them all to Statsig backend. {% /parameter %} {% parameter name="disableDiagnostics" type="boolean" %} Disables diagnostics events from being logged and sent to Statsig {% /parameter %} {% parameter name="initStrategyForIP3Country" type="'await' | 'lazy' | 'none'" %} Method of initializing IP to country lookup on `statsig.initialize()`. {% /parameter %} {% parameter name="initStrategyForIDLists" type="'await' | 'lazy' | 'none'" %} Method of initializing ID lists on `statsig.initialize()`. {% /parameter %} {% parameter name="postLogsRetryLimit" type="number" %} The maximum number of retry attempts when sending `/log_event` requests to Statsig server {% /parameter %} {% parameter name="postLogsRetryBackoff" type="number | (retry: number) => number" %} A fixed number or callback on the retry attempt number to configure the time in ms to wait between each `/log_event` retry. If using a fixed number, a 10x multiplier applies on each subsequent retry. {% /parameter %} {% parameter name="evaluationCallbacks" type="EvaluationCallbacks" %} Provides callback functions for handling custom logic during evaluations of gates, dynamic configs, experiments, or layers. You can provide specific callbacks for each evaluation type to perform tasks such as custom logging (if you prefer not to use Statsig's default logging), or side effects. To turn off Statsig's default logging, set `disableExposureLogging: true` when making checks. Available callbacks: ``` gateCallback?: (gate: FeatureGate, user: StatsigUser, event: LogEvent) => void; dynamicConfigCallback?: (config: DynamicConfig, user: StatsigUser, event: LogEvent) => void; experimentCallback?: (config: DynamicConfig, user: StatsigUser, event: LogEvent) => void; layerCallback?: (layer: Layer, user: StatsigUser) => void; layerParamCallback?: (layer: Layer, paramName: string, user: StatsigUser, event: LogEvent) => void; ``` {% /parameter %} ## Shutdown To gracefully shutdown the SDK and ensure all events are flushed: ```javascript statsig.shutdown(); ``` ## Flush To manually flush logged events: ```javascript await statsig.flush(); ``` ## Client SDK bootstrapping The Statsig server SDK can generate the initialization values for a client SDK. This is useful for server-side rendering (SSR) or when you want to pre-fetch values for a client. ```typescript const values = Statsig.getClientInitializeResponse(user); // Record<string, unknown> | null if (values != null) { // Bootstrap the Statsig React Client SDK return <StatsigSynchronousProvider initializeValues={values} ... />; } ``` ## Local Overrides You can override the values returned by the SDK for testing purposes, which is useful for local development when testing specific scenarios. {% codetabs %} ```typescript TypeScript // Overrides the given gate to the specified value Statsig.overrideGate("a_gate_name", true, "a_user_id"); // Overrides the given config (dynamic config or experiment) to the provided value Statsig.overrideConfig("a_config_or_experiment_name", { key: "value" }, "a_user_id"); // Overrides the given layer to the provided value Statsig.overrideLayer("a_layer_name", { key: "value" }, "a_user_id"); ``` ```javascript JavaScript // Overrides the given gate to the specified value Statsig.overrideGate("a_gate_name", true, "a_user_id"); // Overrides the given config (dynamic config or experiment) to the provided value Statsig.overrideConfig("a_config_or_experiment_name", { key: "value" }, "a_user_id"); // Overrides the given layer to the provided value Statsig.overrideLayer("a_layer_name", { key: "value" }, "a_user_id"); ``` {% /codetabs %} These can be used to set an override for a specific user, or for all users (by not providing a specific user ID). Experiments/Autotune are overridden with the `overrideConfig` API. ### Overriding in getClientInitializeResponse You can also override feature gates, dynamic configs, experiments, and layers when calling `getClientInitializeResponse`. This is useful when you need to provide specific values to the client SDK. {% codetabs %} ```typescript TypeScript // Get client initialize response with overrides const response = Statsig.getClientInitializeResponse(user, clientSDKKey, { overrides: { // Override feature gates featureGates: { 'my_gate': true, // Override gate value to true }, // Override dynamic configs and experiments dynamicConfigs: { // Override config value directly 'price_config': { value: { price: 9.99 } }, // Override experiment by setting the group assignment 'color_experiment': { groupName: 'Control' // Uses the value for the Control group }, // Override both value and group assignment 'spacing_experiment': { value: { spacing: 64 }, groupName: 'Variant_B' } }, // Override layers layers: { 'my_layer': { value: { param: 123 } } } } }); ``` ```javascript JavaScript // Get client initialize response with overrides const response = Statsig.getClientInitializeResponse(user, clientSDKKey, { overrides: { // Override feature gates featureGates: { 'my_gate': true, // Override gate value to true }, // Override dynamic configs and experiments dynamicConfigs: { // Override config value directly 'price_config': { value: { price: 9.99 } }, // Override experiment by setting the group assignment 'color_experiment': { groupName: 'Control' // Uses the value for the Control group }, // Override both value and group assignment 'spacing_experiment': { value: { spacing: 64 }, groupName: 'Variant_B' } }, // Override layers layers: { 'my_layer': { value: { param: 123 } } } } }); ``` {% /codetabs %} For experiments, you can override them in two ways: 1. By setting a `value` override on their dynamic config to directly specify the parameter values 2. By setting the `groupName` assignment to use the value for that group name (e.g., "Control" or "Test") You can also combine both approaches to override both the group assignment and the parameter values. ## Manual Exposures Statsig SDKs automatically log an exposure event every time a gate/experiment/config is checked. In some scenarios, you may want to control when to log an exposure. You can disable the automatic logging like this: ### Gates ```javascript const result = Statsig.checkGate(aUser, 'a_gate_name', {disableExposureLogging: true}); ``` Then, to manually log the exposure: ```javascript Statsig.manuallyLogGateExposure(aUser, 'a_gate_name'); ``` ### Dynamic Configs ```javascript const config = Statsig.getConfigWithExposureLoggingDisabledSync(aUser, 'a_dynamic_config_name'); ``` Then, to manually log the exposure: ```javascript Statsig.manuallyLogConfigExposure(aUser, 'a_dynamic_config_name'); ``` ### Experiments ```javascript const experiment = Statsig.getExperimentWithExposureLoggingDisabledSync(aUser, 'an_experiment_name'); ``` Then, to manually log the exposure: ```javascript Statsig.manuallyLogExperimentExposure(aUser, 'an_experiment_name'); ``` ### Layers ```javascript const layer = Statsig.getLayerWithExposureLoggingDisabledSync(aUser, 'a_layer_name'); const paramValue = layer.get('a_param_name', 'fallback_value'); ``` Then, to manually log the layer parameter exposure: ```javascript Statsig.manuallyLogLayerParameterExposure(aUser, 'a_layer_name', 'a_param_name'); ``` ## Cloudflare Workers setup ### Polling for updates The SDK can't poll for updates across requests since Cloudflare doesn't allow for timers. To solve for this, a manual sync API is available for independently updating the SDK internal store. ```javascript if (env.lastSyncTime < Date.now() - env.syncInterval) { env.lastSyncTime = Date.now(); context.waitUntil(Statsig.syncConfigSpecs()); } ``` ### Flushing events The SDK enqueues logged events and flushes them in batches. To ensure events are properly flushed, call `flush` using [`context.waitUntil`](https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#contextwaituntil). This keeps the request handler alive until events are flushed without blocking the response. ```javascript context.waitUntil(Statsig.flush()); ``` ### Node.JS Compatibility Many native JavaScript API and Node standard libraries can be accessed in Cloudflare through the [`nodejs_compat`](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) compatibility flag. The SDK is now compatible with `nodejs_compat` (since v5.16.0). In older versions, manual polyfilling is required. ## User persistent storage User Persistent Storage is a storage adapter for running sticky experiments. It allows you to persist user assignments across sessions. ### Interface ```typescript export interface IUserPersistentStorage { /** * Returns the full map of persisted values for a specific user key * @param key user key */ load(key: string): UserPersistedValues; /** * Save the persisted values of a config given a specific user key * @param key user key * @param configName Name of the config/experiment * @param data Object representing the persistent assignment to store for the given user-config */ save(key: string, configName: string, data: StickyValues): void; /** * Delete the persisted values of a config given a specific user key * @param key user key * @param configName Name of the config/experiment */ delete(key: string, configName: string): void; } ``` ### Example Implementation ```typescript class UserPersistentStorageExample implements IUserPersistentStorage { public store: Record<string, UserPersistedValues> = {}; load(key: string): UserPersistedValues { return this.store[key]; } save(key: string, configName: string, data: StickyValues): void { if (!(key in this.store)) { this.store[key] = {}; } this.store[key][configName] = data; } delete(key: string, configName: string): void { delete this.store[key][configName]; } } ``` ## Multi-instance usage To create multiple independent instances of the Statsig SDK (for example, to use different API keys or configurations), use the instance-based approach: ```javascript // Statsig.initialize becomes: const sdkInstance = new StatsigServer(secretKey, options); await sdkInstance.initializeAsync(); ``` ## Forward proxy configuration You can configure the SDK to use a forward proxy for network requests: ```javascript const proxyAddress = "0.0.0.0:50051" const options = { proxyConfigs: { 'download_config_specs': { "proxyAddress": proxyAddress, "protocol": "grpc_websocket" as NetworkProtocol } } } await Statsig.initialize(secretKey, options) ``` ## FAQs ### How can I use the node SDK for server side rendering? Refer to [Client SDK Bootstrapping | SSR](#bootstrap). ### How can I mock Statsig for testing? Refer to [LocalOverrides](#local-overrides). ## Reference ### Type StatsigUser ```typescript export type StatsigUser = // at least one of userID or customIDs must be provided ({ userID: string } | { customIDs: Record<string, string> }) & { userID?: string; customIDs?: Record<string, string>; email?: string; ip?: string; userAgent?: string; country?: string; locale?: string; appVersion?: string; custom?: Record< string, string | number | boolean | Array<string> | undefined >; privateAttributes?: Record< string, string | number | boolean | Array<string> | undefined > | null; statsigEnvironment?: StatsigEnvironment; } ``` ### Type StatsigOptions ```typescript export type StatsigOptions = { api: string; apiForDownloadConfigSpecs: string; apiForGetIdLists: string; bootstrapValues: string | null; environment: StatsigEnvironment | null; rulesUpdatedCallback: RulesUpdatedCallback | null; logger: LoggerInterface; localMode: boolean; initTimeoutMs: number; dataAdapter: IDataAdapter | null; rulesetsSyncIntervalMs: number; idListsSyncIntervalMs: number; loggingIntervalMs: number; loggingMaxBufferSize: number; disableDiagnostics: boolean; initStrategyForIP3Country: InitStrategy; initStrategyForIDLists: InitStrategy; postLogsRetryLimit: number; postLogsRetryBackoff: RetryBackoffFunc | number; disableRulesetsSync: boolean; disableIdListsSync: boolean; disableAllLogging: boolean; }; export type RulesUpdatedCallback = (rulesJSON: string, time: number) => void; export type RetryBackoffFunc = (retriesRemaining: number) => number; export type StatsigEnvironment = { tier?: 'production' | 'staging' | 'development' | string; [key: string]: string | undefined; }; export type InitStrategy = 'await' | 'lazy' | 'none'; export interface LoggerInterface { debug?(message?: any, ...optionalParams: any[]): void; info?(message?: any, ...optionalParams: any[]): void; warn(message?: any, ...optionalParams: any[]): void; error(message?: any, ...optionalParams: any[]): void; logLevel: 'none' | 'debug' | 'info' | 'warn' | 'error'; } ``` ### Type FeatureGate ```typescript export type FeatureGate = { readonly name: string; readonly ruleID: string; readonly idType: string | null; readonly value: boolean; readonly evaluationDetails: EvaluationDetails | null; readonly groupName: null; // deprecated }; ``` ### Type DynamicConfig ```typescript export default class DynamicConfig { name: string; value: Record<string, unknown>; get<T>( key: string, defaultValue: T, typeGuard: ((value: unknown) => value is T | null) | null = null, ): T; getValue( key: string, defaultValue?: boolean | number | string | object | Array<any> | null, ): unknown | null; getRuleID(): string; getGroupName(): string | null; getIDType(): string | null; getEvaluationDetails(): EvaluationDetails | null; ``` ### Type Layer ```typescript export default class Layer { name: string; public get<T>( key: string, defaultValue: T, typeGuard: ((value: unknown) => value is T) | null = null, ): T; getValue( key: string, defaultValue?: boolean | number | string | object | Array<any> | null, ): unknown | null; getRuleID(): string; getGroupName(): string | null; getAllocatedExperimentName(): string | null; getEvaluationDetails(): EvaluationDetails | null; ``` ### DataAdapter ```typescript export interface IDataAdapter { get(key: string): Promise<AdapterResponse>; set(key: string, value: string, time?: number): Promise<void>; initialize(): Promise<void>; shutdown(): Promise<void>; supportsPollingUpdatesFor(key: DataAdapterKey): boolean; } ``` ### EvaluationDetails ```typescript export class EvaluationDetails { readonly configSyncTime: number; readonly initTime: number; readonly serverTime: number; readonly reason: EvaluationReason; } ``` ### EvaluationReason ```typescript export type EvaluationReason = | 'Network' | 'LocalOverride' | 'Unrecognized' | 'Uninitialized' | 'Bootstrap' | 'DataAdapter' | 'Unsupported'; ``` ================================================================================ # Legacy Python Server SDK URL: https://docs.statsig.com/server/pythonSDK Updated: 2026-07-11 ================================================================================ ## Setup the SDK {% steps %} {% step title="Install the SDK" %} Install the sdk using [pip3](https://pypi.org/project/statsig/): {% callout type="note" %} The Statsig SDK isn't compatible with python 2. You must be on python 3.7+ to use the Statsig SDK. {% /callout %} ```bash pip3 install statsig ``` {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Do NOT embed your Server Secret Key in client-side applications, or expose it in any external-facing documents. However, if you accidentally expose it, you can create a new one in the Statsig console. {% /callout %} There is also an optional parameter named `options` that allows you to pass in a [StatsigOptions](#statsig-options) to customize the SDK. ```python from statsig import statsig statsig.initialize("server-secret-key") # or with StatsigOptions options = StatsigOptions(tier=StatsigEnvironmentTier.development) statsig.initialize("server-secret-key", options) # check if sdk is initialized initialized = statsig.is_initialized() ``` `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ## Checking a Feature Flag/Gate After the SDK is initialized, you can check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (`return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example, to check a gate for a user: ```python from statsig.statsig_user import StatsigUser ... statsig.check_gate(StatsigUser("user-id"), "gate-name") ``` ## Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The Dynamic Config API is similar to Feature Gates, but returns a full JSON object configured on the server, from which you can fetch typed parameters. ```python config = statsig.get_config(StatsigUser("user-id"), "config-name") config_json = config.get_value() ``` ## Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for faster iterations with parameter reuse. ```python # Values via getLayer layer = statsig.get_layer(user, "user_promo_experiments") title = layer.get("title", "Welcome to Statsig!") discount = layer.get("discount", 0.1) # or, via getExperiment title_exp = statsig.get_experiment(user, "new_user_promo_title") price_exp = statsig.get_experiment(user, "new_user_promo_price") title = title_exp.get("title", "Welcome to Statsig!") discount = price_exp.get("discount", 0.1) ... price = msrp * (1 - discount) ``` ## Retrieving Feature Gate Metadata When you need more than a boolean value from a gate evaluation, use the Get Feature Gate API, which returns a FeatureGate object with additional evaluation metadata: ```python gate = statsig.get_feature_gate(StatsigUser("user-id"), "gate-name") print(gate.name) # 'gate-name' print(gate.value) # True or False print(gate.rule_id) # rule ID that was evaluated print(gate.evaluation_details) # evaluation metadata ``` ## Logging an Event To track custom events and measure how features or experiment groups affect those events, call the Log Event API. Specify the user and event name to log, and optionally provide a value and metadata object: ```python from statsig.statsig_user import StatsigUser from statsig.statsig_event import StatsigEvent statsig.log_event(StatsigEvent(StatsigUser("user-id"), "event-name")) ``` Python supports `retry_queue_size`, which allows you to adjust the memory allocated for handling retries. While service outages are rare, increasing the retry_queue_size can help minimize event loss by providing additional memory to buffer events during such occurrences. This option is generally not needed for typical use but offers added flexibility in exceptional situations. ## Statsig User When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks), and to correctly measure the impact of your experiments on your metrics/events. At least one identifier (userID or customID) is required to provide a consistent experience for a given user. Refer to [userID requirements](/sdks/user#why-is-an-id-always-required-for-server-sdks) for more detail. In addition to `userID`, `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion` are available as top-level fields on StatsigUser. You can also pass any key-value pairs in an object/dictionary to the `custom` field to create targeting based on them. Typing on the `StatsigUser` object is lenient: you can pass numbers, strings, arrays, objects, and even enums or classes. However, evaluation operators only work on primitive types, mostly strings and numbers. The SDK attempts to cast custom field types to match the operator, but evaluation results for other types are not guaranteed. For example, an array set as a custom field is only compared as a string: there is no operator to match a value within that array. ### Private Attributes To keep sensitive user PII data out of logs, use the `privateAttributes` field on the StatsigUser object. This field accepts an object/dictionary of private user attributes. Any attribute set in `privateAttributes` is used only for evaluation/targeting and is removed from all logs before Statsig sends them to its servers. For example, if a feature gate should only pass for users with emails ending in "@statsig.com", but you don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ## Statsig Options `initialize()` takes an optional `options` parameter in addition to the secret key to customize the Statsig client. Create a `StatsigOptions` class with the following available parameters: (unit of measure for time related options is seconds) {% parameter name="tier" type="StatsigEnvironmentTier | str" %} Sets the environment tier (for gates to evaluate differently in development and production) Set the environment tier using the `StatsigEnvironmentTier` enum or as a `str`. {% /parameter %} {% parameter name="timeout" type="int" %} Enforces a minimum timeout on network requests from the SDK {% /parameter %} {% parameter name="init_timeout" type="int" %} Sets the maximum timeout on download config specs and id lists network requests for initialization {% /parameter %} {% parameter name="rulesets_sync_interval" type="int" %} How often the SDK updates rulesets from Statsig servers {% /parameter %} {% parameter name="idlists_sync_interval" type="int" %} How often the SDK updates idlists from Statsig servers {% /parameter %} {% parameter name="local_mode" type="bool" %} Disables all network requests. SDK returns default values and won't log events. Useful in combination with overrides to mock behavior for tests. {% /parameter %} {% parameter name="bootstrap_values" type="str" %} a string that represents all rules for all feature gates, dynamic configs and experiments. It can be provided to bootstrap the Statsig server SDK at initialization in case your server runs into network issue or Statsig server is down temporarily. {% /parameter %} {% parameter name="rules_updated_callback" type="typing.Callable" %} a callback function that's called whenever the rules update; it's called with a logical timestamp and a JSON string (used as is for bootstrapValues mentioned above). As of v0.6.0, the SDK calls this from a background thread that it uses to update config values. {% /parameter %} {% parameter name="event_queue_size" type="int" %} The number of events to batch before flushing the queue to the network. Default 500. Events are also batched every minute by a background thread {% /parameter %} {% parameter name="data_store" type="IDataStore" %} A data store with custom storage behavior for config specs. Can be used to bootstrap Statsig server (takes priority over `bootstrap_values`). {% /parameter %} {% parameter name="proxy_configs" type="Optional[Dict[NetworkEndpoint, ProxyConfig]]" %} Configuration network for each endpoint, for example, download_config_spec, get_id_lists {% /parameter %} {% parameter name="fallback_to_statsig_api" type="Optional[bool]" %} Fallback to Statsig CDN for download config specs and get id lists if the overridden api failed. {% /parameter %} {% parameter name="initialize_sources" type="Optional[List[DataSource]]" %} List of sources SDK tries to get download_config_specs from when initialize. The list is ordered, SDK tries to get source from first element, and stops when getting dcs successfully {% /parameter %} {% parameter name="config_sync_sources" type="Optional[List[DataSource]]" %} List of sources SDK tries to get download_config_specs from when downloading. The list is ordered, SDK tries to get source from first element, and stops when getting dcs successfully {% /parameter %} Example: ```python from statsig import statsig, StatsigEnvironmentTier, StatsigOptions options = StatsigOptions(None, StatsigEnvironmentTier.development) statsig.initialize("secret-key", options).wait() ``` You can also use the `set_environment_parameter` function, but that takes in string values only: ```python from statsig import statsig, StatsigEnvironmentTier, StatsigOptions options = StatsigOptions() options.set_environment_parameter("tier", StatsigEnvironmentTier.development.value) statsig.initialize("secret-key", options).wait() ``` ## Shutdown To gracefully shutdown the SDK and ensure all events are flushed: ```python statsig.shutdown() ``` ## Client SDK bootstrapping The Statsig server SDK can generate the initialization values for a client SDK. This is useful for server-side rendering (SSR) or when you want to pre-fetch values for a client. ```python values = statsig.get_client_initialize_response(user); # dict() | None # To apply local overrides, set include_local_overrides = True (python sdk v0.32.0+) values = statsig.get_client_initialize_response(user=user, include_local_overrides=True); # dict() | None ``` ## Local Overrides You can override the values returned by the SDK for testing purposes, which is useful for local development when testing specific scenarios. ```python # Adding/Removing gate overrides statsig.override_gate("a_gate_name", true, "a_user_id") statsig.remove_gate_override("a_gate_name", "a_user_id") # Adding/Removing config overrides statsig.override_config("a_config_name", {"key": "value"}, "a_user_id") statsig.remove_config_override("a_config_name", "a_user_id") # Adding/Removing experiment overrides statsig.override_experiment("an_experiment_name", {"key": "value"}, "a_user_id") statsig.remove_experiment_override("an_experiment_name", "a_user_id") # Remove All Overrides statsig.remove_all_overrides() # You can also override with custom ids custom_id_user = StatsigUser("a_user_id", custom_ids={"statsigId": "a_statsig_id"}) statsig.override_gate("a_gate_name", true, "a_statsig_id") # Local overrides will prioritize override with userId, then look up the custom id to override. # To prevent clashing overrides, it is recommended to not use the same value for userId and customIds for different users. ``` ## Multi-instance usage To create multiple independent instances of the Statsig SDK (for example, to use different API keys or configurations), use the instance-based approach: ```python sdk_instance = StatsigServer() sdk_instance.initialize(secret_key, options); ``` ## Forward proxy configuration You can configure the SDK to use a forward proxy for network requests: Basic setup to stream download config spec from forward proxy: ```python proxyAddress = "0.0.0.0:50051" // local address update to your address Statsig.initialize(secret_key, StatsigOptions(proxy_configs={ NetworkEndpoint.DOWNLOAD_CONFIG_SPECS: ProxyConfig(NetworkProtocol.GRPC_WEBSOCKET, proxyAddress)})) ``` When the SDK disconnects from the forward proxy when using grpc_websocket, the SDK retries the connection with exponential backoff. After `push_worker_failover_threshold` retries, the SDK starts polling from Statsig until it reconnects to the forward proxy. You can customize Streaming Failover Behavior. You can also define the sources/endpoints SDK poll from, SDK will try from source at index 0, and stops trying if get a response. ```python statsigOptions = StatsigOptions( proxy_configs={ NetworkEndpoint.DOWNLOAD_CONFIG_SPECS: ProxyConfig( protocol=NetworkProtocol.GRPC_WEBSOCKET, proxy_address=address, push_worker_failover_threshold=1, # start polling from Statsig endpoint after 1 retry failed # 1st retry 5000 ms later, 2nd retry 2 * 5000ms = 10 seconds .... retry_backoff_multiplier=2, max_retry_attempt=8, retry_backoff_base_ms=5000 ) }, # Get from network first, which is forward proxy here, if fails, try datastore, if fails try poll from Statsig endpoint initialize_sources=[ DataSource.NETWORK, DataSource.DATASTORE, DataSource.STATSIG_NETWORK, ], ) ``` ## FAQs ### How can I mock Statsig for testing? The python server SDK, starting in version 0.5.1+, supports a few features to make testing easier. The `StatsigOptions` parameter `localMode`, when set to true, prevents the SDK from making network requests and causes it to return only default values. This is useful for placeholder or test environments that shouldn't access the network. The `overrideGate` and `overrideConfig` APIs on the global `statsig` interface (refer to [Local Overrides](#local-overrides)) can set a gate or config override for a specific user, or for all users (by not providing a specific user ID). Enable `localMode` and then override gates/configs/experiments to specific values to test the code flows you are building. ### Can I generate the initialize response for a client SDK using the Python server SDK? Yes. Refer to [Client Initialize Response](#bootstrap). ## Reference ### StatsigUser ```python @dataclass class StatsigUser: """An object of properties relating to the current user user_id or customID is required: /sdks/user#why-is-an-id-always-required-for-server-sdks Provide as many as possible to take advantage of advanced conditions in the statsig console A dictionary of additional fields can be provided under the custom field Set private_attributes for any user property you need for gate evaluation but prefer stripped from logs/metrics """ user_id: Optional[str] = None email: Optional[str] = None ip: Optional[str] = None user_agent: Optional[str] = None country: Optional[str] = None locale: Optional[str] = None app_version: Optional[str] = None custom: Optional[dict] = None # key: string, value: string private_attributes: Optional[dict] = None # key: string, value: string custom_ids: Optional[dict] = None # key: string, value: string ``` ### StatsigOptions ```python class StatsigOptions: """An object of properties for initializing the sdk with additional parameters""" def __init__( self, api: Optional[str] = None, api_for_download_config_specs: Optional[str] = None, api_for_get_id_lists: Optional[str] = None, api_for_log_event: Optional[str] = None, tier: Union[str, StatsigEnvironmentTier, None] = None, init_timeout: Optional[int] = None, timeout: Optional[int] = None, rulesets_sync_interval: int = DEFAULT_RULESET_SYNC_INTERVAL, idlists_sync_interval: int = DEFAULT_IDLIST_SYNC_INTERVAL, local_mode: bool = False, bootstrap_values: Optional[str] = None, rules_updated_callback: Optional[Callable] = None, event_queue_size: Optional[int] = DEFAULT_EVENT_QUEUE_SIZE, data_store: Optional[IDataStore] = None, idlists_thread_limit: int = DEFAULT_IDLISTS_THREAD_LIMIT, logging_interval: int = DEFAULT_LOGGING_INTERVAL, #deprecated disable_diagnostics: bool = False, custom_logger: Optional[OutputLogger] = None, enable_debug_logs = False, disable_all_logging = False, evaluation_callback: Optional[Callable[[Union[Layer, DynamicConfig, FeatureGate]], None]] = None, retry_queue_size: int = DEFAULT_RETRY_QUEUE_SIZE, proxy_configs: Optional[Dict[NetworkEndpoint, ProxyConfig]] = None, fallback_to_statsig_api: Optional[bool] = False, initialize_sources: Optional[List[DataSource]] = None, config_sync_sources: Optional[List[DataSource]] = None, ): ``` ### FeatureGate ```python class FeatureGate: def get_value(self): """Returns the underlying value of this FeatureGate""" def get_name(self): """Returns the name of this FeatureGate""" def get_evaluation_details(self): """Returns the evaluation detail of this FeatureGate""" ``` ### DynamicConfig ```python class DynamicConfig: def get(self, key, default=None): """Returns the value of the config at the given key or the provided default if the key is not found """ def get_typed(self, key, default=None): """Returns the value of the config at the given key iff the type matches the type of the provided default. Otherwise, returns the default value """ def get_value(self): """Returns the underlying value of this DynamicConfig""" def get_name(self): """Returns the name of this DynamicConfig""" def get_evaluation_details(self): """Returns the evaluation detail of this DynamicConfig""" ``` ### Layer ```python class Layer: def get(self, key, default=None): """Returns the value of the layer at the given key or the provided default if the key is not found """ def get_typed(self, key, default=None): """Returns the value of the layer at the given key iff the type matches the type of the provided default. Otherwise, returns the default value """ def get_name(self): """Returns the name of this Layer""" def get_values(self): """Returns all the values in this Layer but does not trigger an exposure log""" def get_evaluation_details(self): """Returns the evaluation detail of this Layer""" ``` ### EvaluationDetails ```python class EvaluationDetails: reason: EvaluationReason config_sync_time: int init_time: int server_time: int class EvaluationReason(str, Enum): network = "Network" local_override = "LocalOverride" unrecognized = "Unrecognized" uninitialized = "Uninitialized" bootstrap = "Bootstrap" data_adapter = "DataAdapter" unsupported = "Unsupported" error = "error" ``` ### DataStore ```python class IDataStore: def get(self, key: str) -> Optional[str]: return None def set(self, key: str, value: str): pass def shutdown(self): pass def should_be_used_for_querying_updates(self, key: str) -> bool: return False ``` ### ForwardProxy - ProxyConfig ```python class NetworkProtocol(Enum): HTTP = "http" GRPC = "grpc" GRPC_WEBSOCKET = "grpc_websocket" class NetworkEndpoint(Enum): LOG_EVENT = "log_event" DOWNLOAD_CONFIG_SPECS = "download_config_specs" GET_ID_LISTS = "get_id_lists" ALL = "all" class ProxyConfig: def __init__( self, protocol: NetworkProtocol, proxy_address: str, # Websocket worker failover config max_retry_attempt: Optional[int] = None, # default is 10 retry_backoff_multiplier: Optional[int] = None, # default is # default is 5 retry_backoff_base_ms: Optional[int] = None, # default is 10,000 ms # Push worker failback to polling threshold, fallback immediate set 0, # n means fallback after n retry failed push_worker_failover_threshold: Optional[int] = None, # default is 4, about 30 minutes ): self.proxy_address = proxy_address self.protocol = protocol self.max_retry_attempt = max_retry_attempt self.retry_backoff_multiplier = retry_backoff_multiplier self.retry_backoff_base_ms = retry_backoff_base_ms self.push_worker_failover_threshold = push_worker_failover_threshold ``` ================================================================================ # Legacy Erlang/Elixir Server SDK URL: https://docs.statsig.com/server/erlang Updated: 2026-07-11 ================================================================================ {% callout type="warning" %} Support for the Legacy Erlang SDK ends April 30, 2026. Migrate to the [new Elixir SDK](/server-core/elixir-core) soon. {% /callout %} {% callout type="warning" %} The erlang SDK repository, and this docs site, are a work in progress. If you are trying to use Statsig in erlang or elixir, reach out to the support team, your sales contact, or in the [Slack community](https://statsig.com/slack). {% /callout %} ## Setup the SDK {% steps %} {% step title="Install the SDK" %} Add a dependency on statsig to your: **mix.exs**: ```elixir {:statsig, "~> 0.0.1"} ``` **rebar.config**: ```erlang {statsig, "0.0.1"}. ``` **erlang.mk**: ```makefile dep_statsig = hex 0.0.1 ``` {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Do NOT embed your Server Secret Key in client-side applications, or expose it in any external-facing documents. If you accidentally expose it, you can create a new one in the Statsig console. {% /callout %} {% codetabs %} ```erlang Erlang statsig:initialize(<<"secret-key">>). % or with options Options = #{environment => #{tier => <<"staging">>}}, statsig:initialize(<<"secret-key">>, Options). ``` ```elixir Elixir Statsig.initialize("secret-key") # or with options options = %{environment: %{tier: "staging"}} Statsig.initialize("secret-key", options) ``` {% /codetabs %} `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ## Checking a Feature Flag/Gate After the SDK is initialized, you can check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (`return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example, to check a gate for a user: {% codetabs %} ```erlang Erlang User = #{<<"userID">> => <<"user-id">>}, GateValue = statsig:check_gate(User, <<"gate_name">>). ``` ```elixir Elixir user = %{"userID" => "user-id"} gate_value = Statsig.check_gate(user, "gate_name") ``` {% /codetabs %} ## Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The Dynamic Config API is similar to Feature Gates, but returns a full JSON object configured on the server, from which you can fetch typed parameters. {% codetabs %} ```erlang Erlang Config = statsig:get_config(User, <<"config_name">>), Value = statsig_config:get(Config, <<"param">>, <<"default">>). ``` ```elixir Elixir config = Statsig.get_config(user, "config_name") value = StatsigConfig.get(config, "param", "default") ``` {% /codetabs %} ## Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for faster iterations with parameter reuse. {% codetabs %} ```erlang Erlang Layer = statsig:get_layer(User, <<"layer_name">>), ParamValue = statsig_layer:get(Layer, <<"param">>, <<"default">>). ``` ```elixir Elixir layer = Statsig.get_layer(user, "layer_name") param_value = StatsigLayer.get(layer, "param", "default") ``` {% /codetabs %} ## Logging an Event To track custom events and measure how features or experiment groups affect those events, call the Log Event API. Specify the user and event name to log, and optionally provide a value and metadata object: {% codetabs %} ```erlang Erlang statsig:log_event(User, <<"event_name">>). ``` ```elixir Elixir Statsig.log_event(user, "event_name") ``` {% /codetabs %} For more about identifying users, group analytics, and best practices, go to the [logging events guide](/guides/logging-events). ## Statsig User When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks), and to correctly measure the impact of your experiments on your metrics/events. At least one identifier (userID or customID) is required to provide a consistent experience for a given user. Refer to [userID requirements](/sdks/user#why-is-an-id-always-required-for-server-sdks) for more detail. In addition to `userID`, `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion` are available as top-level fields on StatsigUser. You can also pass any key-value pairs in an object/dictionary to the `custom` field to create targeting based on them. Typing on the `StatsigUser` object is lenient: you can pass numbers, strings, arrays, objects, and even enums or classes. However, evaluation operators only work on primitive types, mostly strings and numbers. The SDK attempts to cast custom field types to match the operator, but evaluation results for other types are not guaranteed. For example, an array set as a custom field is only compared as a string: there is no operator to match a value within that array. ### Private Attributes To keep sensitive user PII data out of logs, use the `privateAttributes` field on the StatsigUser object. This field accepts an object/dictionary of private user attributes. Any attribute set in `privateAttributes` is used only for evaluation/targeting and is removed from all logs before Statsig sends them to its servers. For example, if a feature gate should only pass for users with emails ending in "@statsig.com", but you don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ## Shutdown To gracefully shutdown the SDK and ensure all events are flushed: {% codetabs %} ```erlang Erlang statsig:shutdown(). ``` ```elixir Elixir Statsig.shutdown() ``` {% /codetabs %} ================================================================================ # Legacy Java/Kotlin Server SDK URL: https://docs.statsig.com/server/java Updated: 2026-07-11 ================================================================================ {% callout type="note" %} These docs cover the Java/Kotlin SDK in a multi-user, server-side context. For client-side Android applications, go to the [Android SDK](/client/Android) or one of the other client SDKs for your client-side applications. {% /callout %} This SDK is written in Kotlin, but exposes methods and overrides to Java based applications. ## Setup the SDK {% steps %} {% step title="Install the SDK" %} `v1.X.X+` of the SDK is now published only to Maven Central. To install the SDK, set the Maven Central repository in your `build.gradle`. You probably already have this for other dependencies. ```groovy repositories { mavenCentral() } ``` Then add the dependency: ```groovy implementation 'com.statsig:serversdk:1.X.X' // replace with the most up to date version // For >v1.24.0 If you are not using streaming and want to reduce the package size you can: implementation 'com.statsig:serversdk:1.X.X' { exclude(group = "io.grpc", module = "*") } ``` You can find the versions in the github releases of the [open source sdk repository](https://github.com/statsig-io/java-server-sdk/releases), or from the [maven central repository](https://mvnrepository.com/artifact/com.statsig/serversdk). ### Jitpack deprecation `v0.X.X` versions of the SDK are available from jitpack, but newer versions won't be published to jitpack. {% callout type="note" %} If you update Statsig to be pulled from Maven Central instead of jitpack, you can remove `maven { url 'https://jitpack.io' }` if Statsig was the only library you got from jitpack and you previously relied on v0.X.X of the SDK. {% /callout %} {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Do NOT embed your Server Secret Key in client-side applications, or expose it in any external-facing documents. However, if you accidentally expose it, you can create a new one in the Statsig console. {% /callout %} {% codetabs %} ```java Java import com.statsig.sdk.Statsig; StatsigOptions options = new StatsigOptions(); // Customize options as needed. For example: // options.initTimeoutMs = 9999; Future initFuture = Statsig.initializeAsync("server-secret-key", options); initFuture.get(); ``` ```kotlin Kotlin import com.statsig.sdk.Statsig val options = StatsigOptions().apply { // Customize options as needed. For example: initTimeoutMs = 9999 } async { Statsig.initialize("server-secret-key", options) }.await() ``` {% /codetabs %} `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ## Checking a Feature Flag/Gate After the SDK is initialized, you can check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (`return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example, to check a gate for a user: {% codetabs %} ```java Java StatsigUser user = new StatsigUser("user_id"); Boolean isFeatureOn = Statsig.checkGateSync(user, "use_new_feature"); if (isFeatureOn) { // Gate is on, use new feature } else { // Gate is off } ``` ```kotlin Kotlin val user = StatsigUser("user_id"); val featureOn = Statsig.checkGateSync(user, "use_new_feature") if (featureOn) { // Gate is on, use new feature } else { // Gate is off } ``` {% /codetabs %} ## Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The Dynamic Config API is similar to Feature Gates, but returns a full JSON object configured on the server, from which you can fetch typed parameters. {% codetabs %} ```java Java DynamicConfig config = Statsig.getConfigSync(user, "awesome_product_details"); String itemName = config.getString("product_name", "Awesome Product v1"); double price = config.getDouble("price", 10.0); ``` ```kotlin Kotlin val config = Statsig.getConfigSync(user, "awesome_product_details") val itemName = config.getString("product_name", "Awesome Product v1") val price = config.getDouble("price", 10.0) ``` {% /codetabs %} ## Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for faster iterations with parameter reuse. {% codetabs %} ```java Java // Values via getLayer Layer layer = Statsig.getLayerSync(user, "user_promo_experiments"); String title = layer.getString("title", "Welcome to Statsig!"); double discount = layer.getDouble("discount", 0.1); // or, via getExperiment DynamicConfig experiment = Statsig.getExperimentSync(user, "new_user_promo"); String expTitle = experiment.getString("title", "Welcome to Statsig!"); double expDiscount = experiment.getDouble("discount", 0.1); ``` ```kotlin Kotlin // Values via getLayer val layer = Statsig.getLayerSync(user, "user_promo_experiments") val title = layer.getString("title", "Welcome to Statsig!") val discount = layer.getDouble("discount", 0.1) // or, via getExperiment val experiment = Statsig.getExperimentSync(user, "new_user_promo") val expTitle = experiment.getString("title", "Welcome to Statsig!") val expDiscount = experiment.getDouble("discount", 0.1) ``` {% /codetabs %} ## Logging an Event To track custom events and measure how features or experiment groups affect those events, call the Log Event API. Specify the user and event name to log, and optionally provide a value and metadata object: {% codetabs %} ```java Java Statsig.logEvent(user, "add_to_cart", "SKU_12345", Map.of("price", "9.99", "item_name", "diet_coke_48_pack")); ``` ```kotlin Kotlin Statsig.logEvent(user, "add_to_cart", "SKU_12345", mapOf("price" to "9.99", "item_name" to "diet_coke_48_pack")) ``` {% /codetabs %} For more about identifying users, group analytics, and best practices, go to the [logging events guide](/guides/logging-events). ## Retrieving Feature Gate Metadata When you need more than a boolean value from a gate evaluation, use the Get Feature Gate API, which returns a FeatureGate object with additional evaluation metadata: {% codetabs %} ```java Java FeatureGate gate = Statsig.getFeatureGateSync(user, "use_new_feature"); boolean value = gate.getValue(); String ruleId = gate.getRuleID(); EvaluationDetails details = gate.getEvaluationDetails(); ``` ```kotlin Kotlin val gate = Statsig.getFeatureGateSync(user, "use_new_feature") val value = gate.getValue() val ruleId = gate.getRuleID() val details = gate.getEvaluationDetails() ``` {% /codetabs %} ## Statsig User When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks), and to correctly measure the impact of your experiments on your metrics/events. At least one identifier (userID or customID) is required to provide a consistent experience for a given user. Refer to [userID requirements](/sdks/user#why-is-an-id-always-required-for-server-sdks) for more detail. In addition to `userID`, `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion` are available as top-level fields on StatsigUser. You can also pass any key-value pairs in an object/dictionary to the `custom` field to create targeting based on them. Typing on the `StatsigUser` object is lenient: you can pass numbers, strings, arrays, objects, and even enums or classes. However, evaluation operators only work on primitive types, mostly strings and numbers. The SDK attempts to cast custom field types to match the operator, but evaluation results for other types are not guaranteed. For example, an array set as a custom field is only compared as a string: there is no operator to match a value within that array. ### Private Attributes To keep sensitive user PII data out of logs, use the `privateAttributes` field on the StatsigUser object. This field accepts an object/dictionary of private user attributes. Any attribute set in `privateAttributes` is used only for evaluation/targeting and is removed from all logs before Statsig sends them to its servers. For example, if a feature gate should only pass for users with emails ending in "@statsig.com", but you don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ## Shutdown To gracefully shutdown the SDK and ensure all events are flushed: {% codetabs %} ```java Java Statsig.shutdown(); ``` ```kotlin Kotlin Statsig.shutdown() ``` {% /codetabs %} ================================================================================ # Legacy Rust Server SDK URL: https://docs.statsig.com/server/rust Updated: 2026-07-11 ================================================================================ {% callout type="warning" %} Support for the Legacy Rust SDK ends April 30, 2026. Migrate to the [new Rust SDK](/server-core/rust-core) soon. {% /callout %} ## Setup the SDK {% steps %} {% step title="Install the SDK" %} To use the SDK, add `statsig` as a dependency in your `Cargo.toml`. The latest version can be found at [crates.io/crates/statsig](https://crates.io/crates/statsig). ```toml [dependencies] statsig = "X.Y.Z" # <- update version ``` {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Do NOT embed your Server Secret Key in client-side applications, or expose it in any external-facing documents. However, if you accidentally expose it, you can create a new one in the Statsig console. {% /callout %} ```rust use statsig::{Statsig}; Statsig::initialize("secret-key").await; // or with StatsigOptions use statsig::{Statsig, StatsigOptions}; let env = HashMap::from([("tier".to_string(), "staging".to_string())]); let opts = StatsigOptions { environment: Some(env), ..StatsigOptions::default() }; Statsig::initialize_with_options("secret-key", opts).await; ``` `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ## Checking a Feature Flag/Gate After the SDK is initialized, you can check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (`return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example, to check a gate for a user: ```rust let user = StatsigUser::with_user_id("a-user".to_string()); if Statsig::check_gate(&user, "a_gate").ok().unwrap_or(false) { // Gate is on, enable new feature } else { // Gate is off } ``` ## Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The Dynamic Config API is similar to Feature Gates, but returns a full JSON object configured on the server, from which you can fetch typed parameters. ```rust let config = Statsig::get_config(&user, "a_config").ok().unwrap(); let value = config.get_string("a_key", "default_value"); ``` ## Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for faster iterations with parameter reuse. ```rust let layer = Statsig::get_layer(&user, "a_layer").ok().unwrap(); let param_value = layer.get_string("a_parameter", "default_value"); // or via get_experiment let experiment = Statsig::get_experiment(&user, "an_experiment").ok().unwrap(); let exp_value = experiment.get_string("a_parameter", "default_value"); ``` ## Logging an Event To track custom events and measure how features or experiment groups affect those events, call the Log Event API. Specify the user and event name to log, and optionally provide a value and metadata object: ```rust let event = StatsigEvent::new("event_name".to_string()); Statsig::log_event(&user, event); ``` For more about identifying users, group analytics, and best practices, go to the [logging events guide](/guides/logging-events). ## Statsig User When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks), and to correctly measure the impact of your experiments on your metrics/events. At least one identifier (userID or customID) is required to provide a consistent experience for a given user. Refer to [userID requirements](/sdks/user#why-is-an-id-always-required-for-server-sdks) for more detail. In addition to `userID`, `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion` are available as top-level fields on StatsigUser. You can also pass any key-value pairs in an object/dictionary to the `custom` field to create targeting based on them. Typing on the `StatsigUser` object is lenient: you can pass numbers, strings, arrays, objects, and even enums or classes. However, evaluation operators only work on primitive types, mostly strings and numbers. The SDK attempts to cast custom field types to match the operator, but evaluation results for other types are not guaranteed. For example, an array set as a custom field is only compared as a string: there is no operator to match a value within that array. ### Private Attributes To keep sensitive user PII data out of logs, use the `privateAttributes` field on the StatsigUser object. This field accepts an object/dictionary of private user attributes. Any attribute set in `privateAttributes` is used only for evaluation/targeting and is removed from all logs before Statsig sends them to its servers. For example, if a feature gate should only pass for users with emails ending in "@statsig.com", but you don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ## Shutdown To gracefully shutdown the SDK and ensure all events are flushed: ```rust Statsig::shutdown().await; ``` ================================================================================ # Legacy PHP Server SDK URL: https://docs.statsig.com/server/php Updated: 2026-07-11 ================================================================================ ## Setup the SDK {% steps %} {% step title="Install the SDK" %} You can install the PHP SDK using composer. ```bash composer require statsig/statsigsdk ``` The SDK is also [open source and hosted on github](https://github.com/statsig-io/php-sdk). The package is published to [packagist](https://packagist.org/packages/statsig/statsigsdk). To successfully use the PHP SDK, you need to: 1. Install it 2. Provide a storage adapter to cache config and event logs (easy default exists) 3. Schedule a Cron job to poll for config changes and flush event logs to Statsig 4. Initialize and use the SDK {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Do NOT embed your Server Secret Key in client-side applications, or expose it in any external-facing documents. However, if you accidentally expose it, you can create a new one in the Statsig console. {% /callout %} The `options` parameter requires a storage adapter for storing configurations and event logs. The example below uses a local file storage adapter, but you can write your own to connect Redis or another storage solution. Create an adapter that implements `Statsig\Adapters\IConfigAdapter` connected to your caching solution. By default, a local file solution is provided, which is useful for initial setup but is not suitable for production settings. For help with the interface and implementing an adapter, browse the [adapters directory](https://github.com/statsig-io/php-sdk/tree/main/src/Adapters) in the open source SDK repository. ```php require_once __DIR__ . '/vendor/autoload.php'; // path to installation folder use Statsig\StatsigServer; use Statsig\StatsigOptions; use Statsig\Adapters\LocalFileDataAdapter; use Statsig\Adapters\LocalFileLoggingAdapter; $config_adapter = new LocalFileDataAdapter(); $logging_adapter = new LocalFileLoggingAdapter(); $options = new StatsigOptions($config_adapter, $logging_adapter); $this->statsig = new StatsigServer("server-sdk-key", $options); ``` ### 🔥 Warning - You need to schedule a job 🔥 {% callout type="warning" %} #### V3.0+ If you don't configure a job to update the config values, the SDK doesn't fire a network request to fetch the latest config value anymore, and config values fetched earlier are used instead. {% /callout %} Refer to [Cron Jobs](#cron-jobs). {% /step %} {% /steps %} ## Working with the SDK ## Checking a Feature Flag/Gate After the SDK is initialized, you can check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (`return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example, to check a gate for a user: ```php use Statsig\StatsigUser; $user = StatsigUser::withUserID("123"); $user->setEmail("testuser@statsig.com"); $this->statsig->checkGate($user, "<gate_name>"); ``` ## Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The Dynamic Config API is similar to Feature Gates, but returns a full JSON object configured on the server, from which you can fetch typed parameters. ```php $this->statsig->getConfig($user, "<config_name>"); ``` ## Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for faster iterations with parameter reuse. ```php // Values via getLayer $layer = $this->statsig->getLayer($user, "user_promo_experiments"); $title = $layer->get("title", "Welcome to Statsig!"); $discount = $layer->get("discount", 0.1); // or, via getExperiment $title_experiment = $this->statsig->getExperiment($user, "new_user_promo_title"); $price_experiment = $this->statsig->getExperiment($user, "new_user_promo_price"); $title = $title_experiment->get("title", "Welcome to Statsig!") $discount = $price_experiment->get("discount", 0.1) ... $price = $msrp * (1 - $discount) ``` ## Logging an Event To track custom events and measure how features or experiment groups affect those events, call the Log Event API. Specify the user and event name to log, and optionally provide a value and metadata object: ```php $event = new StatsigEvent("purchase"); $event->setUser($user); $event->setValue("subscription"); $event->setMetadata(array("promotion" => "2022 deals")); $this->statsig->logEvent($event); ``` At the end of the request, you can flush events to the log file using: ```php $this->statsig->flush(); ``` For more about identifying users, group analytics, and best practices, go to the [logging events guide](/guides/logging-events). ## Cron jobs To keep your configurations up to date and send event data to Statsig, create two jobs. These are documented as cron jobs, but you can use any out-of-band process. If you are using Laravel, you can use Commands to run them locally and on a schedule. ### Sync The first job runs `sync.php` to download the latest definition of gates/configs/experiments from Statsig and save it to a local config file. If this file is not updated, gate/config/experiment values may be stale. The SDK refetches stale values during a request, which can cause slower response times. ```bash # Run once php sync.php --secret <STATSIG_SECRET_KEY> ``` ```bash # Create a cron job that runs as statsigsync every minute echo '*/1 * * * * statsigsync php /my/path/to/statsig/sync.php --secret <STATSIG_SECRET_KEY> > /dev/null' | sudo tee /etc/cron.d/statsigsync sudo service cron reload # reload the cron daemon ``` You should provide your own custom adapter that implements Statsig\Adapters\IDataAdapter ```bash php send.php --secret <STATSIG_SECRET_KEY> \ --adapter-class Namespace\For\MyConfigAdapter \ --adapter-path /path/to/MyConfigAdapter.php \ --adapter-arg an_argument_for_my_adapter \ --adapter-arg another_argument ``` By default, sync.php uses the Statsig LocalFileDataAdapter which writes to /tmp/statsig.configs. ### Send The second job runs `send.php` to send exposure data and log events to Statsig. Without this, events are logged during the lifetime of the request, which can cause slower response times. ```bash # Run once php send.php --secret <STATSIG_SECRET_KEY> ``` ```bash # Create a cron job that runs as statsigdata every minute echo '*/1 * * * * statsigdata php /my/path/to/statsig/send.php --secret <STATSIG_SECRET_KEY> > /dev/null' | sudo tee /etc/cron.d/statsigdata sudo service cron reload # reload the cron daemon ``` You should provide your own custom adapter that implements Statsig\Adapters\ILoggingAdapter ```bash php send.php --secret <STATSIG_SECRET_KEY> \ --adapter-class Namespace\For\MyLoggingAdapter \ --adapter-path /path/to/MyLoggingAdapter.php \ --adapter-arg an_argument_for_my_adapter \ --adapter-arg another_argument ``` By default, send.php uses the Statsig LocalFileDataAdapter which writes to /tmp/statsig.logs. ## Statsig User When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks), and to correctly measure the impact of your experiments on your metrics/events. At least one identifier (userID or customID) is required to provide a consistent experience for a given user. Refer to [userID requirements](/sdks/user#why-is-an-id-always-required-for-server-sdks) for more detail. In addition to `userID`, `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion` are available as top-level fields on StatsigUser. You can also pass any key-value pairs in an object/dictionary to the `custom` field to create targeting based on them. Typing on the `StatsigUser` object is lenient: you can pass numbers, strings, arrays, objects, and even enums or classes. However, evaluation operators only work on primitive types, mostly strings and numbers. The SDK attempts to cast custom field types to match the operator, but evaluation results for other types are not guaranteed. For example, an array set as a custom field is only compared as a string: there is no operator to match a value within that array. ### Private Attributes To keep sensitive user PII data out of logs, use the `privateAttributes` field on the StatsigUser object. This field accepts an object/dictionary of private user attributes. Any attribute set in `privateAttributes` is used only for evaluation/targeting and is removed from all logs before Statsig sends them to its servers. For example, if a feature gate should only pass for users with emails ending in "@statsig.com", but you don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ================================================================================ # Legacy .NET Server SDK URL: https://docs.statsig.com/server/dotnet Updated: 2026-07-11 ================================================================================ ## Setup the SDK {% steps %} {% step title="Install the SDK" %} The package is hosted on [Nuget](https://www.nuget.org/packages/Statsig/). Install it through Visual Studio's Nuget package manager or through the NuGet CLI: ```bash nuget install Statsig ``` {% /step %} {% step title="Initialize the SDK" %} After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Do NOT embed your Server Secret Key in client-side applications, or expose it in any external-facing documents. If you accidentally expose it, you can create a new one in the Statsig console. {% /callout %} ```csharp using Statsig; using Statsig.Server; await StatsigServer.Initialize( "server-secret-key", // optionally customize the SDKs configuration via StatsigOptions new StatsigOptions( environment: new StatsigEnvironment(EnvironmentTier.Development) ) ); ``` `initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background, independently of API calls. {% /step %} {% /steps %} ## Working with the SDK ## Checking a Feature Flag/Gate After the SDK is initialized, you can check a [**Feature Gate**](/feature-flags/overview). Feature Gates create logic branches in code that can be rolled out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (`return false;`) by default. All APIs require you to specify the user (refer to [Statsig user](#statsig-user)) associated with the request. For example, to check a gate for a user: ```csharp var user = new StatsigUser { UserID = "some_user_id", Email = "user@email.com" }; var useNewFeature = await StatsigServer.CheckGate(user, "use_new_feature"); if (useNewFeature) { // Gate is on, enable new feature } else { // Gate is off } ``` ## Reading a Dynamic Config Feature Gates work well for simple on/off switches with optional user targeting. To send a different set of values (strings, numbers, and so on) to clients based on specific user attributes such as country, use [**Dynamic Configs**](/dynamic-config/overview). The Dynamic Config API is similar to Feature Gates, but returns a full JSON object configured on the server, from which you can fetch typed parameters. ```csharp var config = await StatsigServer.GetConfig(user, "awesome_product_details"); var itemName = config.Get<string>("product_name", "Awesome Product v1"); var price = config.Get<double>("price", 10.0); ``` ## Getting a Layer/Experiment Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available, but Statsig recommends [layers](/experiments/layers-overview) for faster iterations with parameter reuse. ```csharp // Values via GetLayer var layer = await StatsigServer.GetLayer(user, "user_promo_experiments"); var title = layer.Get<string>("title", "Welcome to Statsig!"); var discount = layer.Get<double>("discount", 0.1); // or, via GetExperiment var experiment = await StatsigServer.GetExperiment(user, "new_user_promo"); var expTitle = experiment.Get<string>("title", "Welcome to Statsig!"); var expDiscount = experiment.Get<double>("discount", 0.1); var price = msrp * (1 - discount); ``` ## Logging an Event To track custom events and measure how features or experiment groups affect those events, call the Log Event API. Specify the user and event name to log, and optionally provide a value and metadata object: ```csharp StatsigServer.LogEvent(user, "add_to_cart", "SKU_12345", new Dictionary<string, string> { { "price", "9.99" }, { "item_name", "diet_coke_48_pack" } }); ``` For more about identifying users, group analytics, and best practices, go to the [logging events guide](/guides/logging-events). ## Statsig User When calling APIs that require a user, pass as much information as possible to take advantage of advanced gate and config conditions (like country or OS/browser level checks), and to correctly measure the impact of your experiments on your metrics/events. At least one identifier (userID or customID) is required to provide a consistent experience for a given user. Refer to [userID requirements](/sdks/user#why-is-an-id-always-required-for-server-sdks) for more detail. In addition to `userID`, `email`, `ip`, `userAgent`, `country`, `locale`, and `appVersion` are available as top-level fields on StatsigUser. You can also pass any key-value pairs in an object/dictionary to the `custom` field to create targeting based on them. Typing on the `StatsigUser` object is lenient: you can pass numbers, strings, arrays, objects, and even enums or classes. However, evaluation operators only work on primitive types, mostly strings and numbers. The SDK attempts to cast custom field types to match the operator, but evaluation results for other types are not guaranteed. For example, an array set as a custom field is only compared as a string: there is no operator to match a value within that array. ### Private Attributes To keep sensitive user PII data out of logs, use the `privateAttributes` field on the StatsigUser object. This field accepts an object/dictionary of private user attributes. Any attribute set in `privateAttributes` is used only for evaluation/targeting and is removed from all logs before Statsig sends them to its servers. For example, if a feature gate should only pass for users with emails ending in "@statsig.com", but you don't want to log email addresses to Statsig, add the key-value pair `{ email: "my_user@statsig.com" }` to `privateAttributes` on the user. ## Shutdown To gracefully shutdown the SDK and ensure all events are flushed: ```csharp await StatsigServer.Shutdown(); ``` ================================================================================ # Node AI SDK URL: https://docs.statsig.com/ai-evals/node Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Statsig isn't accepting new customers for the AI SDKs at this time. {% /callout %} ## How the Node AI SDK works The Statsig Node AI SDK lets you manage prompts, online and offline evals, and debug LLM applications in production. The SDK depends on the [Statsig Node Server SDK](/server-core/node-core) and provides hooks for AI-specific functionality. {% steps %} {% step title="Install the SDK" %} {% codetabs %} ```js npm npm install @statsig/statsig-ai ``` ```js pnpm pnpm add @statsig/statsig-ai ``` ```js yarn yarn add @statsig/statsig-ai ``` {% /codetabs %} For unique setup needs such as a frozen lockfile, refer to the [Node Server SDK docs](/server-core/node-core#installation). The AI SDK installs the Node Server SDK if you don't already have it. {% /step %} {% step title="Initialize the SDK" %} If you already have a Statsig instance, you can pass it into the SDK. Otherwise, the SDK creates an instance internally. {% tabs %} {% tab title="Don't use Statsig" %} Initialize the AI SDK with a Server Secret Key from the Statsig console. {% callout type="warning" %} Server Secret Keys should always be kept private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} ```js import { StatsigAI } from '@statsig/statsig-ai'; const statsigAI = new StatsigAI({'YOUR_SERVER_SECRET_KEY'}); await statsigAI.initialize(); ``` {% accordion title="Initializing With Options" %} Optionally, you can configure [StatsigOptions](/server-core/node-core#statsig-options) for your Statsig instance: ```js import { StatsigAI, StatsigAIOptions } from '@statsig/statsig-ai'; import { StatsigOptions } from '@statsig/statsig-server-core-node'; // if you want to configure any statsig options, this is optional: const statsigOptions: StatsigOptions = { environment: 'production', }; const statsigAI = new StatsigAI({'YOUR_SERVER_SECRET_KEY', statsigOptions}); await statsigAI.initialize(); // if you would like to use any statsig methods, you can access the statsig instance from the statsigAI instance: const gate = statsigAI.getStatsig().checkGate(statsigUser, 'my_gate'); ``` {% /accordion %} {% /tab %} {% tab title="Already have Statsig instance" %} After installation, initialize the SDK with a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Server Secret Keys should always be kept private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} If you initialize this way, the AI SDK won’t handle initialization, flushing, or shutdown. ```js import { Statsig } from '@statsig/statsig-server-core-node'; import { StatsigAI } from '@statsig/statsig-ai'; const statsig = new Statsig('YOUR_SERVER_SECRET_KEY'); await statsig.initialize(); const statsigAI = new StatsigAI({statsig}); await statsigAI.initialize(); ``` {% accordion title="Initializing With Options" %} Optionally, you can configure [StatsigOptions](/server-core/node-core#statsig-options): ```js import { Statsig } from '@statsig/statsig-server-core-node'; import { StatsigAI, StatsigAIOptions } from '@statsig/statsig-ai'; const statsig = new Statsig('YOUR_SERVER_SECRET_KEY', { environment: 'production', }); await statsig.initialize(); await statsigAI.initialize(); ``` {% /accordion %} {% /tab %} {% /tabs %} {% /step %} {% /steps %} ## Using the SDK ### Getting a prompt Statsig can act as the control plane for your LLM prompts, allowing you to version and change them without deploying code. For more information, refer to the [Prompts](/ai-evals/prompts) documentation. ```js import { StatsigUser } from '@statsig/statsig-ai'; // Create a user object const user = new StatsigUser({ userID: 'a-user' }); // Get the prompt const myPrompt = statsigAI.getPrompt(user, 'my_prompt'); // Use the live version of the prompt const liveVersion = myPrompt.getLive(); // Get the candidate versions of the prompt const candidateVersions = myPrompt.getCandidates(); // Use the live version of the prompt in a completion const response = await openai.chat.completions.create({ model: liveVersion.getModel({ fallback: 'gpt-4' }), // optional fallback temperature: liveVersion.getTemperature(), max_tokens: liveVersion.getMaxTokens(), messages: [{ role: 'user', content: 'Your prompt here' }], }); ``` ### Logging eval results When running an [online eval](/ai-evals/online-evals), you can log results back to Statsig for analysis. Provide a score between 0 and 1, along with the grader name and any useful metadata (such as session IDs). Currently, you must provide the grader manually. Future releases will support automated grading options. ```js import { StatsigUser } from '@statsig/statsig-ai'; const livePromptVersion = statsigAI.getPrompt(user, 'my_prompt').getLive(); // Create a user object const user = new StatsigUser({ userID: 'a-user' }); // Log the results of the eval statsigAI.logEvalGrade(user, livePromptVersion, 0.5, 'my_grader', { session_id: '1234567890', }); // flush eval grade events to statsig await statsigAI.flush(); ``` ### Programmatic evaluation Programmatic evaluation allows you to run evaluations on datasets programmatically, automatically scoring outputs and sending results to Statsig for analysis. With programmatic evaluation, you can: * **Run evaluations on datasets**: Process arrays, iterators, or async generators of input/expected pairs * **Define custom tasks**: Create functions that generate outputs from inputs (supports both sync and async) * **Score outputs**: Use single or multiple named scorer functions to evaluate outputs (supports boolean, numeric, or metadata-rich scores) * **Use parameters**: Pass dynamic parameters to tasks using Zod schemas (Node) or dictionaries (Python) * **Categorize data**: Group evaluation records by categories for better analysis * **Compute summary scores**: Aggregate results across all records with custom summary functions * **Handle errors gracefully**: Task and scorer errors are caught and reported without stopping the evaluation The evaluation automatically sends results to Statsig, where you can view them in the console alongside your other eval data. {% callout type="note" %} Tasks and scorers can be async functions. Data can also be provided as async functions, promises, or async iterators. The `expected` field in data records is optional; scorers can evaluate outputs without expected values. Task and scorer errors are automatically caught and reported in the results. {% /callout %} ```js import { Eval } from '@statsig/statsig-ai'; import { z } from 'zod'; // Basic evaluation with a single scorer const result = await Eval('greeting_task', { data: [ { input: 'world', expected: 'Hello world' }, { input: 'test', expected: 'Hello test' }, ], task: (input: string) => `Hello ${input}`, scorer: ({ output, expected }) => output === expected, evalRunName: 'run-123', }); // Multiple named scorers const result2 = await Eval('multi_scorer_task', { data: [ { input: 'world', expected: 'Hello world' }, { input: 'test', expected: 'Hello test' }, ], task: (input: string) => `Hello ${input}`, scorer: { correctness: ({ output, expected }) => output === expected, startsWithHello: ({ output }) => output.startsWith('Hello'), lengthCheck: ({ output }) => output.length > 5, }, }); // Using parameters with Zod schemas const result3 = await Eval('parameterized_task', { data: [ { input: 'world', expected: 'Hi world' }, ], task: (input: string, hooks) => { const prefix = hooks.parameters.name || 'Hello'; return `$\{prefix\} ${input}`; }, scorer: ({ output, expected }) => output === expected, parameters: { name: z.string().default('Hi'), }, }); // Extras: Categories and summary scores const result4 = await Eval('categorized_with_summary', { data: [ { input: 'world', expected: 'Hello world', category: 'greeting' }, { input: 'test', expected: 'Hello test', category: ['greeting', 'test'] }, { input: 'foo', expected: 'Goodbye foo', category: 'farewell' }, ], task: (input: string) => `Hello ${input}`, scorer: { correctness: ({ output, expected }) => output === expected, }, summaryScoresFn: (results) => { const correct = results.filter(r => r.scores.correctness === 1).length; return { accuracy: correct / results.length, total: results.length, }; }, }); ``` ### OpenTelemetry (OTEL) The AI SDK works with OpenTelemetry for sending telemetry to Statsig. Enable OTel tracing by calling the `initializeTracing` function. You can also provide a custom `TracerProvider` to `initializeTracing` to customize the tracing behavior. More advanced OTel configuration and exporter support are coming soon. To start tracing with Statsig and OTel, call `initializeTracing()` at the root of your application. ```js // instrumentation.{js,ts} import { initializeTracing } from '@statsig/statsig-ai/otel'; initializeTracing({ // optional: enables the global trace provider registration // so that you can create spans without having to create a new trace provider enableGlobalTraceProviderRegistration: true, }); ``` If you already have your own OTel setup with `NodeSDK`, you only need to initialize Statsig's OTel tracing and use the processor created by `initializeTracing()`. ```js // instrumentation.{js,ts} import { NodeSDK } from '@opentelemetry/sdk-node'; import { PeriodicExportingMetricReader, ConsoleMetricExporter, } from '@opentelemetry/sdk-metrics'; import { initializeTracing } from '@statsig/statsig-ai/otel'; // when you have your own otel setup and don't want to use the global trace provider // you can disable it with the options below const { processor } = initializeTracing({ // prevents creating a global context manager skipGlobalContextManagerSetup: true, exporterOptions: { sdkKey: process.env.STATSIG_SDK_KEY!, }, }); const sdk = new NodeSDK({ // IMPORTANT: use the processor created by initializeTracing // to make sure that spans are exported to Statsig spanProcessors: [processor], metricReader: new PeriodicExportingMetricReader({ exporter: new ConsoleMetricExporter(), }), // ... other node sdk options like autoInstrumentations }); sdk.start(); export { sdk }; ``` The `initializeOTel` function accepts the below options for setting up tracing with OTel. ```ts type InitializeOptions = { /** An optional global context manager to use. If not provided, one will be created and set as the global context manager unless `skipGlobalContextManagerSetup` is true. */ globalContextManager?: ContextManager; /** If true, will not attempt to set up a global context manager automatically. */ skipGlobalContextManagerSetup?: boolean; /** If true, will register the trace provider globally. */ enableGlobalTraceProviderRegistration?: boolean; /** An optional global trace provider to use. If not provided, a new BasicTracerProvider will be created and optionally registered globally */ globalTraceProvider?: TracerProvider; /** Options to pass to the StatsigOTLPTraceExporter */ exporterOptions?: StatsigOTLPTraceExporterOptions; // resource options serviceName?: string; version?: string; environment?: string; }; ``` For more examples, refer to the [Statsig AI Node SDK](https://github.com/statsig-io/statsig-ai-node/tree/main/examples/otel). ### Wrapping OpenAI The Statsig OpenAI wrapper automatically adds tracing and log events to your OpenAI SDK usage, giving you console visibility with minimal setup. ```js import { wrapOpenAI, StatsigAI } from '@statsig/statsig-ai'; import { OpenAI } from 'openai'; // if you have your own otel, you do not need an statsigAI instance here. // But if you want to use the default Otel on statsigAI, you need to initialize the SDK. statsigAI = new StatsigAI({"YOUR_SERVER_SECRET_KEY"}); await statsigAI.initialize(); const client = wrapOpenAI( new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }) ); const response = await client.chat.completions.create({ model: "gpt-4", messages: [{ role: "user", content: "Hello, world!" }], }); ``` ## Using other SDK methods You can access the Statsig instance from the statsigAI instance regardless of how you initialized it and use its methods: ```javascript // Check a gate value const gate = statsigAI.getStatsig().checkGate(statsigUser, 'my_gate'); // Log an event statsigAI.getStatsig().logEvent(statsigUser, 'my_event', { value: 1 }); ``` Go to the [Statsig Node SDK](/server-core/node-core) docs for more information on Core Statsig SDK methods, advanced setup, and singleton usage. ================================================================================ # Python AI SDK URL: https://docs.statsig.com/ai-evals/python Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Statsig isn't accepting new customers for the AI SDKs at this time. {% /callout %} ## How the Python AI SDK works The Statsig Python AI SDK lets you manage prompts, online and offline evals, and debug LLM applications in production. It depends on the [Statsig Python Server SDK](/server-core/python-core) and provides hooks for AI-specific functionality. {% steps %} {% step title="Install the SDK" %} {% codetabs %} ```python pip pip install statsig-ai ``` ```python poetry poetry add statsig-ai ``` ```python pipenv pipenv install statsig-ai ``` {% /codetabs %} {% /step %} {% step title="Initialize the SDK" %} {% callout type="tip" %} For initialization requirements in forking and WSGI servers, refer to the [Statsig Python Server SDK](/server-core/python-core) docs. {% /callout %} If you already have a Statsig instance, you can pass it into the SDK. Otherwise, the SDK creates an instance internally. {% tabs %} {% tab title="Don't use Statsig" %} Initialize the AI SDK with a Server Secret Key from the Statsig console. {% callout type="warning" %} Server Secret Keys should always be kept private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} ```python from statsig_ai import StatsigAI, StatsigCreateConfig statsig_ai = StatsigAI(statsig_source=StatsigCreateConfig(server_secret_key='YOUR_SERVER_SECRET_KEY')) statsig_ai.initialize(). ``` {% accordion title="Initializing With Options" %} Optionally, you can configure [StatsigOptions](/server-core/python-core#statsig-options) for your Statsig instance: ```python from statsig_ai import StatsigAI from statsig_python_core import StatsigOptions # if you want to configure any statsig options, this is optional: statsig_options = StatsigOptions() statsig_options.environment = 'production' statsig_ai_options.statsig_options = statsig_options statsig_ai = StatsigAI(statsig_source=StatsigCreateConfig(server_secret_key='YOUR_SERVER_SECRET_KEY', statsig_options=statsig_options)) statsig_ai.initialize() # if you would like to use any statsig methods, you can access the statsig instance from the statsig_ai instance: gate = statsig_ai.get_statsig().check_gate(statsig_user, 'my_gate') ``` {% /accordion %} {% /tab %} {% tab title="Already have Statsig instance" %} After installation, initialize the SDK with a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys). {% callout type="warning" %} Server Secret Keys should always be kept private. If you expose one, you can disable and recreate it in the Statsig console. {% /callout %} If you initialize this way, the AI SDK won’t handle initialization, flushing, or shutdown. ```python from statsig_python_core import Statsig from statsig_ai import StatsigAI, StatsigAttachConfig statsig = Statsig('YOUR_SERVER_SECRET_KEY') statsig.initialize() statsig_ai = StatsigAI(statsig_source=StatsigAttachConfig(statsig=statsig)) statsig_ai.initialize() ``` {% accordion title="Initializing With Options" %} Optionally, you can configure [StatsigOptions](/server-core/python-core#statsig-options): ```python from statsig_python_core import Statsig, StatsigOptions from statsig_ai import StatsigAI, StatsigAttachConfig options = StatsigOptions() options.environment = 'production' statsig = Statsig('YOUR_SERVER_SECRET_KEY', options) statsig.initialize() statsig_ai = StatsigAI(statsig_source=StatsigAttachConfig(statsig=statsig)) statsig_ai.initialize() ``` {% /accordion %} {% /tab %} {% /tabs %} {% /step %} {% /steps %} ## Using the SDK ### Getting a prompt Statsig can act as the control plane for your LLM prompts, allowing you to version and change them without deploying code. For more information, refer to the [Prompts](/ai-evals/prompts) documentation. ```python from statsig_ai import StatsigUser # Create a user object user = StatsigUser(user_id='a-user') # Get the prompt my_prompt = statsig_ai.get_prompt(user, 'my_prompt') # Use the live version of the prompt live_version = my_prompt.get_live() # Get the candidate versions of the prompt candidate_versions = my_prompt.get_candidates() # Use the live version of the prompt in a completion response = openai.chat.completions.create( model=live_version.get_model(fallback='gpt-4'), # optional fallback temperature=live_version.get_temperature(), max_tokens=live_version.get_max_tokens(), messages=[{'role': 'user', 'content': 'Your prompt here'}], ) ``` ### Logging eval results When running an [online eval](/ai-evals/online-evals), you can log results back to Statsig for analysis. Provide a score between 0 and 1, along with the grader name and any useful metadata (such as session IDs). Currently, you must provide the grader manually. Future releases will support automated grading options. ```python from statsig_ai import StatsigUser live_prompt_version = statsig_ai.get_prompt(user, 'my_prompt').get_live() # Create a user object user = StatsigUser(user_id='a-user') # Log the results of the eval statsig_ai.log_eval_grade(user, live_prompt_version, 0.5, 'my_grader', { 'session_id': '1234567890', }) # flush eval grade events to statsig statsig_ai.flush().wait() ``` ### Programmatic evaluation Programmatic evaluation allows you to run evaluations on datasets programmatically, automatically scoring outputs and sending results to Statsig for analysis. With programmatic evaluation, you can: * **Run evaluations on datasets**: Process arrays, iterators, or async generators of input/expected pairs * **Define custom tasks**: Create functions that generate outputs from inputs (supports both sync and async) * **Score outputs**: Use single or multiple named scorer functions to evaluate outputs (supports boolean, numeric, or metadata-rich scores) * **Use parameters**: Pass dynamic parameters to tasks using Zod schemas (Node) or dictionaries (Python) * **Categorize data**: Group evaluation records by categories for better analysis * **Compute summary scores**: Aggregate results across all records with custom summary functions * **Handle errors gracefully**: Task and scorer errors are caught and reported without stopping the evaluation The evaluation automatically sends results to Statsig, where you can view them in the console alongside your other eval data. {% callout type="note" %} Tasks and scorers can be async functions. Data can also be provided as async functions, promises, or async iterators. The `expected` field in data records is optional; scorers can evaluate outputs without expected values. Task and scorer errors are automatically caught and reported in the results. {% /callout %} ```python from statsig_ai import Eval, EvalScorerArgs, EvalDataRecord, EvalHook # Basic evaluation with a single scorer result = Eval( name='greeting_task', data=[ {'input': 'world', 'expected': 'Hello world'}, {'input': 'test', 'expected': 'Hello test'}, ], task=lambda input: f'Hello {input}', scorer=lambda args: args.output == args.expected, eval_run_name='run-123', ) # Multiple named scorers result2 = Eval( name='multi_scorer_task', data=[ {'input': 'world', 'expected': 'Hello world'}, {'input': 'test', 'expected': 'Hello test'}, ], task=lambda input: f'Hello {input}', scorer={ 'correctness': lambda args: args.output == args.expected, 'starts_with_hello': lambda args: args.output.startswith('Hello'), 'length_check': lambda args: len(args.output) > 5, }, ) # Using parameters def task_with_params(input: str, hook: EvalHook) -> str: prefix = hook.parameters.get('prefix', 'Hello') return f'{prefix} {input}' result3 = Eval( name='parameterized_task', data=[ {'input': 'world', 'expected': 'Hi world'}, ], task=task_with_params, scorer=lambda args: args.output == args.expected, parameters={'prefix': 'Hi', 'suffix': '!', 'number': 123}, ) # Extras: Categories and summary scores def summary_scorer(results): correct = sum(1 for r in results if r.scores.get('correctness', 0.0) == 1.0) return { 'accuracy': correct / len(results) if results else 0.0, 'total': len(results), } result4 = Eval( name='categorized_with_summary', data=[ {'input': 'world', 'expected': 'Hello world', 'category': 'greeting'}, {'input': 'test', 'expected': 'Hello test', 'category': ['greeting', 'test']}, {'input': 'foo', 'expected': 'Goodbye foo', 'category': 'farewell'}, ], task=lambda input: f'Hello {input}', scorer={ 'correctness': lambda args: args.output == args.expected, }, summary_score_fn=summary_scorer, ) # Using EvalDataRecord dataclass result5 = Eval( name='dataclass_records', data=[ EvalDataRecord(input='world', expected='Hello world'), EvalDataRecord(input='test', expected='Hello test'), ], task=lambda input: f'Hello {input}', scorer=lambda args: args.output == args.expected, ) ``` ### OpenTelemetry (OTEL) OTel tracing isn't yet supported in the Python AI SDK. Support is coming soon. ### Wrapping OpenAI The OpenAI wrapper isn't yet supported in the Python AI SDK. Support is coming soon. ## Using other SDK methods You can access the Statsig instance from the statsig\_ai instance regardless of how you initialized it, and use its methods: ```python # Check a gate value gate = statsig_ai.get_statsig().check_gate(statsig_user, 'my_gate') # Log an event statsig_ai.get_statsig().log_event(statsig_user, 'my_event', value=1) ``` Refer to the [Statsig Python SDK](/server-core/python-core) docs for more information on Core Statsig SDK methods, advanced setup, and singleton usage. ================================================================================ # Client Persistent Assignment URL: https://docs.statsig.com/client/concepts/persistent_assignment Updated: 2026-07-11 ================================================================================ Persistent assignment ensures that a user's variant stays consistent while an experiment is running, regardless of changes to allocation or targeting. ## Persistent Storage Persistent storage uses an adapter approach, allowing you to plug in a storage solution of your choice to store assignments. The SDK references stored assignments later to ensure a user stays in the same bucket. You can implement an adapter that uses Local Storage, or one that uses remote storage, to enable persistence across multiple devices. The user persistent storage interface consists of a `load`/`loadAsync`, `save`, and `delete` API for read/write operations. {% callout type="info" %} Persistent Storage is supported on: * The modern [`Javascript,`](/client/javascript-sdk) [`React`](/client/React), and [`React Native`](/client/ReactNative) SDKs, including on-device evaluation * [`Android, on-device evaluation`](/client/androidOnDeviceEvaluationSDK) only * [`iOS, on-device evaluation`](/client/swiftOnDeviceEvaluationSDK) only * Go to [Support in iOS and Android SDKs](#support-in-ios-and-android-sdks) for Android and iOS details {% /callout %} ### Persistent Storage Logic * Providing a storage adapter on Statsig initialization gives the SDK access to read & write on your custom storage. * Providing user persisted values to `get_experiment` informs the SDK to * **save** the evaluation of the current user **on first evaluation** * **load** the previously saved evaluation of a persisted user **on subsequent evaluations** * **delete** the previously saved evaluation of a persisted user if the experiment is no longer active * Not providing user persisted values to `get_experiment` **deletes** a previously saved evaluation. ### Example usage {% tabs %} {% tab title="Javascript" %} ```typescript import { StatsigClient } from '@statsig/js-client'; import { UserPersistentOverrideAdapter } from '@statsig/js-user-persisted-storage'; // Custom storage implementation using localStorage class LocalStorageUserPersistedStorage { load(key) { return JSON.parse(localStorage.getItem(key) ?? '{}'); } save(key, experiment, data) { const values = JSON.parse(localStorage.getItem(key) ?? '{}'); values[experiment] = JSON.parse(data); localStorage.setItem(key, JSON.stringify(values)); } delete(key, experiment) { const data = JSON.parse(localStorage.getItem(key) ?? '{}'); delete data[experiment]; localStorage.setItem(key, JSON.stringify(data)); } } const storage = new LocalStorageUserPersistedStorage(); const adapter = new UserPersistentOverrideAdapter(storage); const client = new StatsigClient('client-key', { overrideAdapter: adapter }); await client.initializeAsync({ userID: "123" }); const user = { userID: "123" }; const userPersistedValues = adapter.loadUserPersistedValues(user, 'userID'); const experiment = client.getExperiment('active_experiment', { userPersistedValues }); console.log(experiment.getGroupName()); // 'Control' // Switch to different user - will maintain same experiment group due to persistence const newUser = { userID: "456" }; const newExperiment = client.getExperiment('active_experiment', { userPersistedValues }); console.log(newExperiment.getGroupName()); // Still 'Control' ``` {% /tab %} {% tab title="React" %} The syntax for React matches vanilla JavaScript. For a full implementation example, refer to the [Persistent Storage Example](https://github.com/statsig-io/js-client-monorepo/tree/main/samples/next-js/src/app/persisted-user-storage-example) in Next.js. {% /tab %} {% tab title="Android On-Device Eval" %} #### Synchronous Persistent Evaluations The `UserPersistentStorageInterface` exposes two methods for synchronous persistent storage, which the SDK calls by default when evaluating an experiment. ``` interface UserPersistentStorageInterface { suspend fun load(key: String): PersistedValues fun save(key: String, experimentName: String, data: String) fun delete(key: String, experiment: String) ... } ``` The `key` string is a combination of ID and ID Type: for example, "123:userID" or "abc:stableID". The SDK constructs this key and calls `get` and `set` on it by default. You can use this interface to persist evaluations synchronously to local storage. If you need an async interface, refer to the section below. #### Asynchronous Persistent Evaluations The `UserPersistentStorageInterface` exposes two methods for asynchronous persistent evaluations. Because the `getExperiment` call is synchronous, load the value first and pass it in as `userPersistedValues`. ```kotlin interface UserPersistentStorageInterface { fun loadAsync(key: String, callback: IPersistentStorageCallback) fun save(key: String, experimentName: String, data: String) fun delete(key: String, experiment: String) ... } interface IPersistentStorageCallback { fun onLoaded(values: PersistedValues) } ``` A top-level method loads the value for a given user and ID type: ```kotlin // Asynchronous load values val userPersistedValues = Statsig.client.loadUserPersistedValuesAsync( user: StatsigUser, idType: string, // userID, stableID, customIDxyz, etc callback: IPersistentStorageCallback ); // Synchronous load values val userPersistedvalues = Statsig.client.loadUserPersistedValues( user: StatsigUser, idType: string, // userID, stableID, customIDxyz, etc ) ``` After you implement `UserPersistentStorageInterface` and set it on `StatsigOptions`, the call site looks like this: ```kotlin // Asynchronous val callback = object: IPersistentStorageCallback { @override fun onLoaded(values: PersistedValues) { Statsig.getExperiment(user, "sample_experiment", GetExperimentOptions(userPersistedValues = values)) } } val userValues = Statsig.client.loadUserPersistedValuesAsync(user, "userID", callback) // Synchronous val user = StatsigUser(userID = "user123") val userValues = Statsig.client.loadUserPersistedValues(user, 'userID'); const experiment = statsig.getExperiment({userID: "123"}, 'the_allocated_experiment', { userPersistedValues: userValues }); ``` If you use Java, you can only override the loadAsync function and leave the load function empty. {% /tab %} {% tab title="JS On-Device Eval" %} ```typescript const storage = new CustomStorageAdapter(); // Need to implement const adapter = new UserPersistentOverrideAdapter(storage); const client = new StatsigOnDeviceEvalClient( 'client-key', { overrideAdapter: adapter } ); await client.initializeAsync(); const userInControl = { userID: "123" } const userInUnknown = { userID: "unknown" } const userPersistedValues = adapter.loadUserPersistedValues(user, 'userID'); let experiment = client.getExperiment('active_experiment', user, { userPersistedValues }); console.log(experiment.getGroupName()) // 'Control' experiment = client.getExperiment('active_experiment', userInUnknown, { userPersistedValues }); console.log(experiment.getGroupName()) // 'Control' ``` For a full implementation example, refer to the [Persistent Storage On-Device Eval Example](https://github.com/statsig-io/js-client-monorepo/tree/main/samples/next-js/src/app/persisted-user-storage-example-on-device) in Next.js. {% /tab %} {% tab title="JS (legacy)" %} ```typescript await statsig.initialize( 'client-key', { userPersistentStorage: new CustomStorageAdapter() } // Need to implement ); const userInControl = { userID: "123" } const userInUnknown = { userID: "unknown" } const userPersistedValues = await statsig.loadUserPersistedValuesAsync(userInControl, 'userID'); let experiment = statsig.getExperiment(userInControl, 'active_experiment', { userPersistedValues }); console.log(experiment.getGroupName()) // 'Control' experiment = statsig.getExperiment(userInUnknown, 'active_experiment', { userPersistedValues }); console.log(experiment.getGroupName()) // 'Control' ``` {% /tab %} {% /tabs %} ## Support in iOS and Android SDKs Android and iOS SDKs offer a simplified version of Persistent Storage called `keepDeviceValues` that relies on on-device storage. This option is less flexible but requires only a single boolean flag. When enabled, the SDK checks internally for a previously stored value in the `getExperiment`/`getLayer` call and uses that value instead, even if allocation or targeting has changed. When the experiment ends, the SDK stops persisting values. {% tabs %} {% tab title="iOS" %} #### Swift: ```swift // With an Experiment: let titleExperiment = Statsig.getExperiment("new_user_promo_title", true) // <-- "true" flag sets keep device values // Use the experiment like normal: let promoTitle = titleExperiment.getValue(forKey: "title", defaultValue: "Welcome to Statsig!") // Or a Layer: let layer = Statsig.getLayer("user_promo_experiments", true) // <-- "true" flag sets keep device values // Use the layer like normal: let promoTitle = layer.getValue(forKey: "title", defaultValue: "Welcome to Statsig!") ``` #### Objective C: ```objc // With an Experiment: DynamicConfig *expConfig = [Statsig getExperimentForName:@"new_user_promo_title" true]; // <-- "true" flag sets keep device values // Use the experiment like normal: NSString *promoTitle = [expConfig getStringForKey:@"title" defaultValue:@"Welcome to Statsig! Use discount code WELCOME10OFF for 10% off your first purchase!"]; double discount = [expConfig getDoubleForKey:@"discount" defaultValue:0.1]; double price = msrp * (1 - discount); ``` {% /tab %} {% tab title="Android" %} #### Java: ```Java // With an Experiment: DynamicConfig titleExperiment = Statsig.getExperiment("new_user_promo_title", true); // <-- "true" flag sets keep device values // Use the experiment like normal: String promoTitle = titleExperiment.getString("title", "Welcome to Statsig!"); // Or a Layer: Layer layer = Statsig.getLayer("user_promo_experiments", true) // <-- "true" flag sets keep device values // Use the layer like normal: String promoTitle = layer.getString("title", "Welcome to Statsig!"); ``` #### Kotlin: ```kotlin // With an Experiment: val titleExperiment = Statsig.getExperiment("new_user_promo_title", true) // <-- "true" flag sets keep device values // Use the experiment like normal: val promoTitle = titleExperiment.getString("title", "Welcome to Statsig!") // Or a Layer: val layer = Statsig.getLayer("user_promo_experiments", true) // <-- "true" flag sets keep device values // Use the layer like normal: val promoTitle = layer.getString("title", "Welcome to Statsig!") ``` {% /tab %} {% /tabs %} ================================================================================ # Server Persistent Assignment URL: https://docs.statsig.com/server/concepts/persistent_assignment Updated: 2026-07-11 ================================================================================ Persistent assignment lets you ensure that a user's variant stays consistent while an experiment is running, regardless of changes to allocation or targeting. ## Persistent storage adapter The persistent storage adapter lets you plug in your own storage solution that the Statsig SDK uses to persist user assignments. The storage interface provides a `load` and `save` API for read/write operations. {% callout type="info" %} Currently only supported in `Go`, `Ruby`, `Legacy Node`, `Node Core`, `Java Core`, `Kotlin`, `.Net`, `Python Core`, `PHP Core`, `Rust Core` {% /callout %} ### Persistent storage logic - Providing a storage adapter at Statsig initialization gives the SDK access to read and write to your custom storage. - Providing user persisted values to `get_experiment` tells the SDK to: - **save** the current user's evaluation **on first evaluation** (only when the experiment or layer **is active**) - **load** the previously saved evaluation **on subsequent evaluations** - Statsig deletes persisted values when: - You call `getExperiment` with `user_persisted_values=None` - The experiment isn't active ### Persistent assignment options (limited SDK support) - **Enforce Targeting**: `boolean`, default: `false` - Whether or not to enforce targeting rules before assigning persisted values {% tabs %} {% tab title="Kotlin" %} ```kotlin val options = GetExperimentOptions( ... persistentAssignmentOptions = PersistentAssignmentOptions( enforceTargeting = true, ) ) ``` {% /tab %} {% tab title="Node JS" %} ```ts const options: GetExperimentOptions = { ... persistentAssignmentOptions: { enforceTargeting: true, } } ``` {% /tab %} {% /tabs %} ### Example usage {% tabs %} {% tab title="Ruby" %} ```ruby Statsig.initialize( 'secret-key', StatsigOptions.new( user_persistent_storage: DummyPersistentStorageAdapter.new ) ) persisted_user = StatsigUser.new({ 'userID' => 'test-123' }) exp = Statsig.get_experiment( # User gets saved to persisted storage persisted_user, 'active_experiment', Statsig::GetExperimentOptions.new( user_persisted_values: Statsig.get_user_persisted_values(persisted_user, 'userID') ) ) puts exp.group_name # 'Control' exp = Statsig.get_experiment( # User evaluates using values from persisted storage StatsigUser.new({'userID' => 'unknown'}), 'active_experiment', Statsig::GetExperimentOptions.new( user_persisted_values: Statsig.get_user_persisted_values(persisted_user, 'userID') ) ) puts exp.group_name # 'Control' ``` {% /tab %} {% tab title="Python (Python Core)" %} ```python from statsig_python_core import Statsig, StatsigUser, StatsigOptions, ExperimentEvaluationOptions, PersistentStorage options = StatsigOptions(persistent_storage = MyPersistentStorage()) statsig = Statsig.initialize(options).wait user = StatsigUser("a-user") exp = statsig.get_experiment(StatsigUser("a-user"), ExperimentEvaluationOptions(user_persisted_values= PersistentStorage.get_user_persisted_value(user, "user_id"))) print(f"{exp.group_name}") # control ``` {% /tab %} {% tab title="Java Core" %} ```java PersistentStorage persistentStorage = new MyPersistentStorage(); // See /server-core/java-core/#persistent-storage on how to implement it StatsigOptions options = new StatsigOptions.Builder() .setPersistentStorage(persistentStorage) .build(); Statsig statsig = new Statsig("secret-key", options); statsig.initialize().get(); StatsigUser persistedUser = new StatsigUser.Builder().setUserID("test-123").build(); Map<String, StickyValues> persistedValues = persistentStorage.getValuesForUser(persistedUser, "userID"); if (persistedValues == null) { persistedValues = new HashMap<>(); } Experiment exp = statsig.getExperiment( persistedUser, "active_experiment", new GetExperimentOptions(persistedValues) ); System.out.println(exp.getGroupName()); // "Control" StatsigUser unknownUser = new StatsigUser.Builder().setUserID("unknown").build(); Map<String, StickyValues> valuesForPersistedUser = persistentStorage.getValuesForUser(persistedUser, "userID"); Experiment persistedExp = statsig.getExperiment( unknownUser, "active_experiment", new GetExperimentOptions(valuesForPersistedUser) ); System.out.println(persistedExp.getGroupName()); // "Control" ``` {% /tab %} {% tab title="Node Core" %} ```typescript let persistedStorage = new MyPersistentStorage(); // See /server-core/node-core/#persistent-storage on how to implement it let options = new StatsigOptions((persistentStorage = persistedStorage)); let statsig = new Statsig(secretKye, options); let user = new StatsigUser("a-user"); let exp = statsig.getExperiment( user, ExperimentEvaluationOptions( (userPersistentValues = persistedStorage.getUserPersistedValues( user, "user_id", )), ), ); ``` {% /tab %} {% tab title="PHP Core" %} ```php $persistent_storage = new MyPersistentStorage(); // See /server-core/php-core/#persistent-storage on how to implement it $options = new StatsigOptions(persistent_storage: $persistent_storage); $statsig = new Statsig("secret-key", $options); $statsig->initialize(); $persisted_user = new StatsigUser("test-123"); $persisted_values = $persistent_storage->getValuesForUser($persisted_user, "userID") ?? []; $exp = $statsig->getExperiment( $persisted_user, "active_experiment", ["user_persisted_values" => $persisted_values], ); echo $exp->groupName; // "Control" $unknown_user = new StatsigUser("unknown"); $values_for_persisted_user = $persistent_storage->getValuesForUser($persisted_user, "userID"); $persisted_exp = $statsig->getExperiment( $unknown_user, "active_experiment", ["user_persisted_values" => $values_for_persisted_user], ); echo $persisted_exp->groupName; // "Control" ``` {% /tab %} {% tab title="Rust Core" %} ```rust let persistent_storage = Arc::new(MyPersistentStorage::new()); // See /server-core/rust-core/#persistent-storage on how to implement it let options = StatsigOptions::builder() .persistent_storage(Some(persistent_storage.clone())) .build(); let statsig = Statsig::new("secret-key", Some(Arc::new(options))); statsig.initialize().await; let persisted_user = StatsigUser::with_user_id("test-123"); let persisted_values = persistent_storage .get_values_for_user(&persisted_user, "userID") .unwrap_or_default(); let exp = statsig.get_experiment_with_options( &persisted_user, "active_experiment", ExperimentEvaluationOptions { user_persisted_values: Some(persisted_values), ..Default::default() }, ); println!("{}", exp.group_name.as_deref().unwrap_or("")); // "Control" let unknown_user = StatsigUser::with_user_id("unknown"); let values_for_persisted_user = persistent_storage.get_values_for_user(&persisted_user, "userID"); let persisted_exp = statsig.get_experiment_with_options( &unknown_user, "active_experiment", ExperimentEvaluationOptions { user_persisted_values: values_for_persisted_user, ..Default::default() }, ); println!("{}", persisted_exp.group_name.as_deref().unwrap_or("")); // "Control" ``` {% /tab %} {% tab title="Kotlin" %} ```kotlin runBlocking { Statsig.initialize( "secret-key", StatsigOptions(userPersistentStorage = MyPersistentStorageAdapter()) ) } val persistedUser = StatsigUser("test-123") var exp = Statsig.getExperimentSync( persistedUser, "active_experiment", GetExperimentOptions( userPersistedValues = Statsig.getUserPersistedValues(persistedUser, "userID"), ), ) println(exp.groupName) // "Control" exp = Statsig.getExperimentSync( StatsigUser("unknown"), "active_experiment", GetExperimentOptions( userPersistedValues = Statsig.getUserPersistedValues(persistedUser, "userID"), ), ) println(exp.groupName) // "Control" ``` {% /tab %} {% tab title="Node JS" %} ```ts await Statsig.initialize("secret-key", { userPersistentStorage: new MyPersistentStorageAdapter(), }); const persistedUser: StatsigUser = { userID: "123" }; let exp = Statsig.getExperimentSync(persistedUser, "active_experiment", { userPersistedValues: Statsig.getUserPersistedValues(user, "userID"), }); console.log(exp.getGroupName()); // "Control" exp = Statsig.getExperimentSync({ userID: "unknown" }, "active_experiment", { userPersistedValues: Statsig.getUserPersistedValues(user, "userID"), }); console.log(exp.getGroupName()); // "Control" ``` {% /tab %} {% tab title="Go" %} ```go InitializeWithOptions( "secret-key", &Options{ UserPersistentStorage: persistentStorage, } ) persistedUser := User{UserID: "123"} exp := GetExperimentWithOptions( persistedUser, "active_experiment", &GetExperimentOptions{ PersistedValues: GetUserPersistedValues(persistedUser, "userID") } ) fmt.Println(exp.GroupName) // "Control" exp = GetExperimentWithOptions( User{UserID: "unknown"}, "active_experiment", &GetExperimentOptions{ PersistedValues: GetUserPersistedValues(persistedUser, "userID") } ) fmt.Println(exp.GroupName) // "Control" ``` {% /tab %} {% tab title=".Net" %} ```csharp var options = new StatsigServerOptions(); options.UserPersistentStorage = new MyPersistentStorageAdapter() await StatsigServer.Initialize("server-secret-key", options); var persistedUser = new StatsigUser { UserID = "123" }; var values = await StatsigServer.GetUserPersistedValues(persistedUser, "userID"); var getExpOptions = new StatsigGetExperimentOptions(values); var exp = StatsigServer.GetExperimentSync(persistedUser, "active_experiment", getExpOptions); Console.WriteLine(exp.GroupName); // "Control" var newValues = await StatsigServer.GetUserPersistedValues(persistedUser, "userID"); var newGetExpOptions = StatsigGetExperimentOptions(newValues); var newExp = StatsigServer.GetExperimentSync(new StatsigUser {UserID = "unknown"}, "active_experiment", newGetExpOptions); Console.WriteLine(newExp.GroupName); // "Control" ``` {% /tab %} {% /tabs %} ================================================================================ # Local Eval Adapter URL: https://docs.statsig.com/client/concepts/local-eval-adapter Updated: 2026-07-11 ================================================================================ ## Local Eval Adapter A common limitation of experimentation systems is the **dependence on an upfront network request to fetch experiment configurations**. [Bootstrapping](/client/concepts/initialize#2-bootstrap-initialization) and [Synchronous Initialization](/client/concepts/initialize#4-synchronous-initialization) offer alternatives, but both have limitations: bootstrapping still requires an upfront request (though it can be made synchronous with other requests), and synchronous initialization can result in configs that are out of date by one session. The Local Eval Adapter solves this by letting you ship experiment configurations inline with your application, so you can run experiments before initialization completes. The adapter expects a ruleset defining your experiments, similar to Statsig's Server SDKs. Refer to the [Example Ruleset below](#example-ruleset-config-spec). ## Limitations Shipping with a ruleset presents security concerns. When the experiment set is scoped to only a few experiments that run before any network requests, those concerns are often minimal. Exercise caution when shipping a ruleset in production code and verify that the contents are correct and safe. ## Usage & Functionality With proper attention to security and maintenance of the ruleset in your application, the Local Eval Adapter addresses the downsides of other experiment-at-launch strategies. {% callout type="info" %} The local evaluation adapter is for Enterprise and Pro Tier companies only. If you are trying to follow these instructions but don't meet that criteria, some of the setup steps may not work. {% /callout %} The Adapter manifests as a StatsigOptions object seeded with a ruleset payload: {% tabs %} {% tab title="iOS" %} ```swift Statsig.initialize( sdkKey: "client-sdkkey", user: user, options: StatsigOptions( overrideAdapter: OnDeviceEvalAdapter(stringPayload: onDeviceEvalAdapterRulesetPayload) ) ) { err in // Initialization completed. `err` is `nil` if it was successful } ``` {% /tab %} {% tab title="Android" %} ```kotlin val adapter = OnDeviceEvalAdapter("...") // dcs payload as string val user = StatsigUser("a-user") Statsig.initializeAsync(application, "client-sdk", user, object : IStatsigCallback { override fun onStatsigInitialize() { println(adapter.getGate(Statsig.getFeatureGate("a_gate"), user)?.details?.reason) // [OnDevice] Bootstrap:Recognized println(Statsig.getFeatureGate("a_gate").details.reason) // Network:Recognized } }) ``` {% /tab %} {% tab title="JavaScript" %} ```js const adapter = new OnDeviceEvalAdapter(); adapter.setData('...'); // dcs payload as string const client = new StatsigClient( 'client-...', { userID: 'a-user' }, { overrideAdapter: adapter, }, ); // purposely not awaiting this, will use local specs until network is resolved const isReady = client.initializeAsync(); const localEvalGate = client.getFeatureGate('a_gate'); console.log(localEvalGate.details.reason); // [OnDevice] Bootstrap:Recognized await isReady; const networkEvalGate = client.getFeatureGate('a_gate'); console.log(networkEvalGate.details.reason); // Network:Recognized ``` {% /tab %} {% /tabs %} ### Example Ruleset "Config Spec" The ruleset is a JSON object that contains the following fields: ```json { "feature_gates": [], "dynamic_configs": [], "layer_configs": [], "time": 1735718400000 } ``` You can retrieve your ruleset by API. Use target apps to scope the ruleset to only the experiments and feature gates needed on startup. For assistance, reach out in your Slack channel or the [community Slack](https://statsig.com/slack). ================================================================================ # Server Data Stores / Data Adapter URL: https://docs.statsig.com/server/concepts/data_store Updated: 2026-07-11 ================================================================================ A common question when configuring Statsig is how to handle potential points of failure. For example, if there is a Statsig API outage, can your integration continue to function? Yes. Your server SDK continues to operate normally, serving the most recent set of known values in response to calls like `checkGate`, `getConfig`, `getExperiment`, and `getLayer`. After the API recovers, your SDK automatically re-fetches the most up-to-date version of your project. Starting a new server or SDK instance while Statsig is down is a different scenario. The `DataAdapter`/`DataStore` addresses this case. ## DataAdapter (or DataStore) DataAdapters let you plug in your own storage solution as a cache that the Statsig Server SDKs use to load your project configurations (all experiments, configs, gates, and their targeting and allocation rules). Key use cases include reducing dependency on Statsig servers for initialization, improving initialization time by loading config from a local data store, and minimizing network I/O. DataAdapters implement a simple API: `initialize`, `get`, `set`, and `shutdown`. ### Recommended implementation In most cases, **your web servers should only implement the read path (`get`)**. Leaving the write path (`set`) empty is the best practice. If every SDK instance writes to the store on every update, this creates unnecessary contention and duplication. Instead, you should have a single source of truth that keeps your datastore up-to-date: * Run a **separate out-of-band service** that implements the SDK and is responsible for writing updates into the datastore using `set`. * Or, use a **cron job / periodic job** to fetch the config from the Statsig CDN endpoint and update your datastore under the correct cache keys (`statsig.cache`, `statsig.id_lists`, and `statsig.id_lists::{list_name}`). The Statsig SDK already handles this refresh logic by default, so separating read and write responsibilities is the cleanest and most reliable pattern. ### Data adapter logic * When the Statsig SDK is initialized with a DataAdapter, it first attempts to load config using `DataAdapter.get`. * If the entry exists, the SDK uses it. * If the entry doesn't exist and `localMode` isn't enabled, the SDK fetches the config from Statsig servers and (if you have a writer service) persists it using `DataAdapter.set`. * After initialization, the SDK continues to poll Statsig servers for updates and saves them back to your data store when available (if you have implemented `set` and designated a writer service). * If the SDK client is initialized with `localMode=true`, this will disable all network fetches from Statsig. * The cache keys used to be: * `statsig.cache` → config specs * `statsig.id_lists` → lookup of id lists * `statsig.id_lists::{list_name}` → actual id list values * but in the latest node and server core sdks, the format has changed: * `statsig|{path}|{format}|{hashedSDKKey}` where `path` is `/v1/download_config_specs` or `/v1/get_id_lists`, format is `plain_text`, and the `djb2` has of the sdk key is the last bit * the SDK will handle this for you, reach out if you are trying to recreate this path yourself to double check {% callout type="note" %} Most DataAdapters are used only for the `initialize` path for getting a project definition. At the time of this writing, only `Node.js`, `Ruby`, `Go`, `Java` and `.NET` support polling for updates.\ If you're interested in using a DataAdapter as the source of truth indefinitely, reach out in the [Slack community](https://statsig.com/slack) and let us know which language this would be useful for! {% /callout %} For information on your specific SDK language, go to the language-specific docs in the left-hand column. ================================================================================ # Target Apps URL: https://docs.statsig.com/sdks/target-apps Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Target Apps are an Enterprise-only feature. Reach out to the support team, your sales contact, or through the [Slack community](https://statsig.com/slack) if you want this enabled. {% /callout %} ## Overview SDK Keys support two attributes that restrict their access to certain configs: Environments and Target Apps. A Target App is a user-defined abstraction tied to entities in your Statsig project and can be linked to one or more SDK Keys. Target Apps represent things like your “Android” or “iOS” apps, or more specific services like “Search” and “Feed” that may span both client and server usage. Defining and using Target Apps is a best practice at large scale. The two main benefits are: - Performance: removing unused entities from the SDK payload speeds up initialization time and reduces the data stored in local caches or data stores. - Security: you may not want to expose certain gates, experiments, or configs to client-side code or to a client key that is easily discoverable in your app. Specifying a target app for your client key and linking only client-specific configs to it keeps those configs from being visible. Before using Target Apps, consider whether separate Statsig projects might be a better fit: - If you need to share gates, experiments, or metrics across apps or services, use a Target App. Entities are restricted within a single Statsig project. - If your services or apps always have distinct gates, experiments, and metrics, a separate Statsig project may be a better solution. For existing Statsig projects with multiple SDK integrations, Statsig suggests entities to tag based on historical checks from each SDK, to help you set up Target Apps. ## Configuring target apps ### Project setup Start by going to **Settings** → [**Target Applications**](https://console.statsig.com/settings/apps). {% figure %} ![Project Settings](/images/target_apps/settings.png) {% /figure %} {% figure %} <img src="https://github.com/user-attachments/assets/c359c25c-dead-44c4-8c1f-dd8cefb24929" alt="Screenshot 2025-09-04 at 11 48 55 PM" width="1485" /> {% /figure %} Create a target app, or use one that Statsig suggests based on your current SDK exposure logs. {% figure %} ![Project Settings](/images/target_apps/add.png) {% /figure %} ### Associating a feature gate with a target app To verify suggestions from initial setup or add more entities to a target app, navigate to the entity directly. Navigate to the **Feature Gates** tab and select a specific gate. In the right rail for that gate, view and edit the target apps applied to it. {% figure %} ![Project Settings](/images/target_apps/gate.png) {% /figure %} Repeat this flow for the Dynamic Configs and Experiments that are used by this Target App. ### Associating an SDK key with a target app Navigate to **Settings** → [**Keys & Environments**](https://console.statsig.com/api_keys). Select the SDK Key you want to restrict to a Target App. Under **Actions**, open the dropdown menu. {% figure %} ![Project Settings](/images/target_apps/keys.png) {% /figure %} Then select the target app(s) to apply to that key. {% figure %} ![Project Settings](/images/target_apps/apply.png) {% /figure %} {% callout type="note" %} When [bootstrapping](/client/concepts/initialize#2-bootstrap-initialization) a client SDK from a server SDK, the server SDK must have access to all gates, experiments, and configs needed by both the server and the client. If you have separate target apps for client and server SDKs, apply both target apps to the server key you are using. To filter the bootstrapping response to a specific target app, pass a client key with that target app applied to the `getClientInitializeResponse` call. {% /callout %} ### Debugging To debug SDK evaluations that may be affected by your Target App configuration, use the **Diagnostics** tab for any entity. In the Exposure Stream at the bottom, each check shows the Target App associated with the SDK key used. If any exposure doesn't have a matching Target App, Statsig flags it with a warning. ================================================================================ # Deprecation Notices URL: https://docs.statsig.com/server/deprecation-notices Updated: 2026-07-11 ================================================================================ ## Direct initialization API access for server SDKs, October 31, 2025 #### What's changing? Until mid-2024, Statsig's Server SDKs downloaded configuration values directly from Statsig servers on startup. Statsig now hosts these configuration files on a secure CDN (Cloudflare), which reduces initialization time and increases reliability. Direct API access is deprecated. It is only available in outdated SDK versions. All customers must stop using these SDK versions and upgrade. #### Change required The only change required is a minor SDK version bump. No code changes are needed. Bump your SDK version to at least: * **go-sdk** 1.30.3 * **java-server** 1.9.0 * **py-server** 0.28.0 * **ruby-server** 1.29.0 * **statsig-node** 5.25.0 * **.NET** 2.4.1 * **Node-lite** 0.5.0 * **PHP** 3.7.2 Make this change by October 31st, 2025. {% callout type="note" %} CDN access is only available in a more recent version of the PHP and .NET SDKs, so Statsig won't strictly enforce an upgrade for those 2 SDKs. Upgrade as soon as possible. {% /callout %} ## New method for country resolution Starting December 9th, 2024, Statsig uses a more accurate method of resolving IP addresses to country codes in client SDKs, which may result in different gate behavior for some users. For country resolution in both Client and Server SDKs, Statsig uses a [homegrown package](https://github.com/statsig-io/ip3country) that lacks IPv6 support. To resolve countries without increasing SDK size, Statsig uses the cloud provider's country resolution when serving requests to the `/initialize` endpoint. ### Potential changes in client evaluations This may result in different evaluations for some client-side checks. The new method is more accurate and complete. You may see more users passing country rules, because the new method resolves countries for IPv6 users, who now represent a large share of many customers' user base. ### Conflicts with server-side checks If you check some configs on both the client and server side, a small number of users may pass targeting on the client but fail on the server, or vice versa. Server-side country resolution continues to use [IP3Country](https://github.com/statsig-io/ip3country) when a country isn't explicitly set on the `StatsigUser` object. To avoid conflicts, use your cloud provider's country resolution (often available in a load balancer) and set the result as the `country` field on the `StatsigUser` object. This applies to evaluations for gates, configs, experiments, and layers, and also to `getClientInitializeResponse` when generating the payload for a client SDK. ## Async evaluation functions #### Reason Server SDKs were originally designed for maximum backward compatibility. If a Server SDK didn't support an operator or configuration, it fell back to Statsig servers to ensure a valid result, which required asynchronous top-level evaluation functions. In practice, this fallback is rarely needed. Newer releases remove the asynchronous functions. #### Example ```java var result = await Statsig.checkGate("my_gate"); // Bad // For node v6.0.0 var result = Statsig.checkGate("my_gate") // Good // For .Net and Java var result = Statsig.checkGateSync("my_gate"); // Good ``` #### SDKs .NET Server - [v1.20.0](https://github.com/statsig-io/dotnet-sdk/releases/tag/v1.20.0) NodeJS Server - [v5.10.0](https://github.com/statsig-io/node-js-server-sdk/releases/tag/v5.10.0) Java/Kotlin Server - [v1.12.0](https://github.com/statsig-io/java-server-sdk/releases/tag/v1.12.0) ================================================================================ # SDK Support Policy URL: https://docs.statsig.com/sdks/support Updated: 2026-07-11 ================================================================================ Statsig provides official support only for SDK versions released within the past **one year**, unless otherwise specified. Some updates may require a different timeline or migration path. ## Supported versions * Statsig provides support (bug reports, issue investigation, and compatibility assistance) for SDK versions released **within the past 12 months**. * Versions older than one year are considered **unsupported**. They may continue to function, but Statsig doesn't guarantee their behavior or compatibility with backend services. * Upgrade to the latest version to benefit from new features, performance improvements, and critical fixes. ## Versioning and releases * All SDKs follow [Semantic Versioning (SemVer)](https://semver.org/), using the format `MAJOR.MINOR.PATCH`, **unless otherwise specified**. * **MAJOR**: breaking changes * **MINOR**: backwards-compatible new features * **PATCH**: backwards-compatible bug fixes * Release notes are published in the GitHub Releases section of each SDK’s repository. These notes contain details on changes, improvements, and upgrade instructions. ## Open source and community contributions * All SDKs are open source and licensed under the [ISC License](https://opensource.org/licenses/ISC), allowing developers to freely use, modify, and distribute them. * Statsig welcomes and encourages community contributions. ## Get help For help with upgrading, reviewing release notes, or contributing, refer to the SDK’s GitHub repository or reach out in <a href="https://statsig.com/slack" target="_blank">Slack</a>. ================================================================================ # Console API Overview URL: https://docs.statsig.com/console-api/introduction Updated: 2026-07-11 ================================================================================ The Console API is the CRUD API for performing the actions available on console.statsig.com without using the web UI. If you have feature requests, post them in the [Slack channel](https://www.statsig.com/slack). ## Base URL `https://statsigapi.net` ## Authorization All requests must include the **STATSIG-API-KEY** field in the header. The value should be a **Console API Key**, which you can create in the Project Settings on [console.statsig.com/api\_keys](https://console.statsig.com/api_keys) ## Rate limiting Mutation requests (POST/PATCH/PUT/DELETE) to the Console API are limited to ~ 100 requests / 10 seconds and ~ 900 requests / 15 minutes, per project. ## API version The Console API is versioned. Each version is guaranteed to not break existing usage; each new version introduces breaking changes. There is only one version: `20240601`. The [OpenAPI spec](https://api.statsig.com/openapi/20240601.json) for this API version is kept up-to-date. Pass the version in the **STATSIG-API-VERSION** field in the header. This field is currently optional but will be required in the future. ================================================================================ # Create Topline Alert URL: https://docs.statsig.com/api-reference/alerts/create-topline-alert Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-topline-alert.json" /%} ================================================================================ # Delete Topline Alert URL: https://docs.statsig.com/api-reference/alerts/delete-topline-alert Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-topline-alert.json" /%} ================================================================================ # List Topline Alert Events URL: https://docs.statsig.com/api-reference/alerts/list-topline-alert-events Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-topline-alert-events.json" /%} ================================================================================ # List Topline Alerts URL: https://docs.statsig.com/api-reference/alerts/list-topline-alerts Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-topline-alerts.json" /%} ================================================================================ # Read Topline Alert URL: https://docs.statsig.com/api-reference/alerts/read-topline-alert Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-topline-alert.json" /%} ================================================================================ # Read Topline Alert Event URL: https://docs.statsig.com/api-reference/alerts/read-topline-alert-event Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-topline-alert-event.json" /%} ================================================================================ # Update Topline Alert URL: https://docs.statsig.com/api-reference/alerts/update-topline-alert Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-topline-alert.json" /%} ================================================================================ # List Audit Logs URL: https://docs.statsig.com/api-reference/audit-logs/list-audit-logs Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-audit-logs.json" /%} ================================================================================ # Approve Autotune Review URL: https://docs.statsig.com/api-reference/autotunes/approve-autotune-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="approve-autotune-review.json" /%} ================================================================================ # Cancel Autotune Review URL: https://docs.statsig.com/api-reference/autotunes/cancel-autotune-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="cancel-autotune-review.json" /%} ================================================================================ # Commit Autotune Review URL: https://docs.statsig.com/api-reference/autotunes/commit-autotune-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="commit-autotune-review.json" /%} ================================================================================ # Create Autotune URL: https://docs.statsig.com/api-reference/autotunes/create-autotune Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-autotune.json" /%} ================================================================================ # Create Autotune Review URL: https://docs.statsig.com/api-reference/autotunes/create-autotune-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-autotune-review.json" /%} ================================================================================ # Delete Autotune URL: https://docs.statsig.com/api-reference/autotunes/delete-autotune Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-autotune.json" /%} ================================================================================ # Edit Autotune Review URL: https://docs.statsig.com/api-reference/autotunes/edit-autotune-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="edit-autotune-review.json" /%} ================================================================================ # Finish Experiment Early URL: https://docs.statsig.com/api-reference/autotunes/finish-experiment-early Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="finish-experiment-early.json" /%} ================================================================================ # Fully Update Autotune URL: https://docs.statsig.com/api-reference/autotunes/fully-update-autotune Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="fully-update-autotune.json" /%} ================================================================================ # Get Autotune Review URL: https://docs.statsig.com/api-reference/autotunes/get-autotune-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-autotune-review.json" /%} ================================================================================ # List Autotune URL: https://docs.statsig.com/api-reference/autotunes/list-autotune Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-autotune.json" /%} ================================================================================ # List Autotune Reviews URL: https://docs.statsig.com/api-reference/autotunes/list-autotune-reviews Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-autotune-reviews.json" /%} ================================================================================ # Partially Update Autotune URL: https://docs.statsig.com/api-reference/autotunes/partially-update-autotune Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="partially-update-autotune.json" /%} ================================================================================ # Read Autotune URL: https://docs.statsig.com/api-reference/autotunes/read-autotune Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-autotune.json" /%} ================================================================================ # Reject Autotune Review URL: https://docs.statsig.com/api-reference/autotunes/reject-autotune-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="reject-autotune-review.json" /%} ================================================================================ # Reset Experiment URL: https://docs.statsig.com/api-reference/autotunes/reset-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="reset-experiment.json" /%} ================================================================================ # Start Autotune Experiment URL: https://docs.statsig.com/api-reference/autotunes/start-autotune-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="start-autotune-experiment.json" /%} ================================================================================ # Change Validation URL: https://docs.statsig.com/api-reference/change-validation/change-validation Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="change-validation.json" /%} ================================================================================ # Update change validation message URL: https://docs.statsig.com/api-reference/change-validation/update-change-validation-message Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-change-validation-message.json" /%} ================================================================================ # Get Company Info URL: https://docs.statsig.com/api-reference/company/get-company-info Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-company-info.json" /%} ================================================================================ # Read Exposure Event Count URL: https://docs.statsig.com/api-reference/configs/read-exposure-event-count Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-exposure-event-count.json" /%} ================================================================================ # Add Widgets to Dashboard URL: https://docs.statsig.com/api-reference/dashboards/add-widgets-to-dashboard Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="add-widgets-to-dashboard.json" /%} ================================================================================ # Create Dashboard URL: https://docs.statsig.com/api-reference/dashboards/create-dashboard Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-dashboard.json" /%} ================================================================================ # List Dashboards URL: https://docs.statsig.com/api-reference/dashboards/list-dashboards Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-dashboards.json" /%} ================================================================================ # Read Dashboard URL: https://docs.statsig.com/api-reference/dashboards/read-dashboard Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-dashboard.json" /%} ================================================================================ # Read Dashboard Widget Results URL: https://docs.statsig.com/api-reference/dashboards/read-dashboard-widget-results Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-dashboard-widget-results.json" /%} ================================================================================ # Replace Widgets on Dashboard URL: https://docs.statsig.com/api-reference/dashboards/replace-widgets-on-dashboard Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="replace-widgets-on-dashboard.json" /%} ================================================================================ # Approve Dynamic Config Review URL: https://docs.statsig.com/api-reference/dynamic-configs/approve-dynamic-config-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="approve-dynamic-config-review.json" /%} ================================================================================ # Archive Dynamic Config URL: https://docs.statsig.com/api-reference/dynamic-configs/archive-dynamic-config Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="archive-dynamic-config.json" /%} ================================================================================ # Cancel Dynamic Config Review URL: https://docs.statsig.com/api-reference/dynamic-configs/cancel-dynamic-config-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="cancel-dynamic-config-review.json" /%} ================================================================================ # Commit Dynamic Config Review URL: https://docs.statsig.com/api-reference/dynamic-configs/commit-dynamic-config-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="commit-dynamic-config-review.json" /%} ================================================================================ # Create Dynamic Config URL: https://docs.statsig.com/api-reference/dynamic-configs/create-dynamic-config Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-dynamic-config.json" /%} ================================================================================ # Create Dynamic Config Review URL: https://docs.statsig.com/api-reference/dynamic-configs/create-dynamic-config-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-dynamic-config-review.json" /%} ================================================================================ # Delete Dynamic Config URL: https://docs.statsig.com/api-reference/dynamic-configs/delete-dynamic-config Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-dynamic-config.json" /%} ================================================================================ # Delete Dynamic Config Rule URL: https://docs.statsig.com/api-reference/dynamic-configs/delete-dynamic-config-rule Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-dynamic-config-rule.json" /%} ================================================================================ # Disable Dynamic Config URL: https://docs.statsig.com/api-reference/dynamic-configs/disable-dynamic-config Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="disable-dynamic-config.json" /%} ================================================================================ # Edit Dynamic Config Review URL: https://docs.statsig.com/api-reference/dynamic-configs/edit-dynamic-config-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="edit-dynamic-config-review.json" /%} ================================================================================ # Enable Dynamic Config URL: https://docs.statsig.com/api-reference/dynamic-configs/enable-dynamic-config Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="enable-dynamic-config.json" /%} ================================================================================ # Fully Update Dynamic Config URL: https://docs.statsig.com/api-reference/dynamic-configs/fully-update-dynamic-config Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="fully-update-dynamic-config.json" /%} ================================================================================ # Get Dynamic Config URL: https://docs.statsig.com/api-reference/dynamic-configs/get-dynamic-config Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-dynamic-config.json" /%} ================================================================================ # Get Dynamic Config Review URL: https://docs.statsig.com/api-reference/dynamic-configs/get-dynamic-config-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-dynamic-config-review.json" /%} ================================================================================ # Get Dynamic Config Rules URL: https://docs.statsig.com/api-reference/dynamic-configs/get-dynamic-config-rules Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-dynamic-config-rules.json" /%} ================================================================================ # Get Specific Dynamic Config Rule URL: https://docs.statsig.com/api-reference/dynamic-configs/get-specific-dynamic-config-rule Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-specific-dynamic-config-rule.json" /%} ================================================================================ # List Dynamic Config Reviews URL: https://docs.statsig.com/api-reference/dynamic-configs/list-dynamic-config-reviews Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-dynamic-config-reviews.json" /%} ================================================================================ # List Dynamic Config Versions URL: https://docs.statsig.com/api-reference/dynamic-configs/list-dynamic-config-versions Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-dynamic-config-versions.json" /%} ================================================================================ # List Dynamic Configs URL: https://docs.statsig.com/api-reference/dynamic-configs/list-dynamic-configs Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-dynamic-configs.json" /%} ================================================================================ # Partially Update Dynamic Config URL: https://docs.statsig.com/api-reference/dynamic-configs/partially-update-dynamic-config Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="partially-update-dynamic-config.json" /%} ================================================================================ # Reject Dynamic Config Review URL: https://docs.statsig.com/api-reference/dynamic-configs/reject-dynamic-config-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="reject-dynamic-config-review.json" /%} ================================================================================ # Unarchive Dynamic Config URL: https://docs.statsig.com/api-reference/dynamic-configs/unarchive-dynamic-config Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="unarchive-dynamic-config.json" /%} ================================================================================ # Update Dynamic Config Rule By Id URL: https://docs.statsig.com/api-reference/dynamic-configs/update-dynamic-config-rule-by-id Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-dynamic-config-rule-by-id.json" /%} ================================================================================ # Get Environments URL: https://docs.statsig.com/api-reference/environments/get-environments Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-environments.json" /%} ================================================================================ # Update Environments URL: https://docs.statsig.com/api-reference/environments/update-environments Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-environments.json" /%} ================================================================================ # Get metrics using event name URL: https://docs.statsig.com/api-reference/events/get-metrics-using-event-name Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-metrics-using-event-name.json" /%} ================================================================================ # Get specific events URL: https://docs.statsig.com/api-reference/events/get-specific-events Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-specific-events.json" /%} ================================================================================ # List Events URL: https://docs.statsig.com/api-reference/events/list-events Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-events.json" /%} ================================================================================ # Abandon Experiment URL: https://docs.statsig.com/api-reference/experiments/abandon-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="abandon-experiment.json" /%} ================================================================================ # Approve Experiment Review URL: https://docs.statsig.com/api-reference/experiments/approve-experiment-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="approve-experiment-review.json" /%} ================================================================================ # Archive Experiment URL: https://docs.statsig.com/api-reference/experiments/archive-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="archive-experiment.json" /%} ================================================================================ # Cancel Experiment Review URL: https://docs.statsig.com/api-reference/experiments/cancel-experiment-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="cancel-experiment-review.json" /%} ================================================================================ # Cancel Pulse Load (Warehouse Native) URL: https://docs.statsig.com/api-reference/experiments/cancel-pulse-load-warehouse-native Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="cancel-pulse-load-warehouse-native.json" /%} ================================================================================ # Commit Experiment Review URL: https://docs.statsig.com/api-reference/experiments/commit-experiment-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="commit-experiment-review.json" /%} ================================================================================ # Conclude Experiment & Defer Decision URL: https://docs.statsig.com/api-reference/experiments/conclude-experiment-defer-decision Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="conclude-experiment-defer-decision.json" /%} ================================================================================ # Create Assignment Source URL: https://docs.statsig.com/api-reference/experiments/create-assignment-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-assignment-source.json" /%} ================================================================================ # Create Entity Property Source URL: https://docs.statsig.com/api-reference/experiments/create-entity-property-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-entity-property-source.json" /%} ================================================================================ # Create Experiment URL: https://docs.statsig.com/api-reference/experiments/create-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-experiment.json" /%} ================================================================================ # Create Experiment Review URL: https://docs.statsig.com/api-reference/experiments/create-experiment-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-experiment-review.json" /%} ================================================================================ # Delete a Single Experiment Override URL: https://docs.statsig.com/api-reference/experiments/delete-a-single-experiment-override Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-a-single-experiment-override.json" /%} ================================================================================ # Delete a Single Experiment User ID Override URL: https://docs.statsig.com/api-reference/experiments/delete-a-single-experiment-user-id-override Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-a-single-experiment-user-id-override.json" /%} ================================================================================ # Delete Assignment Source URL: https://docs.statsig.com/api-reference/experiments/delete-assignment-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-assignment-source.json" /%} ================================================================================ # Delete Entity Property Source URL: https://docs.statsig.com/api-reference/experiments/delete-entity-property-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-entity-property-source.json" /%} ================================================================================ # Delete Experiment URL: https://docs.statsig.com/api-reference/experiments/delete-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-experiment.json" /%} ================================================================================ # Delete Experiment Overrides URL: https://docs.statsig.com/api-reference/experiments/delete-experiment-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-experiment-overrides.json" /%} ================================================================================ # Disable Experiment Groups URL: https://docs.statsig.com/api-reference/experiments/disable-experiment-groups Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="disable-experiment-groups.json" /%} ================================================================================ # Edit Experiment Review URL: https://docs.statsig.com/api-reference/experiments/edit-experiment-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="edit-experiment-review.json" /%} ================================================================================ # Enable Experiment Groups URL: https://docs.statsig.com/api-reference/experiments/enable-experiment-groups Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="enable-experiment-groups.json" /%} ================================================================================ # Finish Experiment Early URL: https://docs.statsig.com/api-reference/experiments/finish-experiment-early Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="finish-experiment-early.json" /%} ================================================================================ # Fully Update Experiment URL: https://docs.statsig.com/api-reference/experiments/fully-update-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="fully-update-experiment.json" /%} ================================================================================ # Get Entity Property Source URL: https://docs.statsig.com/api-reference/experiments/get-entity-property-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-entity-property-source.json" /%} ================================================================================ # Get Experiment URL: https://docs.statsig.com/api-reference/experiments/get-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-experiment.json" /%} ================================================================================ # Get Experiment Context URL: https://docs.statsig.com/api-reference/experiments/get-experiment-context Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-experiment-context.json" /%} ================================================================================ # Get Experiment Guardrail Alert Statuses URL: https://docs.statsig.com/api-reference/experiments/get-experiment-guardrail-alert-statuses Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-experiment-guardrail-alert-statuses.json" /%} ================================================================================ # Get Experiment Overrides URL: https://docs.statsig.com/api-reference/experiments/get-experiment-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-experiment-overrides.json" /%} ================================================================================ # Get Experiment Review URL: https://docs.statsig.com/api-reference/experiments/get-experiment-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-experiment-review.json" /%} ================================================================================ # Get Pulse Load History Details (Warehouse Native) URL: https://docs.statsig.com/api-reference/experiments/get-pulse-load-history-details-warehouse-native Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-pulse-load-history-details-warehouse-native.json" /%} ================================================================================ # List Assignment Sources URL: https://docs.statsig.com/api-reference/experiments/list-assignment-sources Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-assignment-sources.json" /%} ================================================================================ # List Entity Property Sources URL: https://docs.statsig.com/api-reference/experiments/list-entity-property-sources Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-entity-property-sources.json" /%} ================================================================================ # List Experiment Reviews URL: https://docs.statsig.com/api-reference/experiments/list-experiment-reviews Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-experiment-reviews.json" /%} ================================================================================ # List Experiment Versions URL: https://docs.statsig.com/api-reference/experiments/list-experiment-versions Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-experiment-versions.json" /%} ================================================================================ # List Experiments URL: https://docs.statsig.com/api-reference/experiments/list-experiments Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-experiments.json" /%} ================================================================================ # Load Pulse (Warehouse Native) URL: https://docs.statsig.com/api-reference/experiments/load-pulse-warehouse-native Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="load-pulse-warehouse-native.json" /%} ================================================================================ # Partially Update Experiment URL: https://docs.statsig.com/api-reference/experiments/partially-update-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="partially-update-experiment.json" /%} ================================================================================ # Partially Update Experiment Overrides URL: https://docs.statsig.com/api-reference/experiments/partially-update-experiment-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="partially-update-experiment-overrides.json" /%} ================================================================================ # Patch Assignment Source URL: https://docs.statsig.com/api-reference/experiments/patch-assignment-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="patch-assignment-source.json" /%} ================================================================================ # Patch Entity Property Source URL: https://docs.statsig.com/api-reference/experiments/patch-entity-property-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="patch-entity-property-source.json" /%} ================================================================================ # Post Assignment Source URL: https://docs.statsig.com/api-reference/experiments/post-assignment-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="post-assignment-source.json" /%} ================================================================================ # Post Entity Property Source URL: https://docs.statsig.com/api-reference/experiments/post-entity-property-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="post-entity-property-source.json" /%} ================================================================================ # Pulse Load History (Warehouse Native) URL: https://docs.statsig.com/api-reference/experiments/pulse-load-history-warehouse-native Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="pulse-load-history-warehouse-native.json" /%} ================================================================================ # Reject Experiment Review URL: https://docs.statsig.com/api-reference/experiments/reject-experiment-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="reject-experiment-review.json" /%} ================================================================================ # Reset Experiment URL: https://docs.statsig.com/api-reference/experiments/reset-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="reset-experiment.json" /%} ================================================================================ # Resolve Metric Rollout Alert URL: https://docs.statsig.com/api-reference/experiments/resolve-metric-rollout-alert Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="resolve-metric-rollout-alert.json" /%} ================================================================================ # Restart As New Experiment URL: https://docs.statsig.com/api-reference/experiments/restart-as-new-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="restart-as-new-experiment.json" /%} ================================================================================ # Retrieve cumulative exposures URL: https://docs.statsig.com/api-reference/experiments/retrieve-cumulative-exposures Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="retrieve-cumulative-exposures.json" /%} ================================================================================ # Retrieve Experiment Checks Diagnostics URL: https://docs.statsig.com/api-reference/experiments/retrieve-experiment-checks-diagnostics Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="retrieve-experiment-checks-diagnostics.json" /%} ================================================================================ # Retrieve Experiment Summary Charts (Beta) URL: https://docs.statsig.com/api-reference/experiments/retrieve-experiment-summary-charts-beta Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="retrieve-experiment-summary-charts-beta.json" /%} ================================================================================ # Retrieve Exposures By Dimension URL: https://docs.statsig.com/api-reference/experiments/retrieve-exposures-by-dimension Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="retrieve-exposures-by-dimension.json" /%} ================================================================================ # Retrieve Pulse Metric Result URL: https://docs.statsig.com/api-reference/experiments/retrieve-pulse-metric-result Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="retrieve-pulse-metric-result.json" /%} ================================================================================ # Retrieve Pulse Results (Beta) URL: https://docs.statsig.com/api-reference/experiments/retrieve-pulse-results-beta Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="retrieve-pulse-results-beta.json" /%} ================================================================================ # Schedule Experiment Start URL: https://docs.statsig.com/api-reference/experiments/schedule-experiment-start Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="schedule-experiment-start.json" /%} ================================================================================ # Start Experiment URL: https://docs.statsig.com/api-reference/experiments/start-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="start-experiment.json" /%} ================================================================================ # Start Experiment Code Cleanup URL: https://docs.statsig.com/api-reference/experiments/start-experiment-code-cleanup Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="start-experiment-code-cleanup.json" /%} ================================================================================ # Unarchive Experiment URL: https://docs.statsig.com/api-reference/experiments/unarchive-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="unarchive-experiment.json" /%} ================================================================================ # Update Experiment Overrides URL: https://docs.statsig.com/api-reference/experiments/update-experiment-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-experiment-overrides.json" /%} ================================================================================ # Create Qualifying Event URL: https://docs.statsig.com/api-reference/experiments-warehouse-native/create-qualifying-event Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-qualifying-event.json" /%} ================================================================================ # Delete Qualifying Event URL: https://docs.statsig.com/api-reference/experiments-warehouse-native/delete-qualifying-event Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-qualifying-event.json" /%} ================================================================================ # List qualifying event URL: https://docs.statsig.com/api-reference/experiments-warehouse-native/list-qualifying-event Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-qualifying-event.json" /%} ================================================================================ # Read Qualifying Event URL: https://docs.statsig.com/api-reference/experiments-warehouse-native/read-qualifying-event Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-qualifying-event.json" /%} ================================================================================ # Update Qualifying Event URL: https://docs.statsig.com/api-reference/experiments-warehouse-native/update-qualifying-event Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-qualifying-event.json" /%} ================================================================================ # Add Gate Overrides URL: https://docs.statsig.com/api-reference/gates/add-gate-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="add-gate-overrides.json" /%} ================================================================================ # Add Gate Rule URL: https://docs.statsig.com/api-reference/gates/add-gate-rule Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="add-gate-rule.json" /%} ================================================================================ # Approve Gate Review URL: https://docs.statsig.com/api-reference/gates/approve-gate-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="approve-gate-review.json" /%} ================================================================================ # Archive Gate URL: https://docs.statsig.com/api-reference/gates/archive-gate Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="archive-gate.json" /%} ================================================================================ # Cancel Gate Review URL: https://docs.statsig.com/api-reference/gates/cancel-gate-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="cancel-gate-review.json" /%} ================================================================================ # Commit Gate Review URL: https://docs.statsig.com/api-reference/gates/commit-gate-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="commit-gate-review.json" /%} ================================================================================ # Create Gate URL: https://docs.statsig.com/api-reference/gates/create-gate Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-gate.json" /%} ================================================================================ # Create Gate Review URL: https://docs.statsig.com/api-reference/gates/create-gate-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-gate-review.json" /%} ================================================================================ # Delete Gate Overrides URL: https://docs.statsig.com/api-reference/gates/delete-gate-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-gate-overrides.json" /%} ================================================================================ # Delete Gate Rule URL: https://docs.statsig.com/api-reference/gates/delete-gate-rule Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-gate-rule.json" /%} ================================================================================ # Delete Gates URL: https://docs.statsig.com/api-reference/gates/delete-gates Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-gates.json" /%} ================================================================================ # Disable Gate URL: https://docs.statsig.com/api-reference/gates/disable-gate Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="disable-gate.json" /%} ================================================================================ # Edit Gate Review URL: https://docs.statsig.com/api-reference/gates/edit-gate-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="edit-gate-review.json" /%} ================================================================================ # Enable Gate URL: https://docs.statsig.com/api-reference/gates/enable-gate Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="enable-gate.json" /%} ================================================================================ # Fully Update Gates URL: https://docs.statsig.com/api-reference/gates/fully-update-gates Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="fully-update-gates.json" /%} ================================================================================ # Get Eligible Reviewers URL: https://docs.statsig.com/api-reference/gates/get-eligible-reviewers Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-eligible-reviewers.json" /%} ================================================================================ # Get Gate Override URL: https://docs.statsig.com/api-reference/gates/get-gate-override Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-gate-override.json" /%} ================================================================================ # Get Gate Review URL: https://docs.statsig.com/api-reference/gates/get-gate-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-gate-review.json" /%} ================================================================================ # Launch Gate URL: https://docs.statsig.com/api-reference/gates/launch-gate Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="launch-gate.json" /%} ================================================================================ # List Dynamic Config References URL: https://docs.statsig.com/api-reference/gates/list-dynamic-config-references Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-dynamic-config-references.json" /%} ================================================================================ # List Experiment References URL: https://docs.statsig.com/api-reference/gates/list-experiment-references Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-experiment-references.json" /%} ================================================================================ # List Gate References URL: https://docs.statsig.com/api-reference/gates/list-gate-references Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-gate-references.json" /%} ================================================================================ # List Gate Reviews URL: https://docs.statsig.com/api-reference/gates/list-gate-reviews Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-gate-reviews.json" /%} ================================================================================ # List Gate Versions URL: https://docs.statsig.com/api-reference/gates/list-gate-versions Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-gate-versions.json" /%} ================================================================================ # List Gates URL: https://docs.statsig.com/api-reference/gates/list-gates Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-gates.json" /%} ================================================================================ # Load Pulse Gate URL: https://docs.statsig.com/api-reference/gates/load-pulse-gate Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="load-pulse-gate.json" /%} ================================================================================ # Partially Update Gates URL: https://docs.statsig.com/api-reference/gates/partially-update-gates Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="partially-update-gates.json" /%} ================================================================================ # Pulse Load History (Warehouse Native) URL: https://docs.statsig.com/api-reference/gates/pulse-load-history-warehouse-native Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="pulse-load-history-warehouse-native.json" /%} ================================================================================ # Read Gate URL: https://docs.statsig.com/api-reference/gates/read-gate Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-gate.json" /%} ================================================================================ # Read Gate Checks URL: https://docs.statsig.com/api-reference/gates/read-gate-checks Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-gate-checks.json" /%} ================================================================================ # Read Gate Rules URL: https://docs.statsig.com/api-reference/gates/read-gate-rules Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-gate-rules.json" /%} ================================================================================ # Reject Gate Review URL: https://docs.statsig.com/api-reference/gates/reject-gate-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="reject-gate-review.json" /%} ================================================================================ # Resolve Metric Rollout Alert URL: https://docs.statsig.com/api-reference/gates/resolve-metric-rollout-alert Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="resolve-metric-rollout-alert.json" /%} ================================================================================ # Retrieve Pulse Results URL: https://docs.statsig.com/api-reference/gates/retrieve-pulse-results Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="retrieve-pulse-results.json" /%} ================================================================================ # Start Gate Code Cleanup URL: https://docs.statsig.com/api-reference/gates/start-gate-code-cleanup Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="start-gate-code-cleanup.json" /%} ================================================================================ # Unarchive Gate URL: https://docs.statsig.com/api-reference/gates/unarchive-gate Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="unarchive-gate.json" /%} ================================================================================ # Update Gate Overrides URL: https://docs.statsig.com/api-reference/gates/update-gate-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-gate-overrides.json" /%} ================================================================================ # Update Gate Rules URL: https://docs.statsig.com/api-reference/gates/update-gate-rules Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-gate-rules.json" /%} ================================================================================ # Add Holdout Overrides URL: https://docs.statsig.com/api-reference/holdouts/add-holdout-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="add-holdout-overrides.json" /%} ================================================================================ # Create holdout URL: https://docs.statsig.com/api-reference/holdouts/create-holdout Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-holdout.json" /%} ================================================================================ # Delete holdout by id URL: https://docs.statsig.com/api-reference/holdouts/delete-holdout-by-id Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-holdout-by-id.json" /%} ================================================================================ # Get holdout by id URL: https://docs.statsig.com/api-reference/holdouts/get-holdout-by-id Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-holdout-by-id.json" /%} ================================================================================ # List Holdouts URL: https://docs.statsig.com/api-reference/holdouts/list-holdouts Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-holdouts.json" /%} ================================================================================ # Partially update holdout by id URL: https://docs.statsig.com/api-reference/holdouts/partially-update-holdout-by-id Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="partially-update-holdout-by-id.json" /%} ================================================================================ # Read Holdout Overrides URL: https://docs.statsig.com/api-reference/holdouts/read-holdout-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-holdout-overrides.json" /%} ================================================================================ # Remove Holdout Overrides URL: https://docs.statsig.com/api-reference/holdouts/remove-holdout-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="remove-holdout-overrides.json" /%} ================================================================================ # Retrieve Pulse Results URL: https://docs.statsig.com/api-reference/holdouts/retrieve-pulse-results Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="retrieve-pulse-results.json" /%} ================================================================================ # Update holdout by id URL: https://docs.statsig.com/api-reference/holdouts/update-holdout-by-id Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-holdout-by-id.json" /%} ================================================================================ # Update Holdout Overrides URL: https://docs.statsig.com/api-reference/holdouts/update-holdout-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-holdout-overrides.json" /%} ================================================================================ # Backfill Ingestion URL: https://docs.statsig.com/api-reference/ingestions/backfill-ingestion Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="backfill-ingestion.json" /%} ================================================================================ # Create Ingestion Databricks URL: https://docs.statsig.com/api-reference/ingestions/create-ingestion-databricks Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-ingestion-databricks.json" /%} ================================================================================ # Create Ingestion Source URL: https://docs.statsig.com/api-reference/ingestions/create-ingestion-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-ingestion-source.json" /%} ================================================================================ # Delete Ingestion Source URL: https://docs.statsig.com/api-reference/ingestions/delete-ingestion-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-ingestion-source.json" /%} ================================================================================ # Get Ingestion Event Count URL: https://docs.statsig.com/api-reference/ingestions/get-ingestion-event-count Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-ingestion-event-count.json" /%} ================================================================================ # Get Ingestion Event Delta Ledger URL: https://docs.statsig.com/api-reference/ingestions/get-ingestion-event-delta-ledger Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-ingestion-event-delta-ledger.json" /%} ================================================================================ # List Ingestion Runs URL: https://docs.statsig.com/api-reference/ingestions/list-ingestion-runs Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-ingestion-runs.json" /%} ================================================================================ # List Ingestions Status URL: https://docs.statsig.com/api-reference/ingestions/list-ingestions-status Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-ingestions-status.json" /%} ================================================================================ # Read Ingestion URL: https://docs.statsig.com/api-reference/ingestions/read-ingestion Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-ingestion.json" /%} ================================================================================ # Read Ingestion Run URL: https://docs.statsig.com/api-reference/ingestions/read-ingestion-run Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-ingestion-run.json" /%} ================================================================================ # Read Ingestion Schedule URL: https://docs.statsig.com/api-reference/ingestions/read-ingestion-schedule Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-ingestion-schedule.json" /%} ================================================================================ # Update Ingestion Schedule URL: https://docs.statsig.com/api-reference/ingestions/update-ingestion-schedule Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-ingestion-schedule.json" /%} ================================================================================ # Update Ingestion Source URL: https://docs.statsig.com/api-reference/ingestions/update-ingestion-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-ingestion-source.json" /%} ================================================================================ # Create Key URL: https://docs.statsig.com/api-reference/keys/create-key Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-key.json" /%} ================================================================================ # Deactivate Key URL: https://docs.statsig.com/api-reference/keys/deactivate-key Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="deactivate-key.json" /%} ================================================================================ # Delete Key URL: https://docs.statsig.com/api-reference/keys/delete-key Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-key.json" /%} ================================================================================ # List Keys URL: https://docs.statsig.com/api-reference/keys/list-keys Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-keys.json" /%} ================================================================================ # Read Key URL: https://docs.statsig.com/api-reference/keys/read-key Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-key.json" /%} ================================================================================ # Rotate Key URL: https://docs.statsig.com/api-reference/keys/rotate-key Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="rotate-key.json" /%} ================================================================================ # Update Key URL: https://docs.statsig.com/api-reference/keys/update-key Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-key.json" /%} ================================================================================ # Add Layer Overrides URL: https://docs.statsig.com/api-reference/layers/add-layer-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="add-layer-overrides.json" /%} ================================================================================ # Create a Layer URL: https://docs.statsig.com/api-reference/layers/create-a-layer Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-a-layer.json" /%} ================================================================================ # Delete a layer URL: https://docs.statsig.com/api-reference/layers/delete-a-layer Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-a-layer.json" /%} ================================================================================ # Delete a Single Layer Override URL: https://docs.statsig.com/api-reference/layers/delete-a-single-layer-override Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-a-single-layer-override.json" /%} ================================================================================ # Delete a Single Layer User ID Override URL: https://docs.statsig.com/api-reference/layers/delete-a-single-layer-user-id-override Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-a-single-layer-user-id-override.json" /%} ================================================================================ # Delete Layer Overrides URL: https://docs.statsig.com/api-reference/layers/delete-layer-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-layer-overrides.json" /%} ================================================================================ # Get Layer Overrides URL: https://docs.statsig.com/api-reference/layers/get-layer-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-layer-overrides.json" /%} ================================================================================ # Get Layers URL: https://docs.statsig.com/api-reference/layers/get-layers Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-layers.json" /%} ================================================================================ # Get one layer URL: https://docs.statsig.com/api-reference/layers/get-one-layer Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-one-layer.json" /%} ================================================================================ # Lineage: List Experiment related to Layer URL: https://docs.statsig.com/api-reference/layers/lineage-list-experiment-related-to-layer Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="lineage-list-experiment-related-to-layer.json" /%} ================================================================================ # Partially update a layer URL: https://docs.statsig.com/api-reference/layers/partially-update-a-layer Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="partially-update-a-layer.json" /%} ================================================================================ # Update a layer URL: https://docs.statsig.com/api-reference/layers/update-a-layer Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-a-layer.json" /%} ================================================================================ # Update Layer Overrides URL: https://docs.statsig.com/api-reference/layers/update-layer-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-layer-overrides.json" /%} ================================================================================ # List recent events for the Logs Explorer URL: https://docs.statsig.com/api-reference/logs/list-recent-events-for-the-logs-explorer Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-recent-events-for-the-logs-explorer.json" /%} ================================================================================ # Cancel archive a metric URL: https://docs.statsig.com/api-reference/metrics/cancel-archive-a-metric Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="cancel-archive-a-metric.json" /%} ================================================================================ # Create Metric URL: https://docs.statsig.com/api-reference/metrics/create-metric Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-metric.json" /%} ================================================================================ # Create Metric Source URL: https://docs.statsig.com/api-reference/metrics/create-metric-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-metric-source.json" /%} ================================================================================ # Delete a metric URL: https://docs.statsig.com/api-reference/metrics/delete-a-metric Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-a-metric.json" /%} ================================================================================ # Delete Metric Source URL: https://docs.statsig.com/api-reference/metrics/delete-metric-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-metric-source.json" /%} ================================================================================ # Get SQL for a metric URL: https://docs.statsig.com/api-reference/metrics/get-sql-for-a-metric Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-sql-for-a-metric.json" /%} ================================================================================ # Lineage: List experiments related to Metric URL: https://docs.statsig.com/api-reference/metrics/lineage-list-experiments-related-to-metric Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="lineage-list-experiments-related-to-metric.json" /%} ================================================================================ # List All Metric Values URL: https://docs.statsig.com/api-reference/metrics/list-all-metric-values Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-all-metric-values.json" /%} ================================================================================ # List all Metrics URL: https://docs.statsig.com/api-reference/metrics/list-all-metrics Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-all-metrics.json" /%} ================================================================================ # List metric source URL: https://docs.statsig.com/api-reference/metrics/list-metric-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-metric-source.json" /%} ================================================================================ # Read Metric Definition URL: https://docs.statsig.com/api-reference/metrics/read-metric-definition Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-metric-definition.json" /%} ================================================================================ # Read Metric Definition by Name URL: https://docs.statsig.com/api-reference/metrics/read-metric-definition-by-name Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-metric-definition-by-name.json" /%} ================================================================================ # Read Metric Source URL: https://docs.statsig.com/api-reference/metrics/read-metric-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-metric-source.json" /%} ================================================================================ # Read Metric Source Metrics URL: https://docs.statsig.com/api-reference/metrics/read-metric-source-metrics Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-metric-source-metrics.json" /%} ================================================================================ # Read Single Metric Value URL: https://docs.statsig.com/api-reference/metrics/read-single-metric-value Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-single-metric-value.json" /%} ================================================================================ # Reload metric data URL: https://docs.statsig.com/api-reference/metrics/reload-metric-data Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="reload-metric-data.json" /%} ================================================================================ # Schedule a metric archive URL: https://docs.statsig.com/api-reference/metrics/schedule-a-metric-archive Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="schedule-a-metric-archive.json" /%} ================================================================================ # Unarchive a metric URL: https://docs.statsig.com/api-reference/metrics/unarchive-a-metric Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="unarchive-a-metric.json" /%} ================================================================================ # Update a metric URL: https://docs.statsig.com/api-reference/metrics/update-a-metric Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-a-metric.json" /%} ================================================================================ # Update Metric Source URL: https://docs.statsig.com/api-reference/metrics/update-metric-source Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-metric-source.json" /%} ================================================================================ # Audit Overrides URL: https://docs.statsig.com/api-reference/overrides/audit-overrides Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="audit-overrides.json" /%} ================================================================================ # Create Param Store URL: https://docs.statsig.com/api-reference/param-store/create-param-store Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-param-store.json" /%} ================================================================================ # Delete Param Store URL: https://docs.statsig.com/api-reference/param-store/delete-param-store Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-param-store.json" /%} ================================================================================ # Get Param Store URL: https://docs.statsig.com/api-reference/param-store/get-param-store Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-param-store.json" /%} ================================================================================ # List Param Stores URL: https://docs.statsig.com/api-reference/param-store/list-param-stores Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-param-stores.json" /%} ================================================================================ # Update Param Store URL: https://docs.statsig.com/api-reference/param-store/update-param-store Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-param-store.json" /%} ================================================================================ # Get Project Info URL: https://docs.statsig.com/api-reference/project/get-project-info Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-project-info.json" /%} ================================================================================ # Create Prompt URL: https://docs.statsig.com/api-reference/prompts/create-prompt Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-prompt.json" /%} ================================================================================ # Create Prompt Version URL: https://docs.statsig.com/api-reference/prompts/create-prompt-version Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-prompt-version.json" /%} ================================================================================ # Get Prompt URL: https://docs.statsig.com/api-reference/prompts/get-prompt Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-prompt.json" /%} ================================================================================ # List Prompts URL: https://docs.statsig.com/api-reference/prompts/list-prompts Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-prompts.json" /%} ================================================================================ # Start Prompt Version Evaluation Job URL: https://docs.statsig.com/api-reference/prompts/start-prompt-version-evaluation-job Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="start-prompt-version-evaluation-job.json" /%} ================================================================================ # Update Prompt (partial) URL: https://docs.statsig.com/api-reference/prompts/update-prompt-partial Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-prompt-partial.json" /%} ================================================================================ # Abort Pipeline Trigger URL: https://docs.statsig.com/api-reference/release-pipelines/abort-pipeline-trigger Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="abort-pipeline-trigger.json" /%} ================================================================================ # Approve Pipeline Trigger Phase URL: https://docs.statsig.com/api-reference/release-pipelines/approve-pipeline-trigger-phase Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="approve-pipeline-trigger-phase.json" /%} ================================================================================ # Create Pipeline URL: https://docs.statsig.com/api-reference/release-pipelines/create-pipeline Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-pipeline.json" /%} ================================================================================ # Delete Pipeline URL: https://docs.statsig.com/api-reference/release-pipelines/delete-pipeline Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-pipeline.json" /%} ================================================================================ # Fully Roll Out Pipeline Trigger URL: https://docs.statsig.com/api-reference/release-pipelines/fully-roll-out-pipeline-trigger Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="fully-roll-out-pipeline-trigger.json" /%} ================================================================================ # Get Pipeline URL: https://docs.statsig.com/api-reference/release-pipelines/get-pipeline Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-pipeline.json" /%} ================================================================================ # Get Pipeline Trigger URL: https://docs.statsig.com/api-reference/release-pipelines/get-pipeline-trigger Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-pipeline-trigger.json" /%} ================================================================================ # List Pipeline Triggers URL: https://docs.statsig.com/api-reference/release-pipelines/list-pipeline-triggers Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-pipeline-triggers.json" /%} ================================================================================ # List Pipelines URL: https://docs.statsig.com/api-reference/release-pipelines/list-pipelines Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-pipelines.json" /%} ================================================================================ # Pause Pipeline Trigger URL: https://docs.statsig.com/api-reference/release-pipelines/pause-pipeline-trigger Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="pause-pipeline-trigger.json" /%} ================================================================================ # Skip to Pipeline Trigger Phase URL: https://docs.statsig.com/api-reference/release-pipelines/skip-to-pipeline-trigger-phase Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="skip-to-pipeline-trigger-phase.json" /%} ================================================================================ # Unpause Pipeline Trigger URL: https://docs.statsig.com/api-reference/release-pipelines/unpause-pipeline-trigger Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="unpause-pipeline-trigger.json" /%} ================================================================================ # Update Pipeline URL: https://docs.statsig.com/api-reference/release-pipelines/update-pipeline Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-pipeline.json" /%} ================================================================================ # Get Reports URL: https://docs.statsig.com/api-reference/reports/get-reports Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-reports.json" /%} ================================================================================ # Create Role URL: https://docs.statsig.com/api-reference/roles/create-role Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-role.json" /%} ================================================================================ # Delete Role URL: https://docs.statsig.com/api-reference/roles/delete-role Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-role.json" /%} ================================================================================ # Get Role URL: https://docs.statsig.com/api-reference/roles/get-role Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-role.json" /%} ================================================================================ # List Roles URL: https://docs.statsig.com/api-reference/roles/list-roles Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-roles.json" /%} ================================================================================ # Update Role URL: https://docs.statsig.com/api-reference/roles/update-role Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-role.json" /%} ================================================================================ # Add IDs to Segment URL: https://docs.statsig.com/api-reference/segments/add-ids-to-segment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="add-ids-to-segment.json" /%} ================================================================================ # Add IDs to User Store ID List URL: https://docs.statsig.com/api-reference/segments/add-ids-to-user-store-id-list Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="add-ids-to-user-store-id-list.json" /%} ================================================================================ # Archive Segment URL: https://docs.statsig.com/api-reference/segments/archive-segment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="archive-segment.json" /%} ================================================================================ # Commit Segment Review URL: https://docs.statsig.com/api-reference/segments/commit-segment-review Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="commit-segment-review.json" /%} ================================================================================ # Create Segment URL: https://docs.statsig.com/api-reference/segments/create-segment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-segment.json" /%} ================================================================================ # Delete Segment URL: https://docs.statsig.com/api-reference/segments/delete-segment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-segment.json" /%} ================================================================================ # Get ID List Metadata URL: https://docs.statsig.com/api-reference/segments/get-id-list-metadata Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-id-list-metadata.json" /%} ================================================================================ # Get IDs in a Segment URL: https://docs.statsig.com/api-reference/segments/get-ids-in-a-segment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-ids-in-a-segment.json" /%} ================================================================================ # Get Segment URL: https://docs.statsig.com/api-reference/segments/get-segment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-segment.json" /%} ================================================================================ # List Segments URL: https://docs.statsig.com/api-reference/segments/list-segments Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-segments.json" /%} ================================================================================ # Remove IDs from Segment URL: https://docs.statsig.com/api-reference/segments/remove-ids-from-segment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="remove-ids-from-segment.json" /%} ================================================================================ # Remove IDs from User Store ID List URL: https://docs.statsig.com/api-reference/segments/remove-ids-from-user-store-id-list Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="remove-ids-from-user-store-id-list.json" /%} ================================================================================ # Reset ID List Segment URL: https://docs.statsig.com/api-reference/segments/reset-id-list-segment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="reset-id-list-segment.json" /%} ================================================================================ # Update Segment Rules URL: https://docs.statsig.com/api-reference/segments/update-segment-rules Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-segment-rules.json" /%} ================================================================================ # Get Project Settings URL: https://docs.statsig.com/api-reference/settings/get-project-settings Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-project-settings.json" /%} ================================================================================ # Get Reviews Settings URL: https://docs.statsig.com/api-reference/settings/get-reviews-settings Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-reviews-settings.json" /%} ================================================================================ # Get Roles Settings URL: https://docs.statsig.com/api-reference/settings/get-roles-settings Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-roles-settings.json" /%} ================================================================================ # Get Teams Settings URL: https://docs.statsig.com/api-reference/settings/get-teams-settings Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-teams-settings.json" /%} ================================================================================ # Update Project Settings URL: https://docs.statsig.com/api-reference/settings/update-project-settings Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-project-settings.json" /%} ================================================================================ # Update Reviews Settings URL: https://docs.statsig.com/api-reference/settings/update-reviews-settings Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-reviews-settings.json" /%} ================================================================================ # Update Roles Settings URL: https://docs.statsig.com/api-reference/settings/update-roles-settings Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-roles-settings.json" /%} ================================================================================ # Update Teams Settings URL: https://docs.statsig.com/api-reference/settings/update-teams-settings Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-teams-settings.json" /%} ================================================================================ # Create Tag URL: https://docs.statsig.com/api-reference/tags/create-tag Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-tag.json" /%} ================================================================================ # Delete Tag URL: https://docs.statsig.com/api-reference/tags/delete-tag Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-tag.json" /%} ================================================================================ # List Tags URL: https://docs.statsig.com/api-reference/tags/list-tags Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-tags.json" /%} ================================================================================ # Read Tag URL: https://docs.statsig.com/api-reference/tags/read-tag Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-tag.json" /%} ================================================================================ # Update Tag URL: https://docs.statsig.com/api-reference/tags/update-tag Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-tag.json" /%} ================================================================================ # Bulk Assign Target Apps URL: https://docs.statsig.com/api-reference/target-app/bulk-assign-target-apps Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="bulk-assign-target-apps.json" /%} ================================================================================ # Create Target App URL: https://docs.statsig.com/api-reference/target-app/create-target-app Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-target-app.json" /%} ================================================================================ # Delete Target App URL: https://docs.statsig.com/api-reference/target-app/delete-target-app Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-target-app.json" /%} ================================================================================ # List Target Apps URL: https://docs.statsig.com/api-reference/target-app/list-target-apps Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-target-apps.json" /%} ================================================================================ # Read Target App URL: https://docs.statsig.com/api-reference/target-app/read-target-app Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="read-target-app.json" /%} ================================================================================ # Update Target App URL: https://docs.statsig.com/api-reference/target-app/update-target-app Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-target-app.json" /%} ================================================================================ # Create Unit ID Type URL: https://docs.statsig.com/api-reference/unit-id-types/create-unit-id-type Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-unit-id-type.json" /%} ================================================================================ # Delete Unit ID Type URL: https://docs.statsig.com/api-reference/unit-id-types/delete-unit-id-type Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-unit-id-type.json" /%} ================================================================================ # Get Unit ID Type URL: https://docs.statsig.com/api-reference/unit-id-types/get-unit-id-type Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-unit-id-type.json" /%} ================================================================================ # List Unit ID Types URL: https://docs.statsig.com/api-reference/unit-id-types/list-unit-id-types Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-unit-id-types.json" /%} ================================================================================ # Update Unit ID Type URL: https://docs.statsig.com/api-reference/unit-id-types/update-unit-id-type Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-unit-id-type.json" /%} ================================================================================ # Get Report in CSV format URL: https://docs.statsig.com/api-reference/usage/get-report-in-csv-format Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-report-in-csv-format.json" /%} ================================================================================ # Create Team URL: https://docs.statsig.com/api-reference/users/create-team Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="create-team.json" /%} ================================================================================ # Delete Team URL: https://docs.statsig.com/api-reference/users/delete-team Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-team.json" /%} ================================================================================ # Get Team URL: https://docs.statsig.com/api-reference/users/get-team Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-team.json" /%} ================================================================================ # Get user by email URL: https://docs.statsig.com/api-reference/users/get-user-by-email Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-user-by-email.json" /%} ================================================================================ # Get user by ID URL: https://docs.statsig.com/api-reference/users/get-user-by-id Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-user-by-id.json" /%} ================================================================================ # Invite users URL: https://docs.statsig.com/api-reference/users/invite-users Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="invite-users.json" /%} ================================================================================ # List Teams URL: https://docs.statsig.com/api-reference/users/list-teams Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-teams.json" /%} ================================================================================ # List Users URL: https://docs.statsig.com/api-reference/users/list-users Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="list-users.json" /%} ================================================================================ # Update team URL: https://docs.statsig.com/api-reference/users/update-team Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-team.json" /%} ================================================================================ # Update user URL: https://docs.statsig.com/api-reference/users/update-user Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-user.json" /%} ================================================================================ # Update Warehouse Connection Parameters URL: https://docs.statsig.com/api-reference/warehouse-connections/update-warehouse-connection-parameters Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="update-warehouse-connection-parameters.json" /%} ================================================================================ # Statsig CLI ("Siggy") URL: https://docs.statsig.com/statsigcli/introduction Updated: 2026-07-11 ================================================================================ ## How the Statsig CLI works The Statsig CLI is a command-line interface that helps with management of Feature Gates, Experiments, and Dynamic Configs within the Statsig platform. This tool lets you create, manage, and delete configs from the command line, and you can also use it within scripts. ## Why use the Statsig CLI ### Efficiency The Statsig CLI provides a fast way to interact with Statsig, reducing the need for manual actions through the Console interface. It allows quick execution of tasks with simple commands. ### Automation You can integrate the CLI into CI/CD pipelines to automate the management of Feature Gates and Experiments. ### Consistency Using the CLI enables scriptable and repeatable actions, promoting best practices and reducing the risk of human error. ## Installation ### Prerequisites * Node.js (version 14 or higher) * npm (Node Package Manager) ### Install and configure 1. Install the Statsig CLI using npm: ```bash $ npm install -g @statsig/siggy ``` 2. Check install ```bash $ siggy --version ``` {% callout type="info" %} If you get a `command not found` error, add your node global bin folder to your path. Run `export PATH=$PATH:$(npm get prefix -g)/bin` to do this. If that doesn't work, run the CLI by prefixing `npx` in the command line: ```bash $ npx siggy --version ``` {% /callout %} 3. Configure the API keys by running: ```bash $ siggy config -c <console-api-key> $ siggy config -k <client-api-key> ``` {% callout type="info" %} Retrieve these keys from your Statsig project. Log in to Statsig Console at https://console.statsig.com and go to the Settings page (https://console.statsig.com/settings). Select the **Keys & Environments** panel within *Project Settings*, then copy both the Console API Key and Client API Key, and paste them in the Settings dialog. ![Statsig Console API keys configuration screen](/images/guides/sidecar-experiments/setup/754cc245-1821-4f75-a87d-08b536422587.png) {% /callout %} ### Next steps: [Commands in CLI](/statsigcli/commands) ================================================================================ # Commands in Statsig CLI URL: https://docs.statsig.com/statsigcli/commands Updated: 2026-07-11 ================================================================================ ## Getting help Running `siggy` with no arguments prints usage and the currently supported commands and options. ```bash $ siggy # Response Usage: siggy [options] [command] Statsig CLI For information on schema, see /console-api/introduction Options: -V, --version output the version number -h, --help display help for command Commands: config [options] view/edit configuration settings gates create/list/edit gates dyncon create/list/edit dynamic configs segments create/list/edit segments experiments create/list/edit experiments help [command] display help for command ``` Calling each command with the `--help` option prints the help content for that command. ```bash $ siggy gates --help # Response Usage: siggy gates [options] [command] create/list/edit gates Options: -h, --help display help for command Commands: create <gate-name> create a new feature gate get <gate-id> retrieve gate details list [options] list all gates update <gate-id> <gate-properties-json> update a gate delete [options] <gate-id> delete a gate check [options] <gate-id> check if the current state of the gate for a user help [command] display help for command ``` ## Other commands You can list, create, update, and delete entities like Gates, Experiments, Dynamic Configs, Segments, and more. Help content lives in code, so running the CLI with the `--help` option gives the most current information. For commands that require a JSON body, refer to the schema in the [Console API](/console-api/introduction). ================================================================================ # Walkthrough guide for Gate Management with CLI URL: https://docs.statsig.com/statsigcli/gate-management Updated: 2026-07-11 ================================================================================ {% callout type="info" %} This guide assumes you have Statsig CLI installed and configured with the required API keys. Refer to the [Statsig CLI Overview](/statsigcli/introduction) to get started. {% /callout %} ## Create a new gate Create a new empty gate with no rules using the `create` command on gates. ```bash $ siggy gates create my-first-gate # Response { id: 'my-first-gate', name: 'my-first-gate', description: '', idType: 'userID', lastModifierID: '..', ... } ``` ## Update rules Update rules by passing a rule object to the `update` command. This replaces the existing rules entirely. ```bash $ siggy gates update my-first-gate '{ "rules": [ { "name": "all employees", "passPercentage": 100, "conditions": [ { "type": "email", "operator": "str_contains_any", "targetValue": [ "@statsig.com" ] } ] } ] }' # Response { id: 'my-first-gate', name: 'my-first-gate', ... rules: [ { id: '729Qb4MVDs0YrIjNR5aOSm', name: 'all employees', passPercentage: 100, conditions: [ { type: 'email', targetValue: [ '@statsig.com' ], operator: 'str_contains_any' } ], } ], ... } ``` ## Check if the gate works When you have configured the Client API key correctly, you can invoke the gate for different users to validate that it works. Passing no user object creates an empty user object and evaluates the gate against it. ```bash $ siggy gates check my-first-gate # Response { name: 'my-first-gate', value: false, rule_id: 'default', group_name: null } ``` You can also pass a user object crafted as JSON using the `--user` option ```bash $ siggy gates check my-first-gate --user '{ "email": "siggy@statsig.com" }' # Response { name: 'my-first-gate', value: true, rule_id: '729Qb4MVDs0YrIjNR5aOSm', group_name: null } ``` ## List all gates ```bash $ siggy gates list # Response [ { id: 'my-first-gate', name: 'my-first-gate', lastModifiedTime: 1718222637700, lastModifierName: 'CONSOLE API' }, { id: 'from_siggy', name: 'from_siggy', lastModifiedTime: 1717807438090, lastModifierName: 'CONSOLE API' } ... ] ``` ## Delete gate Delete a gate using the CLI. By default, a confirmation prompt requires your input. ```bash $ siggy gates delete my-first-gate # Response Are you sure you want to delete gate (id: my-first-gate)? (y/n): ``` Use the `--force` option to skip the confirmation prompt. ```bash $ siggy gates delete my-first-gate --force # Response Gate deleted successfully. ``` ================================================================================ # HTTP API URL: https://docs.statsig.com/http-api/overview Updated: 2026-07-11 ================================================================================ {% callout type="warning" %} While this HTTP API is available for direct use, Statsig strongly recommends using an official SDK for your programming language whenever possible. SDKs offer better performance, automatic error handling, and type safety. They also provide a more idiomatic integration with your codebase. Only use this HTTP API directly if there isn't an SDK available for your language or if you have a specific use case that requires direct API access. {% /callout %} Before calling the server APIs, complete the following steps: {% steps %} {% step title="Create a free account on Statsig" %} Create a free account on the [Statsig sign-up page](https://statsig.com). An account gives you access to the Statsig Console, where you can manage your Feature Gates, Dynamic Configs, and Experiments. You can also invite others to collaborate on your Statsig projects so they can interact with your gates and configs. {% /step %} {% step title="Get an API key from the Statsig Console" %} An API key is required in every API request. There are two types of API keys you can use with the HTTP API: * **Server-side secret Key**: Used only from secure servers and should never be exposed in client-side code. * **Client-SDK Key**: Safe to embed in mobile apps and front-end web apps. {% callout type="tip" %} If you're working with server-side logic or sensitive data, use the Server-side secret Key. If you're in doubt or working with public-facing code, use the Client-SDK Key. {% /callout %} {% /step %} {% step title="Issue API requests" %} The API is built on HTTPS. Authenticate using the `statsig-api-key` header. All API requests use the POST method, and parameters are passed as a JSON object in the request body. {% callout type="tip" %} **Why POST?** Even for fetching data, Statsig uses POST to ensure secure and flexible transmission of user-specific data (e.g., configurations or experiment results). {% /callout %} Statsig automatically logs exposure events whenever you call the APIs. These exposure events help attribute downstream events to experiments or feature gates and are used to calculate metrics like analytics lift. {% /step %} {% /steps %} ================================================================================ # Get Ranked List for Contextual Bandit URL: https://docs.statsig.com/api-reference/autotune/get-ranked-list-for-contextual-bandit Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-ranked-list-for-contextual-bandit.json" /%} ================================================================================ # Get Dynamic Config or Experiment URL: https://docs.statsig.com/api-reference/dynamic-configs/get-dynamic-config-or-experiment Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-dynamic-config-or-experiment.json" /%} ================================================================================ # Log Custom Events URL: https://docs.statsig.com/api-reference/events/log-custom-events Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="log-custom-events.json" /%} ================================================================================ # Log Custom Exposure Events URL: https://docs.statsig.com/api-reference/events/log-custom-exposure-events Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="log-custom-exposure-events.json" /%} ================================================================================ # Check Feature Gate(s) URL: https://docs.statsig.com/api-reference/feature-gates/check-feature-gates Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="check-feature-gates.json" /%} ================================================================================ # Get Layer Parameters URL: https://docs.statsig.com/api-reference/layers/get-layer-parameters Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-layer-parameters.json" /%} ================================================================================ # Workspace Management Overview URL: https://docs.statsig.com/access-management/introduction Updated: 2026-07-11 ================================================================================ Statsig provides several access management solutions as you scale adoption across your team, organization, or company. Basic settings include automatically adding new users with the same email domain to your project. Statsig also supports SSO to simplify inviting your team to your projects. To get started, learn more about Statsig's [organization](/access-management/organizations) and [project](/access-management/projects) abstractions. *** ## SSO vs SCIM In many enterprise environments, organizations use [SSO](/access-management/sso/overview) and [SCIM](/access-management/scim/overview) together to improve security and usability. * SSO handles authentication, allowing users to log in once to access multiple applications. * SCIM ensures consistent management of user accounts and permissions across those applications. ### Example scenario * Onboarding a new employee: * SCIM: Automatically provisions the employee’s user accounts in all necessary applications based on their role. * SSO: Allows the employee to access all these applications with a single set of login credentials. * Offboarding an employee: * SCIM: Automatically deactivates or deletes user accounts, removing access. * SSO: No longer authenticates the user after their credentials are disabled. ### Key differences * SSO centralizes authentication across multiple applications to reduce login steps. * SCIM automates the management of user identities and attributes across different systems. Together, SSO and SCIM give organizations a more secure and efficient approach to identity and access management. ================================================================================ # API Keys URL: https://docs.statsig.com/access-management/api-keys Updated: 2026-07-11 ================================================================================ ## API key types There are three main types of API keys: 1. **[Client API Key](#client-api-keys)**: Intended for getting configuration and logging events on the client side. 2. **[Server Secret Key](#server-secret-keys)**: Intended for getting configuration and logging events on the server side. 3. **[Console API Key](#console-api-keys)**: The most powerful key, intended for server-side use for full CRUD operations on your Statsig project. Generate API keys in the Statsig Console by going to **Project Settings** > [Environments & Keys](https://console.statsig.com/api_keys). ### Client API keys Client API keys (Statsig Client SDK keys) are required to initialize all Statsig client SDKs. Use them in a client environment, such as a mobile app or a web app, where the key itself can't be secret. Client API keys have access to the following: * /initialize endpoint, which returns all evaluated gates/configs/experiments/layers for a given user, with the names hashed. * /log\_event endpoint, which logs events to Statsig. Because Client API Keys can't access actual names, and because the gates/configs/experiments/layers are all pre-evaluated, a Client API Key can't access the project definition. This means that any conditions you create in the console (for example, for a gate to pass for certain users or email addresses) aren't accessible through a Client API Key. ### Server secret keys Server Secret keys (Statsig Server SDK keys) are required to initialize all Statsig server SDKs. Use them on webservers or in server scripts. They have access to the following: * /download\_config\_specs endpoint, which downloads the configuration for a project * /log\_event endpoint, which logs events to Statsig. ### Console API keys Console API keys (Statsig CAPI keys) are the most powerful keys in your project. They can read all project configuration and also create, update, and delete entities in your project. They have access to the entire suite of [console api](/console-api/introduction) endpoints. You can also create **Personal Console API Keys** that are scoped to your role in Statsig. If your role has limited access, the same access conditions apply to the personal key. Personal keys are intended for multi-user projects where each user can have their own keys for clean access and audit logs. You can control the ability to generate personal keys in the [organization info settings tab](https://console.statsig.com/settings?tab=organization). ## Additional configuration options Use [target apps](/sdks/target-apps) with server secret keys and client keys to control which gates, configs, experiments, and layers are accessible through each key. You can also create [per-environment API keys](/guides/using-environments#per-environment-api-keys) to control which rules Statsig sends to the SDK based on the environment. Both client and server secret keys can also access individual entities through the HTTP API (/check\_gate, /get\_config, /get\_layer). ### Client keys with server permissions Client keys can also access the download\_config\_specs endpoint through a scope you can add to the key. Use this option only with client local evaluation SDKs, such as the [js-on-device-eval sdk](/client/jsOnDeviceEvaluationSDK), which Statsig recommends only for specific situations. Creating a key with this scope requires additional consideration. Your entire project scope is exposed to clients that can access this key: the names and configurations of all experiments and feature flags accessible by your client key are visible. This risk may be acceptable (many other experimentation platforms operate this way by default), but you may choose to add additional controls to prevent certain information from being included, such as plaintext email addresses used in targeting or confidential config names. Recommended steps: 1. Consider whether on-device evaluation is appropriate for your use case. While on-device evaluation has [some benefits](/client/jsOnDeviceEvaluationSDK#pros), precomputed SDKs are the default solution. Ask questions in the [Slack channel](https://statsig.com/slack). 2. Educate Statsig users on your team about the types of information to include in configs based on your info security policy. 3. Use [target apps](/sdks/target-apps) to filter your configs to only the ones needed for the client side, both for privacy and performance. ================================================================================ # Organization Settings & Administration URL: https://docs.statsig.com/access-management/organizations Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Organizations and their related features are for Enterprise contracts only. Reach out to the support team, your sales contact, or the [Slack community](https://statsig.com/slack) to enable Enterprise features. {% /callout %} ## Creating an organization Statsig creates an organization for companies with an Enterprise contract. When you move a project to the organization: * All existing **members** receive an invitation to join the organization. * All existing project **administrators** remain the same. * Statsig adds the project as **Open**, meaning any registered Statsig user with the same email domain as the project owner can access the project. You can change this setting in the **Project Settings** tab. If you don't move a project to the organization, you may lose access to it because Statsig archives it. ## Managing organizations Creating an organization automatically makes you the organization's **Owner** and **Admin**. There can only be one organization Owner but you can have as many Admins as needed. You can view your Organization page by going to [Account Settings](https://console.statsig.com/account_settings) in the settings tab and selecting **Manage** next to your organization. {% figure %} ![Account Settings organization management interface](/images/access_management_account_info.png) {% /figure %} #### Organization information Your [**Info** setting's **Organization** tab](https://console.statsig.com/settings?tab=organization) includes the organization's name, SSO configuration, and other settings on access management and security settings. As the organization's **Admin**, you can enable or disable SSO for all projects in the organization from this one place. {% figure %} ![Organization information interface](/images/access_management_organization_info.png) {% /figure %} #### Organization members All members in the organization can view all current members in the organization from [Members Settings](https://console.statsig.com/settings/members?tab=organization). As the organization's **Owner** or **Admin**, you can also manage its membership by: * Promoting members to the organization's **Admin** * Removing members from the organization * Managing invitations including sending, viewing, and canceling invitations as well as tracking all pending invitations {% figure %} ![Organization member management interface](/images/access_management_organization_members.png) {% /figure %} To change a member's roles within the organization or remove a member, select the member's name using the checkbox in the list and select one of the icons at the top right of the list. ## Managing projects Members of an organization can view all public projects in the organization. As the organization's **Owner** or **Admin**, you can view every project in the organization as well as **archive** the project if it's no longer necessary. {% figure %} ![Organization project administration interface](/images/access_management_project_view.png) {% /figure %} ================================================================================ # Project Access Management URL: https://docs.statsig.com/access-management/projects Updated: 2026-07-11 ================================================================================ {% callout type="info" %} This guide applies only to on-demand customers. If your organization has set up SSO, refer to the [SSO Guides](/access-management/sso/overview) for information about managing access permissions through SSO. {% /callout %} In the [Basic Settings page](https://console.statsig.com/settings) you can configure who has access to your project. Each person invited to a project receives a role that specifies their level of access. ## Roles The different Project Roles available are: | Role | Description | |-----------|-------------| | Read-Only | Users with this role can only read data on the project. This includes reading gate and dynamic config configurations, experiment data, and metrics. Any actions users with these roles take that attempt to edit the configuration for the project fail. | | Member | Users with this role can read data for a project and edit configurations within the project. This includes being able to create and modify: Feature Gates, Dynamic Configs, Holdouts, Experiments+, etc. | | Admin | Users with this role have the same access as `Member` but can also modify project access settings. This includes inviting new users to the project and changing roles for existing users of the project. | | Owner | Only the user that created a project receives the `Owner` role. The Owner has the same access as `Admin` but can also delete the project and configure automatic invitations for the project. If you need to change the Owner of a project, the current owner can change it by going to *Settings > Project Members & Invites*, selecting the person, and editing their role to Owner. | ## Custom roles {% callout type="info" %} Custom roles are for Enterprise contracts only. Reach out to the support team, your sales contact, or the [Slack channel](https://statsig.com/community) to enable Enterprise features. {% /callout %} Enterprise customers can customize roles used to assign permissions in Statsig. You can create new roles beyond Admin, Member, and Read-Only and choose what permissions these roles have. Common use cases include creating a Metrics Admin role or a Warehouse Admin role (for Statsig Warehouse Native). {% figure %} ![Custom roles configuration interface](/images/access-management/projects/1b5a0601-6311-401e-87e3-5d1055a025e7.png) {% /figure %} ## Automatic project invitations To simplify sending invitations for a project, you can allow users creating a new Statsig account to automatically join your project if their work email domain matches the owner's. For example, a project owner with an `@statsig.com` email can enable all new users signing up with an `@statsig.com` email to automatically join their project. To enable this feature: 1. Go to your [Project Basic Settings page](https://console.statsig.com/settings). 2. Click **Edit Project Settings**. 3. Toggle the checkbox labeled `Anyone who signs up with the same email domain can join` and choose the role that users receive. {% figure %} ![Project settings automatic invitation configuration](/images/access-management/projects/128581866-b5856f1d-9ac6-462d-a57e-22320a093457.png) {% /figure %} ================================================================================ # Teams URL: https://docs.statsig.com/access-management/teams Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Teams are an Enterprise-only feature. If you are on the Developer or Pro tiers, this guide does not apply to you. To upgrade to Enterprise, contact the Statsig team at [statsig.com/contact/demo](https://www.statsig.com/contact/demo). {% /callout %} ## How Teams works For larger organizations, the Teams feature adds an organizational and permissions layer on top of a project. Teams are configured at the project level (not the organization level), and all project admins can edit them by default. After you configure teams and assign a user to a team, any config (gates, experiments, metrics, etc.) that user creates is associated with their team and inherits that team's settings. Users who are members of multiple teams can choose which team to associate their config with at creation time. ## Creating teams To create a team, navigate to *Settings > People > Teams*. Select **+Create**, then name the team and add members. You can add or remove members from a team at any time, not only during initial team creation. Each team has a *Members* page and a *Settings* page. The *Members* page lists all team members, including each member's project role and team role (member or admin). You can promote or remove team members. {% figure %} ![Statsig Teams page showing member list with project and team roles](/images/team_create_1.png) {% /figure %} ## Configuring team settings At the project level, you can require all config creations to be associated with a team using the "Require teams" setting under *Settings > Product Configuration > General*. This blocks anyone not yet assigned to a team from creating a config, so enable this setting only after you've added all project members to at least one team. {% figure %} ![Project configuration toggle requiring configs to be associated with teams](/images/access-management/teams/d11ed451-7fff-4031-b117-4cd05cb3b960.png) {% /figure %} Each team has the following configurable settings: **Default Monitoring Metrics/Scorecard Metrics:** Pre-configure a set of metrics to add to every new gate, experiment, or holdout at the team level. These might include top-line company metrics every team must monitor (such as revenue or app performance) and team-specific KPIs that all rollouts and experiments should track. {% figure %} <img src="/images/access-management/teams/16f0ccfd-05d6-4fb2-8992-ec8780ff3778.png" alt="Statsig Team settings showing default monitoring metrics selection" width="881" /> {% /figure %} **Require Reviews:** Require reviews at the individual team level, if reviews aren't already required at the project level. This setting doesn't appear if reviews are already required at the project level (controlled under *Settings > Product Configuration > Reviews*). {% figure %} ![Team require reviews option within settings](/images/access-management/teams/554e0f6a-c9ce-466a-b5a4-db94b0cb24fa.png) {% /figure %} **Default Allowed Reviewers:** Controls who can review and approve changes to a team’s configs. There are three options: “Anyone in the Project” (least restrictive), “Team Members Only” (keeps reviews within the team), and “Team or Project Admins Only” (most restrictive). Team-based review configurations layer on top of [role-based review settings](/guides/setting-up-reviews#enforcing-team-reviews). For example, if your role has permission to approve reviews and your team has review settings set to “Team members only”, an approver must both hold a role with review approval permission and be a member of the team to approve a review for that team’s config. {% figure %} <img src="/images/access-management/teams/39263840-cb37-4286-b30a-c6d255f218d0.png" alt="Default allowed reviewers dropdown specifying who can approve changes" width="924" /> {% /figure %} **Create/Edit Configs and Metrics:** Controls which members of a team can edit or create configs tagged with the team. There are two options: "Anyone in the Project" (no restrictions; anyone can edit the team's configs) or "Team Members Only." {% figure %} <img src="/images/access-management/teams/df517c17-acdd-4516-a9ee-bc612a0bfdc9.png" alt="Create and edit configs permissions for team members only or entire project" width="919" /> {% /figure %} **Default Target Applications:** Automatically applies any assigned Target Applications to all configs associated with this team. This setting only affects which Target Applications Statsig adds to the config by default at creation time; you can edit or override the list as needed. {% figure %} ![Team default target applications selector](/images/access-management/teams/2cf75c17-9441-4645-beef-feb57578fb46.png) {% /figure %} **Default Holdout:** Teams often need to measure the cumulative impact of new features and experiments over a quarter or half year. Associate a default Holdout with a team to automate this. Statsig then automatically adds all subsequent configs associated with the team to that default Holdout. {% figure %} ![Team default holdout configuration UI](/images/access-management/teams/8f5a2226-c716-4882-939a-8ba53e852b22.png) {% /figure %} ## How Teams appear throughout the console After you associate a user with a team, every config that user creates is associated with their team by default. Users on multiple teams can choose which team to associate their config with at creation time. Statsig then applies the selected team’s settings to that config. {% figure %} ![Config creation screen with team selection dropdown](/images/access-management/teams/dbdc681d-3918-4da1-b9e3-6e43ecb744f8.png) {% /figure %} Every config has a “Team” field in the header. This field is separate from “Owner”: “Owner” is a single individual, while “Team” is a group of individuals. The Team field doesn't update automatically if the Owner moves to a different team. You must change teams manually at the config level, subject to review requirements. {% figure %} ![Config header showing Team field separate from Owner](/images/access-management/teams/7fc0faf7-c059-451b-8a44-6f58e526ef8e.png) {% /figure %} Every user can filter gate, experiment, and metric lists and the Home Feed by team. The Home Feed defaults to a user's team or teams, so the most relevant content appears first. {% figure %} ![Console list filters for gates experiments and metrics by team](/images/access-management/teams/299cc0ad-5878-454b-9186-e005f8f442b5.png) {% /figure %} ================================================================================ # Initial Setup Guide of your Workspace URL: https://docs.statsig.com/access-management/guide Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Organizations and their related features are for Enterprise contracts only. Reach out to the support team, your sales contact, or the [Slack channel](https://statsig.com/slack) to enable Enterprise features. {% /callout %} ## How this guide is organized This guide covers the essential configurations needed to get started with Statsig on an Enterprise plan. It's written for **organization admins** who need to set up the initial environment and configure access controls for their teams, and includes **best practices** to increase operational efficiency. ### Statsig workspace structure {% figure %} ![Statsig structure diagram](/images/access_management_structure_diagram.png) {% /figure %} Statsig provides three constructs to help you organize your workspace. **Organization** is an enterprise-level environment that allows companies to create project(s) and bring members to work inside the project. **Project** is a workspace within the organization where the configs (for example, feature gates, experiments, layers), metrics, and SDK keys your team creates live. **Team** is a group of members at the project level that can help your organization manage the permissions and ownership of resources. #### Recommended structure In Statsig, each project within the organization is isolated from the others. Statsig doesn't share resources or data among different projects, even when they're part of the same organization. The recommended structure for most customers is a single project within the organization, where multiple teams contribute and collaborate. Different teams and functions can stay well-organized within a single project by using features such as **[teams](/access-management/teams), [roles](/access-management/projects#roles), [tags](/access-management/tags),** and **[templates](/experiments/templates/templates)**. This structure also avoids costly migrations if projects ever need to be consolidated for cross-functional collaboration. {% figure %} ![Statsig structure diagram anti pattern](/images/access_management_structure_anti_pattern.png) {% /figure %} Statsig has rich support for **environments** within resources. Creating multiple projects to manage lower environments separately from production is an *anti-pattern*. ### Setup guide #### Step 1: Setting up the organization When you sign an enterprise contract with Statsig, Statsig provisions your existing account into an enterprise account. After provisioning, Statsig adds the [tab for Organization](https://console.statsig.com/settings?tab=organization) to your account along with additional security and governance settings. #### Step 2: Setting up SSO Statsig recommends configuring [**SSO**](/access-management/sso/overview) (Single Sign-On) for your Statsig organization to reduce login steps and improve security. Statsig supports any Identity Provider that implements the **OIDC protocol** for SSO, such as [Okta](/access-management/sso/okta_sso), [Microsoft Entra ID](/access-management/sso/azuread), [Google](/access-management/sso/google), and more. Also consider integrating [**SCIM**](/access-management/scim/overview) (System for Cross-domain Identity Management) if you're using Okta as your identity provider. Statsig assigns new users provisioned through SSO the *Member* role unless you're using SCIM. #### Step 3: Creating your project {% figure %} ![Organization project administration interface](/images/access_management_project_view.png) {% /figure %} A project in Statsig is a workspace that contains everything you and your team create, including configs (for example, feature gates, experiments, dynamic configs, layers), metrics, integrations, and more. When creating a new project, you can set its type to *Open*, which allows anyone in the organization to join freely, or to *Closed*, which allows people to join only by invitation or request. #### Step 4: Setting up roles {% figure %} ![Project roles interface](/images/access_management_roles.png) {% /figure %} Statsig assigns each person a role that defines their level of access within the project and organization. Review the permissions for each predefined role so you can [assign the appropriate roles](/access-management/organizations#organization-members) or create a custom role that aligns with your organization's needs. Common examples of custom roles include a *Metrics Admin* (responsible for managing metrics) and a *Data Warehouse Admin* (in Warehouse Native projects, responsible for managing warehouse connections). #### Step 5: Inviting your team to the organization {% figure %} ![Project invite interface](/images/access_management_invite.png) {% /figure %} After you configure SSO, your teams can join the Statsig organization by signing in from the console and completing the request flow within your identity provider. If you haven't set up SSO, you can invite individual members to your organization or project in the console. #### Step 6: Creating teams within the project You can create [Teams](/access-management/teams) within the project to help with organization and ownership. After you configure a team and add users, Statsig associates any configs created with the user's team and automatically inherits all default settings and templates from that team. {% figure %} ![Team filter in experiments](/images/access_management_team_filter.png) {% /figure %} Teams become particularly useful as multiple teams begin to use Statsig because teams help with organization and governance and provide a set of defaults that simplify onboarding of new users. As a best practice, require all resource creations to be attached to a team and have team admins (for example, tech leads and engineering managers) add people to their teams so members can start creating configs and metrics. ### Best practices #### Setting up review policies {% figure %} ![Review setting interface](/images/access_management_reviews.png) {% /figure %} Within Statsig, you can enable [reviews](/guides/setting-up-reviews) for any changes to configs and metrics, requiring a reviewer to approve the change before it deploys to production. You can **customize** your review policies to align with your organization’s needs. For example, you can set up a team of reviewers by giving their roles permission to approve reviews. You can also give certain roles (for example, on-call engineers) the ability to self-approve, or require reviews for the production environment only. Each **team** also has team-level **review settings** that can require reviews for configs and metrics owned by a specific team, and allow only members or admins (with roles that permit approving reviews) of that team to approve the reviews. #### Setting up your API keys {% figure %} ![SDK environment interface](/images/access_management_sdk_env.png) {% /figure %} In Statsig, you have **Client API Keys** to initialize all Statsig [client SDKs](/client/introduction) and **Server Secret Keys** to initialize all Statsig [server SDKs](/server/introduction). Statsig recommends a **"Crawl, Walk, Run"** approach when configuring your API keys: * **Crawl:** Create API keys scoped to a specific environment within Statsig for [environment-based evaluation](/guides/using-environments#1-environment-specific-sdk-keys). * **Walk:** Create different API keys for each frontend client (for example, iOS, Android, and Web). * **Run:** At scale, create separate API keys at the service level so each backend service and its environments have their own keys. This setup works with [Target Apps](/sdks/target-apps) to unlock additional performance and security benefits. #### Configuring Target Apps for API keys {% figure %} ![Target apps interface](/images/access_management_target_apps.png) {% /figure %} A [target app](/sdks/target-apps) is an attribute you can associate with your SDK keys and configs. Target apps let you precisely set the scope of configs accessible from each SDK key, which can also reduce payload size. Set up target apps as you scale Statsig usage for [additional performance and security](/sdks/target-apps#motivation). #### Setting up notifications Statsig has a [Slack integration](/integrations/slack) that sends real-time notifications about activities, status changes, and other alerts directly to your Slack workspace. You can also configure [email notifications](https://www.statsig.com/updates/update/email-notifs) for alerts, reviews, reports, and more. ================================================================================ # Single Sign-On With OIDC URL: https://docs.statsig.com/access-management/sso/overview Updated: 2026-07-11 ================================================================================ {% callout type="info" %} SSO is an Enterprise feature. Reach out to the support team, your sales contact, or through the [Slack community](https://statsig.com/slack) if you need to enable Enterprise features as you try out Statsig. {% /callout %} *This documentation assumes that you already have an OIDC Provider up and running.* You can configure Single Sign-On (SSO) with OIDC for your Statsig Organization to continue using your company's identity store with Statsig and reduce the steps for inviting your team to your Projects. Statsig automatically provisions new users after they authenticate with your Identity Provider. Organizations are an Enterprise Tier feature. If your SSO requires multi-factor authentication (MFA), Statsig automatically requires MFA when your users sign into Statsig with SSO enabled. ## Supported Providers Statsig supports any Identity Provider (IdP) that implements the OIDC protocol for SSO. Custom documentation is available for the following OIDC providers: * [Okta](/access-management/sso/okta_sso) * [Microsoft Entra ID (AzureAD)](/access-management/sso/azuread) * [Google](/access-management/sso/google) * Ping Identity * Be sure to include `openid` and `email` in the scopes * OneLogin ## Configure SSO with OIDC ### In your Identity Provider Specify the following for your Statsig App: * Sign-in redirect URI: https://console.statsig.com/sso/oidc (and https://latest.console.statsig.com/sso/oidc if possible) * Sign-out redirect URI: https://console.statsig.com * Sign-in URI: https://console.statsig.com/sso To enable SSO in Statsig, collect the following from your OIDC Provider: * OIDC Domain * Client ID * Client Secret ### In Statsig Console After you have obtained all of the information from your OIDC Provider: 1. Navigate to your Organization's [`Info Settings` page](https://console.statsig.com/settings?tab=organization) and click the `Enable` button for Single Sign-on. {% callout type="note" %} An `Owner`/`Admin` role in your Statsig organization is required to configure SSO on Statsig {% /callout %} {% figure %} ![SSO enable button in organization settings](/images/access-management/sso/overview/a31ba14d-9476-4897-afc6-5c39c2f932c9.png) {% /figure %} 2. Provide the information acquired from your OIDC Provider into the fields in the dialog and click `Enable`. {% figure %} ![SSO configuration dialog with OIDC provider fields](/images/access-management/sso/overview/d2143c48-f144-4544-a77b-af2e2d486cc8.png) {% /figure %} 3. After clicking **Enable**, Statsig displays an SSO link that you can send to your team to allow them to log in to Statsig through your OIDC Provider. {% figure %} ![SSO link generated for team login](/images/access-management/sso/overview/e3743107-2e26-4944-b2fb-f90536163b6f.png) {% /figure %} By default, Statsig assigns users provisioned through SSO the "Member" role in the organization. If the organization has only one open project, users who sign in through an SSO link automatically join any Projects that have SSO enabled with the same OIDC Provider. If there are multiple projects, Statsig adds users to the organization, but they need to request to join open projects or be invited to closed projects. Enabling `Strict SSO` requires that all members of a Project besides the `Owner` log in to the Statsig Console through SSO with the configured provider to access the Project. ## Break Glass Scenarios If you have configured SSO as required and your SSO configuration becomes corrupted, users are blocked from logging in. In that case, the user with the Owner role can use the break glass URL to sign in with a password, bypassing SSO. The break glass URL is [https://console.statsig.com/login?method=password-only](https://console.statsig.com/login?method=password-only) ================================================================================ # Single Sign-On With Okta URL: https://docs.statsig.com/access-management/sso/okta_sso Updated: 2026-07-11 ================================================================================ ## Requirements * You must be the `Admin` of the Statsig Organization you intend to add SSO with Okta to. * You must be the Administrator of the Okta account you want to link. ## Supported Features Statsig supports the OIDC protocol for SSO with the following flows: * Service Provider(SP)-Initiated Authentication for Single Sign-On (SSO). This flow starts when you log in on the Statsig website. * Identity Provider(IDP)-Initiated Authentication for SSO. This flow starts when you launch the Statsig App from Okta. * Just-In-Time (JIT) provisioning for SSO. Upon successful first-time login, Statsig automatically provisions an account for the user. ## Configure SSO with Okta ### Adding the Statsig OIDC Application in Okta 1. Navigate to your Okta portal. 2. On your Okta portal, click on `Applications` on the left-hand-column, and click into `Applications` in the dropdown. {% figure %} ![Okta portal navigation highlighting Applications menu](/images/access-management/sso/okta_sso/129780676-c04bd2fb-83ed-4d17-9ae2-4e286f2b3b52.png) {% /figure %} 3. On the Applications page, click on the `Browse App Catalog` button. {% figure %} ![Okta Applications page with Browse App Catalog button](/images/access-management/sso/okta_sso/129780681-c48a6012-a882-475a-bbc9-924ec1391126.png) {% /figure %} 4. On the App Catalog page, use the searchbox to search for Statsig and click on the Statsig OIDC Application. 5. In the Statsig Application, click on the `Add` button. {% figure %} ![Statsig app listing within Okta catalog showing Add button](/images/access-management/sso/okta_sso/129780685-e6e141c6-8fdf-42f0-8ed6-edc734f4c2a7.png) {% /figure %} 6. After creating the Statsig OIDC Application in Okta, go to the `Sign On` tab to find the `Client ID` and `Client Secret` fields. You need both values to enable SSO on the Statsig Project. Statsig automatically configures the sign-in and sign-out redirect URIs. {% figure %} ![Okta Sign On tab showing Client ID and Client Secret values](/images/access-management/sso/okta_sso/129780687-bacc68c7-4fb1-4740-bb3e-a7c6b27d006e.png) {% /figure %} After completing these steps, you have configured the Statsig OIDC Application in Okta. Navigate to [SSO configuration on your Statsig Organization](/access-management/sso/overview#in-statsig-console) to finish setup. ## SP-Initiated SSO 1. Navigate to https://console.statsig.com/sso 2. Enter your email address and click on "Authenticate" 3. Statsig redirects you to authenticate with Okta. If prompted, enter your Okta credentials. 4. After you authenticate, Statsig redirects you and logs you in. ### Proof Key for Code Exchange (PKCE) Statsig doesn't currently support the PKCE Flow, so you need to turn off the feature in Okta when you enable SSO with Statsig. ================================================================================ # Single Sign-On With Entra ID/Azure AD/Office 365 URL: https://docs.statsig.com/access-management/sso/azuread Updated: 2026-07-11 ================================================================================ Microsoft Entra ID, formerly known as Azure AD, is a supported IdP for SSO into Statsig. ## Requirements * You must be the `Admin` of the Statsig Project you intend to add SSO with Azure AD to. * You must be the Administrator of the Azure AD tenant you want to link. ## Supported Features You can enable Service Provider (SP)-Initiated Authentication for Single Sign-On (SSO) using OIDC on Statsig to connect your Azure AD account to your Statsig Projects. ## Configure SSO with Azure AD ### Adding the Statsig OIDC Application in Azure AD 1. Navigate to App Registrations in the Azure portal. 2. Click on the "New Registration" button to register the Statsig App. 3. Provide a name (for example, Statsig Console) and finish creation. For the Redirect URI, use https://console.statsig.com/sso/oidc. 4. Select Certificates and secrets for this app from the left nav and create a new client secret. Save the value; you can't retrieve this value after creation. {% figure %} ![Azure AD app registration interface](/images/access-management/sso/azuread/153957278-5f463453-dd7a-443b-9c1c-2a8c205995f6.png) {% /figure %} Enter three items in the Statsig SSO configuration: 1. Find the Azure AD OIDC URL for this app under Overview -> Endpoints -> OpenID Connect metadata document. Truncate the string to end at "/v2.0". 2. Get the client ID for this app from Overview -> Application (client) ID. 3. Get the client secret value you saved after creating a new client secret. After completing these steps, the Azure AD configuration is complete. Navigate to [SSO configuration on your Statsig Project](/access-management/sso/overview#configure-sso-with-oidc) to finish setup. ================================================================================ # SSO with Google as your IdP URL: https://docs.statsig.com/access-management/sso/google Updated: 2026-07-11 ================================================================================ ## Requirements * You must be the `Admin` of the Statsig Project you intend to add SSO with Google Apps to. * You must be the Administrator of the Google Apps account you want to link. ## Supported Features You can enable Service Provider (SP)-Initiated Authentication for Single Sign-On (SSO) using OIDC on Statsig to connect your Google Apps account to your Statsig Projects. ## Configure SSO with Google 1. Follow the [Google OIDC instructions](https://developers.google.com/identity/openid-connect/openid-connect#getcredentials) to obtain the client ID and client secret from your Google account. At [Google Credentials](https://console.cloud.google.com/apis/credentials), you can create a new "OAuth 2.0 Client ID" or select an existing one to find the client ID and client secret (under "Additional information"). 2. Add Statsig's redirect URI (go to [SSO configuration](/access-management/sso/overview#configure-sso-with-oidc) for more detail) under "Authorized redirect URIs" on the chosen OIDC Client: `https://console.statsig.com/sso/oidc`. 3. Navigate to [SSO configuration on your Statsig Project](/access-management/sso/overview#configure-sso-with-oidc) to complete setup. Set the OIDC Domain / Issuer field to `https://accounts.google.com`, and use the client ID and secret from step 1. ================================================================================ # SCIM User Provisioning URL: https://docs.statsig.com/access-management/scim/overview Updated: 2026-07-11 ================================================================================ ## How SCIM works SCIM (System for Cross-domain Identity Management) is a standardized protocol that automates user provisioning and management across multiple platforms. By integrating SCIM with your preferred Identity Provider (IdP), such as Okta, you can manage user creation, updates, and de-provisioning within Statsig. Statsig currently offers an Okta SCIM integration. ## How to obtain SCIM auth key {% callout type="note" %} You must be a Statsig Organization Admin to enable SCIM. The SCIM key includes the `scim` prefix. {% /callout %} {% figure %} ![Statsig organization settings showing SCIM key management panel](/images/okta_scim_steps/scim-access.png) {% /figure %} 1. Navigate to Organization Access Management: Go to [Settings > Organization > Organization Info > Access Management](https://console.statsig.com/settings?tab=organization). 2. Generate a Key: If SCIM isn't yet enabled, generate a new authentication key. 3. Deactivate the Key: To disable the key, select Deactivate. 4. Regenerate the Key: If you suspect the key has been compromised, you can regenerate a new one to replace it. ## Current SCIM Offering ### Okta * **Push Users.** Assigning users to the Statsig application in Okta automatically adds them as members of your organization in Statsig. Unassigning users deactivates them and wipes all roles/permissions. * **Import Users.** Statsig can import users into Okta and either match them to existing Okta users, or create them as new Okta users. * **Import Groups.** Import project/team roles as groups in Okta. You can't modify imported groups in Okta. * **Push Groups.** Push groups to Statsig to update user project/team roles. [Okta Setup Guide](/access-management/scim/okta_scim_setup) ================================================================================ # SCIM Concepts URL: https://docs.statsig.com/access-management/scim/concepts Updated: 2026-07-11 ================================================================================ The Statsig SCIM implementation represents Statsig users at both the organization level and project level with their associated roles. There are two major resources in the SCIM implementation: Users and Groups. ## Users Users in SCIM correspond to Statsig users at the organization level. Statsig recognizes users who are part of the Statsig organization and have the organization email domain as SCIM users. Each user has the following attributes: * First name * Last name * Email address (main identifier, must be unique within the IdP) * Statsig ID (used by the IDP to identify the user in the Statsig SCIM API) ## Groups Groups in SCIM represent Statsig Projects with specific roles. For example, SCIM represents a project named "Project A" with a role of "Admin" as a group. The group name for this example project is `Statsig-ProjectA-Admin`. ### Team x role groups Team x Role Groups are a special type of group that represent Statsig Teams with specific roles. For example, SCIM represents a team named "Team A" with a role of "Admin" as a group. The group name for this example team is `Statsig-ProjectName-TeamA-Admin`. Important notes about groups: * SCIM doesn't support project-level deletion. SCIM supports team-level deletion. * SCIM doesn't support updating group names. ### General mapping between SCIM and Statsig {% figure %} ![Mapping diagram showing Okta group synced to Statsig project and team roles through SCIM](/images/okta_scim_steps/okta-statsig-group.png) {% /figure %} ================================================================================ # Okta SCIM Setup URL: https://docs.statsig.com/access-management/scim/okta_scim_setup Updated: 2026-07-11 ================================================================================ This guide describes how to set up SCIM (System for Cross-domain Identity Management) integration between Statsig and Okta for automated user provisioning and management. ## Prerequisites * An Okta account with admin access * A SCIM Key from the [Statsig Console](/access-management/scim/overview#how-to-obtain-scim-auth-key) (requires Statsig Org Admin rights) {% callout type="note" %} ### Integration Notes * User email management isn't enabled on SCIM yet. * When a user is removed from Statsig, Okta automatically unassigns them. If a user is unassigned or deactivated in Okta, Statsig removes them from the organization. * Creation of Statsig Projects and Roles isn't supported through SCIM. {% /callout %} ## Step 1: Create a new app integration in Okta * Log in to your Okta admin console * Navigate to Applications > Applications > Create App Integration * Select "SWA - Secure Web Authentication" {% figure %} ![Okta Create App Integration dialog selecting Secure Web Authentication](/images/okta_scim_steps/step1-create-new-custom-integration.png) {% /figure %} ## Step 2: Configure app settings * Set the App name to "Statsig SCIM" * Enter a placeholder URL for the App Login Page (required field, not used for SCIM). Example: `https://console.statsig.com/` {% figure %} ![Okta app settings form with Statsig SCIM name and placeholder login URL](/images/okta_scim_steps/step2-configure-app-settings.png) {% /figure %} ## Step 3: Enable SCIM provisioning * After creating the integration, go to the "General" tab * Click on "Edit" in the "Provisioning" section * Enable "SCIM Provisioning" {% figure %} ![Okta application general tab highlighting provisioning section and SCIM toggle](/images/okta_scim_steps/step3-enable-scim.png) {% /figure %} ## Step 4: Configure SCIM settings :::info `Import Groups` requires an Okta flag `SELECTIVE_APP_IMPORT_PLATFORM`. If this flag is enabled for your organization, select this option. If it isn't, leave it unchecked. ::: * Navigate to the `Provisioning` tab * Set the SCIM connector base URL to: [https://statsigapi.net/scim](https://statsigapi.net/scim) * Set "Unique identifier field for users" to `userName` * Enable * `Import New Users and Profile Update` * `Push New Users` * `Push Profile Updates` * `Push Groups` * `Import Groups` (Only if your organization has the `SELECTIVE_APP_IMPORT_PLATFORM` flag enabled; refer to note above) * Set the authentication mode to "HTTP Header" * For the authorization header, use the SCIM Bearer token generated in Statsig by your Org Admin. Go to [How to Obtain SCIM Auth Key](/access-management/scim/overview#how-to-obtain-scim-auth-key) for details. {% figure %} ![Okta provisioning tab showing SCIM base URL and push settings](/images/okta_scim_steps/step4.png) {% /figure %} ## Step 5: Configure Okta to Statsig settings * Enable "Create Users" * Enable "Update User Attributes" * Enable "Deactivate Users" {% figure %} ![Provisioning To App settings enabling create, update, and deactivate actions](/images/okta_scim_steps/step5-configure-okta-to-statsig-settings.png) {% /figure %} ## Step 6: Import existing Statsig users and groups * In Okta, go to the Statsig app's "Import" tab * Click "Import Now" to fetch existing Statsig users and groups * Process the imported users as needed {% figure %} ![Okta Import tab with Import Now button for Statsig users and groups](/images/okta_scim_steps/step6-import-existing-users.png) {% /figure %} ================================================================================ # Okta SCIM User and Project/Role Management URL: https://docs.statsig.com/access-management/scim/okta_scim_user_management Updated: 2026-07-11 ================================================================================ ## Import existing Statsig users and groups {% callout type="note" %} You can't push users into groups unless they're assigned to the integration. {% /callout %} * In Okta, go to the Statsig app's "Import" tab * Click "Import Now" to fetch existing Statsig users and groups * Process the imported users as needed {% figure %} ![Okta Import tab listing Statsig users ready to be brought into Okta](/images/okta_scim_steps/step6-import-existing-users.png) {% /figure %} ## Manage user assignments * Use the "Assignments" tab in Okta to add or remove users from Statsig * Adding a user assignment in Okta creates the user in Statsig. Removing the assignment deactivates their Statsig account. {% figure %} ![Okta Assignments tab showing Statsig app user assignment controls](/images/okta_scim_steps/step7-manage-user-assignments.png) {% /figure %} ## Push groups to Statsig 1. In Okta, go to the Statsig Integration's "Push Groups" tab {% figure %} ![Push Groups tab in Okta Statsig integration](/images/okta_scim_steps/step8-push-groups-1.png) {% /figure %} 2. Click the settings button and disable "Rename Groups" {% figure %} ![Push group settings dialog with Rename Groups toggle](/images/okta_scim_steps/step8-push-groups-2.png) {% /figure %} 3. Click "Push Groups" and select the method for finding groups in Okta. {% figure %} ![Okta modal prompting to find groups by name for push](/images/okta_scim_steps/step8-push-groups-3.png) {% /figure %} 4. Type in and select the Okta group to push to a Statsig Project x Role Group. * Find groups under `Directory > Groups` in the Okta left nav. You will see groups created from Okta and groups created by Statsig. * The required groups are those you created in Okta. Filter by setting `Group source type` to `Okta groups`. If you have no Okta groups, create one and add members. {% figure %} ![Directory listing of Okta groups filtered to Okta source](/images/okta_scim_steps/step8-push-groups-4.png) {% /figure %} 5. Link the Okta group to the Statsig group with the role you want. * Change `Match Result & Push Action` to `Link Group` {% figure %} ![Push group configuration selecting Link group action](/images/okta_scim_steps/step8-push-groups-5.png) {% /figure %} 6. Select the Statsig Project x Role Group that the Okta group pushes to. * Statsig displays the Project x Role Group with the format `Statsig-<Project Name>-<Role Name>` on Okta. * By default Okta only allows you to map 1 Okta Group to 1 Statsig Group. {% figure %} ![Statsig project role group dropdown showing Statsig-Project-Role format](/images/okta_scim_steps/step8-push-groups-6.png) {% /figure %} 7. Then link the Okta group to a Statsig Project x Role Group. On save, the group pushes to Statsig. All future group changes on Okta push to Statsig. {% figure %} ![Summary screen confirming Okta group linked to Statsig project role](/images/okta_scim_steps/step8-push-groups-7.png) {% /figure %} ================================================================================ # Okta SCIM Org Roles URL: https://docs.statsig.com/access-management/scim/okta_scim_org_roles Updated: 2026-07-11 ================================================================================ ## Update Okta user org role For every user, Statsig surfaces a SCIM field named `statsigOrgRole`. Through this field, you can manage organization user roles. Okta can only push role updates to Statsig. Supported org roles are: `Member` `Admin` `Owner` ### Step 1. Create the custom attribute in Okta Navigate to `Directory > Profile Editor` and select the User (default) Okta profile. This profile represents all Okta user attributes. Scroll down and press `Add Attribute` and fill out the new attribute to have the variable name `statsigOrgRole`. {% figure %} ![Okta profile editor showing custom statsigOrgRole attribute being added](/images/okta_scim_steps/org_steps/step1.png) {% /figure %} ### Step 2. Create the custom attribute in the Statsig SCIM integration Navigate to the `Statsig SCIM Integration's User Profile` in the `Profile Editor`. Add a new attribute that matches the following format: * Variable name: `statsigOrgRole` * External namespace: `urn:ietf:params:scim:schemas:core:2.0:User` * Attribute type: either `Personal` or `Group`, depending if using groups for app assignment {% figure %} ![Statsig SCIM integration user profile with statsigOrgRole attribute definition](/images/okta_scim_steps/org_steps/step2.png) {% /figure %} ### Step 3. Create a mapping from Statsig to Okta for the custom attribute On the same Statsig SCIM profile editor, navigate to the `Mappings` button. Scroll down to the new attribute `statsigOrgRole` and map `user.statsigOrgRole` to the Okta attribute `statsigOrgRole`. {% figure %} ![Okta mapping editor linking statsigOrgRole between Statsig and Okta profiles](/images/okta_scim_steps/org_steps/step3.png) {% /figure %} ### Step 4. Create a mapping from Okta to Statsig for the custom attribute Now navigate to the Okta User to Statsig SCIM user mapping. {% figure %} ![Okta to Statsig user mapping screen for statsigOrgRole attribute](/images/okta_scim_steps/org_steps/step4_1.png) {% /figure %} Scroll down to the `statsigOrgRole` attribute and map `user.statsigOrgRole` to the Okta attribute `statsigOrgRole`. {% figure %} ![statsigOrgRole attribute mapping row pointing from user field to Okta attribute](/images/okta_scim_steps/org_steps/step4_2.png) {% /figure %} All users now sync with their organization role. You can also modify a user's role directly on the Statsig SCIM integration. ### Step 5. Modify integration mappings Navigate to the Statsig SCIM integration provisioning section. Under the "To App" tab, scroll down to the `statsigOrgRole` attribute. {% figure %} ![Provisioning To App tab showing statsigOrgRole attribute settings](/images/okta_scim_steps/org_steps/step5_1.png) {% /figure %} Set the attribute value to `Map from Okta Profile` and `statsigOrgRole`. Set apply on `Create and update`. {% figure %} ![Set value dialog choosing Map from Okta Profile for statsigOrgRole](/images/okta_scim_steps/org_steps/step5_2.png) {% /figure %} Navigate to the "To Okta" tab and scroll down to the `statsigOrgRole` attribute. {% figure %} ![Provisioning To Okta tab listing statsigOrgRole attribute](/images/okta_scim_steps/org_steps/step5_3.png) {% /figure %} Set the attribute value to `Map from Statsig Profile` and `statsigOrgRole`. Set apply on `Create`. {% figure %} ![Set value dialog mapping statsigOrgRole from Statsig profile back to Okta](/images/okta_scim_steps/org_steps/step5_4.png) {% /figure %} ================================================================================ # Okta SCIM Team Management URL: https://docs.statsig.com/access-management/scim/okta_scim_team_management Updated: 2026-07-11 ================================================================================ ## Import existing Statsig teams {% callout type="note" %} Teams are specific Statsig groups that exist within projects. They aren't shared across projects. They also have two possible roles: `Admin` and `Member`. {% /callout %} ## Before you begin * Ensure you have some teams created in Statsig. * Navigate to the Statsig Project and create a team if you have none. {% figure %} ![Statsig project team creation dialog showing fields for team name and members](/images/okta_scim_steps/team_steps/step1-create-team.png) {% /figure %} ## Import existing groups * In Okta, go to the Statsig app's "Import" tab * Click "Import Now" to fetch existing Statsig users, groups, and teams * After importing, the group appears in your Okta groups following the format `Statsig-ProjectName-TeamName-RoleName`. {% figure %} ![Okta Import tab displaying Statsig team groups discovered from SCIM](/images/okta_scim_steps/team_steps/step2-verify-import.png) {% /figure %} ## Create a mapping group * To push a group of Okta users to a Statsig team, first create a mapping group. * This group contains the Okta members who are part of the Team x Role Group. {% figure %} ![Okta group creation screen for mapping Statsig team members](/images/okta_scim_steps/team_steps/step3-create-mapping-group.png) {% /figure %} ## Push teams to Statsig 1. In Okta, go to the Statsig Integration's "Push Teams" tab and select "Push Groups". Then pick the mapping group created in the previous step. {% figure %} ![Okta Push Groups dialog selecting mapping group to link with Statsig team](/images/okta_scim_steps/team_steps/step4-push-teams-1.png) {% /figure %} 2. Change the Match result & push action to Link group. Then select the Statsig team you want to push to. The team name must follow the format `Statsig-ProjectName-TeamName-RoleName`. {% figure %} ![Match result settings linking Okta group to Statsig-Project-Team role name](/images/okta_scim_steps/team_steps/step4-push-teams-2.png) {% /figure %} 3. When you finish setting up the push, click **Save**. ## Verify the push * Navigate to the Statsig Project and verify that the team has the new members. * Okta can take a few minutes to push members to the team. {% figure %} ![Statsig team detail page showing synced Okta members](/images/okta_scim_steps/team_steps/step5-verify-push.png) {% /figure %} ## Delete the team * On Okta, navigate to push groups and find the pushed group that maps to the Statsig team. * Click the push status section and select "Unlink Group". {% figure %} ![Okta push groups status view with unlink group option](/images/okta_scim_steps/team_steps/step6-delete-1.png) {% /figure %} * When prompted to delete the group or leave it in the app, select "Delete". * After Okta pushes the deletion, Statsig deletes the team. This can take a few minutes to complete. {% figure %} ![Confirmation dialog asking to delete pushed group from app](/images/okta_scim_steps/team_steps/step6-delete-2.png) {% /figure %} ## Optional: Create a team using SCIM * Find an existing Okta group with the members for a new Statsig Team. * Navigate to Push Groups and Select "Push Groups". * Select the group you want to push to Statsig. * Change the Match result & push action to Create Group. * The naming for this group must match the format `Statsig-ProjectName-TeamName-Member`. {% figure %} ![Okta push groups workflow set to create new Statsig team group](/images/okta_scim_steps/team_steps/step7-create-1.png) {% /figure %} * When you finish setting up the push, click **Save**. * The new team appears in the Statsig Project you specified. {% figure %} ![Statsig console showing newly created team populated from SCIM push](/images/okta_scim_steps/team_steps/step7-create-2.png) {% /figure %} ================================================================================ # Okta SCIM Troubleshooting URL: https://docs.statsig.com/access-management/scim/okta_scim_troubleshooting Updated: 2026-07-11 ================================================================================ ## Common SCIM errors | Error | Solution | |-------|----------| | User userID is not allowed to be created in this organization | Users assigned to Statsig must have the org's email domain. Contact Statsig to add additional domains. | | Owner cannot be modified | The organization owner can only be replaced by another user. | | Cannot set multiple owners | Statsig doesn't allow multiple owners for an organization. Remove any existing owner assignment before setting a new one. | | Project creation is not supported through SCIM | Create projects in the Statsig Console instead. | | Project deletion is not supported through SCIM | Delete projects in the Statsig Console instead. | ## Common issues * I'm not seeing any Statsig groups in Okta * Your organization may have the Okta flag `SELECTIVE_APP_IMPORT_PLATFORM` enabled. If so, select `Import Groups` in the SCIM integration settings. Go to the [setup page](/access-management/scim/okta_scim_setup) for details. * I can't select `Import Groups` in the SCIM integration settings * Your organization may have the Okta flag `SELECTIVE_APP_IMPORT_PLATFORM` disabled. If so, you can't select `Import Groups` in the SCIM integration settings. However, groups still appear in the Okta SCIM integration when importing. * I'm trying to push groups but not seeing users populate those groups * Verify that the users you are pushing to Statsig are assigned to the integration. * If the users are assigned, verify that the groups you are pushing to Statsig are the Statsig Groups generated on import. * I don't want to use Statsig generated groups, can I use my own? * You can use your own groups in the push groups section. Your groups must still be pushed to a specific Statsig Project x Role Group. ================================================================================ # SCIM API Overview URL: https://docs.statsig.com/access-management/scim/scim-endpoints Updated: 2026-07-11 ================================================================================ The System for Cross-domain Identity Management (SCIM) specification is designed to make managing user identities in cloud-based applications and services easier. Statsig's SCIM API allows you to automate user provisioning and deprovisioning between your identity provider and Statsig. This API reference provides details for all available SCIM endpoints and operations. ================================================================================ # Delete scimgroups URL: https://docs.statsig.com/api-reference/scim-groups/delete-scimgroups Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="delete-scimgroups.json" /%} ================================================================================ # Get scimgroups URL: https://docs.statsig.com/api-reference/scim-groups/get-scimgroups Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-scimgroups.json" /%} ================================================================================ # Get scimgroups 1 URL: https://docs.statsig.com/api-reference/scim-groups/get-scimgroups-1 Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-scimgroups-1.json" /%} ================================================================================ # Patch scimgroups URL: https://docs.statsig.com/api-reference/scim-groups/patch-scimgroups Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="patch-scimgroups.json" /%} ================================================================================ # Post scimgroups URL: https://docs.statsig.com/api-reference/scim-groups/post-scimgroups Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="post-scimgroups.json" /%} ================================================================================ # Put scimgroups URL: https://docs.statsig.com/api-reference/scim-groups/put-scimgroups Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="put-scimgroups.json" /%} ================================================================================ # Get scimresourcetypes URL: https://docs.statsig.com/api-reference/scim-settings/get-scimresourcetypes Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-scimresourcetypes.json" /%} ================================================================================ # Get scimschemas URL: https://docs.statsig.com/api-reference/scim-settings/get-scimschemas Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-scimschemas.json" /%} ================================================================================ # Get scimserviceproviderconfig URL: https://docs.statsig.com/api-reference/scim-settings/get-scimserviceproviderconfig Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-scimserviceproviderconfig.json" /%} ================================================================================ # Get scimusers URL: https://docs.statsig.com/api-reference/scim-users/get-scimusers Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-scimusers.json" /%} ================================================================================ # Get scimusers 1 URL: https://docs.statsig.com/api-reference/scim-users/get-scimusers-1 Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="get-scimusers-1.json" /%} ================================================================================ # Patch scimusers URL: https://docs.statsig.com/api-reference/scim-users/patch-scimusers Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="patch-scimusers.json" /%} ================================================================================ # Post scimusers URL: https://docs.statsig.com/api-reference/scim-users/post-scimusers Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="post-scimusers.json" /%} ================================================================================ # Put scimusers URL: https://docs.statsig.com/api-reference/scim-users/put-scimusers Updated: 2026-07-11 ================================================================================ {% openapi-operation sidecar="put-scimusers.json" /%} ================================================================================ # WHN Usage-Based Pricing Model URL: https://docs.statsig.com/access-management/pricing Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Applies to Warehouse Native customers on the new usage-based pricing model only. The instructions below describe how to view credit usage under Statsig's new pricing. {% /callout %} {% callout type="info" %} Images below are from a sample account. Your view may differ based on your rate and usage. Credit usage may take up to 2 days to appear in your usage dashboard. {% /callout %} You can view credit usage in two places: * The **Statsig Console** * The **Orb billing portal** Both show the same view of your total credit balance and usage by SKU. ## Statsig Console Use the Statsig Console to view credit usage. 1. Log in to the [Statsig Console](https://console.statsig.com/). 2. Open **Settings**. 3. Go to **Manage Account → Usage & Billing**. 4. At the top of the page, your total credit balance appears. 5. Below the total, you can view credit usage by SKU, along with current and past invoice and usage details. ## Orb portal Use Orb to view credit usage and automated alerts. {% callout type="info" %} Use Orb for visibility and alerts only. You won't be auto-charged or invoiced through Orb. The Statsig team handles any credit top-ups directly. {% /callout %} 1. **Log in to your Orb account.** 2. **View your total credit balance** - Go to **Account Information** to see your total credit balance. ![Credit balance in Orb](/images/credit_balance_image_2.png) Click **"Credit block detail"** to view: * Original credit balance * Remaining balance * Credit expiration date 3. **View usage by SKU** - Under upcoming invoices, you can view how credits are being consumed across different metrics/SKUs. Click **"Show usage details"** to view: * Total credit usage by SKU * Daily credit drawdown over time ![Daily credit drawdown](/images/daily_credit_drawdown_2.png) 4. **Experiment Analysis Unit (EAU) usage** Statsig bills EAUs when an experiment ends, so EAU usage may appear as a single spike rather than as daily usage. ================================================================================ # Integrations Overview URL: https://docs.statsig.com/integrations/introduction Updated: 2026-07-11 ================================================================================ *For Warehouse Integrations, go to [Data Warehouse Integrations](/data-warehouse-ingestion/introduction).* The following data connectors are available, and Statsig adds more regularly: > **Warning:** Statsig only supports a single data connection at a time. ### Events Forward any events logged through Statsig APIs or SDKs to the following providers: * [Segment](/integrations/data-connectors/segment) * [Snowflake](/data-warehouse-ingestion/snowflake) * [Amplitude](/integrations/data-connectors/amplitude) * Bugsnag * [Fivetran](/integrations/data-connectors/fivetran) * [Google Analytics](/integrations/data-connectors/google-analytics) * [Heap](/integrations/data-connectors/heap) * [Mixpanel](/integrations/data-connectors/mixpanel) * [RevenueCat](/integrations/data-connectors/revenuecat) * [mParticle](/integrations/data-connectors/mparticle) * [RudderStack](/integrations/data-connectors/rudderstack) * [Webhook](/integrations/event_webhook) * [Google Tag Manager (GTM)](/integrations/gtm) * [Braze](/integrations/data-connectors/braze) ### Changelog and alerts Notify and update the following places when Feature Gates/Experiments/Dynamic Configs change: * [Datadog](/integrations/datadog) * Discord * LogDNA * Microsoft Teams * [Slack](/integrations/slack) * [Jira](/integrations/jira): track Statsig gate rollout status and A/B test results from Jira issues ================================================================================ # Amplitude URL: https://docs.statsig.com/integrations/data-connectors/amplitude Updated: 2026-07-11 ================================================================================ ## How the Amplitude integration works Statsig supports both incoming and outgoing events for Amplitude, and also supports adding Amplitude Cohorts to Statsig ID Lists. ## Incoming: receiving events from Amplitude {% callout type="note" %} This integration supports ingestion for Statsig Cloud only. For Warehouse Native, create a metrics source that references this data in your warehouse. {% /callout %} The following steps outline how to forward events from Amplitude into Statsig. 1. Get a Statsig "Server Secret Key" from the API keys page in [Project Settings](https://console.statsig.com/api_keys). 2. Go to Amplitude and navigate to the Data Destinations page. Click the "Add Destination" button in the top right. {% figure %} ![Amplitude Data Destinations page showing Add Destination button](/images/integrations/data-connectors/amplitude/step_1.png) {% /figure %} 3. From the Destinations Catalog, search for and select the Statsig Event Streaming destination. {% figure %} ![Destinations catalog highlighting Statsig event streaming option](/images/integrations/data-connectors/amplitude/step_2.png) {% /figure %} 4. Give this destination a name and click "Create Sync". {% figure %} ![Create Sync form for Statsig destination](/images/integrations/data-connectors/amplitude/step_3.png) {% /figure %} 5. Enter the "Server Secret Key" you copied in Step 1 into the provided field. Select the events you wish to send to Statsig. Ensure that the Status is set to "Enabled" and then click "Save". {% figure %} ![Statsig destination settings entering server secret key and event selection](/images/integrations/data-connectors/amplitude/step_4.png) {% /figure %} 6. *Enable the integration* - On the Integrations page for your Statsig project, enable the Amplitude Incoming integration. {% figure %} ![Statsig integration panel confirming Amplitude connection](/images/integrations/data-connectors/amplitude/09de394e-dcc7-4a80-81fb-ae4cc58b25a1.png) {% /figure %} ## Outgoing: sending Statsig events to Amplitude 1. Navigate to Amplitude and click on the Settings button in the bottom-left corner. {% figure %} ![Amplitude settings menu accessed from bottom-left](/images/integrations/data-connectors/amplitude/settings.png) {% /figure %} 2. Click on the Projects tab and choose the Project you wish to send data to. {% figure %} ![Amplitude Projects tab listing available workspaces](/images/integrations/data-connectors/amplitude/project.png) {% /figure %} 3. Copy the API Key and paste it in the Statsig integration panel. {% figure %} ![Amplitude project API key display](/images/integrations/data-connectors/amplitude/api_key.png) {% /figure %} {% figure %} ![Statsig integration panel fields for Amplitude API key](/images/integrations/data-connectors/amplitude/197276579-b3884a8f-ed47-4cd9-8852-c37f40958554.png) {% /figure %} 4. Select **Enable** on the integration panel. Any data logged to Statsig appears in your Amplitude project. {% figure %} ![Amplitude event stream showing Statsig exposure events](/images/integrations/data-connectors/amplitude/amplitude_data.png) {% /figure %} ## First exposures [First exposures](/experiments/interpreting-results/export#first-exposures-file-description) are an enterprise-tier feature that simplifies your project insights. {% callout type="info" %} This feature requires an Enterprise contract. Reach out to the support team, your sales contact, or through the [Slack community](https://statsig.com/slack) to enable it. {% /callout %} ### How first exposures work The Amplitude integration can forward first exposures instead of every exposure, reducing the total number of events forwarded. Statsig calculates first exposures daily and forwards them to integrations at approximately 7pm UTC. ### How to enable First, confirm that Statsig has enabled the "first exposure" feature for your account by reaching out to the support team, your sales contact, or through the [Slack community](https://statsig.com/slack). After that, go to the event filtering tab of the integration and enable the "First Exposure" setting. ### Example events in Amplitude Example of a get\_experiment First Exposure in Amplitude. {% figure %} ![Example first exposure event in Amplitude log](/images/integrations/data-connectors/amplitude/5e945a95-8c7b-4181-8440-8e60502455e2.png) {% /figure %} ### Accessing raw data To access raw first exposure data in CSV format, make a request to the [console/v1/reports](/console-api/introduction#get-/reports) endpoint to receive a download link. ## Cohort sync: syncing Amplitude cohorts to Statsig segments For current configuration information on syncing Cohorts (also called Segments) from Amplitude to Statsig, refer to [Amplitude's documentation](https://www.docs.developers.amplitude.com/data/destinations/statsig-cohort/). {% callout type="warning" %} Ensure you create and use a console API key from your [Statsig project settings](https://console.statsig.com/api_keys) {% /callout %} ## Filtering events You can customize which events Statsig sends and receives through Amplitude using [Event Filtering](/integrations/event_filtering). ================================================================================ # Braze URL: https://docs.statsig.com/integrations/data-connectors/braze Updated: 2026-07-11 ================================================================================ ## How the Braze integration works Enabling the Braze integration allows you to export Statsig exposure events to your configured Braze app with information on the status of each user's feature gate and experimentation groups. Exposures are forwarded to Braze as a [Custom Attribute](https://www.braze.com/docs/user_guide/data/custom_data/custom_attributes) object on the user. There is one Custom Attribute per gate/experiment the user has been exposed to. The Custom Attribute in Braze is named `statsig_exposure::{gate/experiment name}` and has the following form: ``` { group_name: String, timestamp: Time } ``` You can then filter exposed users into a Segment in Braze. By default, Custom Attributes are forwarded to Braze users using the unit ID from the gate/experiment as the `external_id` in Braze. You can provide a custom Unit ID Type from your Statsig project to use as the `external_id` for all gate/experiment exposures. Provide this in the ID Type Mapping section of the Setup dialog for this integration. The integration uses this custom ID Type if it is provided in the SDK call at the time of exposure, and falls back to the experiment's Unit ID Type if not. ## Setup in Statsig {% callout type="info" %} This is available for Enterprise contracts. Reach out to the support team, your sales contact, or through the [Slack community](https://statsig.com/slack) if you want this enabled. {% /callout %} After the integration is enabled, you can find 'Braze' as an option in your Statsig project's [list of integrations](https://console.statsig.com/integrations) from within the Statsig console. 1. Open your [Braze dashboard](https://dashboard.braze.com/). Navigate to Settings > APIs and Identifiers, then open the API Keys tab. 2. Create or select an existing API key that has the 'users.track' permission. Enter the API Key Identifier in the Braze Integration Setup dialog in your Statsig project. 3. Find your Instance's REST Endpoint from the [Braze API docs](https://www.braze.com/docs/api/basics/#api-definitions/). Enter it in the Integration Setup dialog. ## Segment filtering in Braze After your integration is set up in Statsig, exposures start flowing into your Braze app. When exposures arrive in Braze from a new gate/experiment, you can create a filter on these users. 1. Open your [Braze dashboard](https://dashboard.braze.com/). Navigate to Data Settings > Custom Attributes. You should see your new Custom Attribute from Statsig like below: {% figure %} ![image.png](/images/integrations/data-connectors/braze/9b09c6b2-b230-499a-a303-29bfe254c6bd.png) {% /figure %} 2. Click 'Generate Schema'. It automatically detects the schema like below: {% figure %} ![image.png](/images/integrations/data-connectors/braze/f6545fc7-e328-44af-8f32-700d442c7869.png) {% /figure %} 3. Now you can create a Segment from these users. Navigate to Audience > Segments, and click 'Create Segment'. 4. Under the 'Segment Builder' section, add a new filter. Click 'Custom Attributes', then 'Nested Custom Attributes'. {% figure %} ![image.png](/images/integrations/data-connectors/braze/d0257d44-793c-4ceb-b81e-4a0d58019ff0.png) {% /figure %} 5. Now you can filter to a specific group\_name (true/false for gates, group name for experiments), or timestamp for your set of users. An example Segment filter for all users that have passed a specific gate is like below: {% figure %} ![image.png](/images/integrations/data-connectors/braze/20d71870-98bf-47b0-aa8d-e0aed4d7a2df.png) {% /figure %} ## First exposures [First exposures](/experiments/interpreting-results/export#first-exposures-file-description) are an enterprise-tier feature that simplifies your project insights. {% callout type="info" %} This is available for Enterprise contracts. Reach out to the support team, your sales contact, or through the [Slack community](https://statsig.com/slack) if you want this enabled. {% /callout %} ### How first exposures work The Braze integration can forward first exposures instead of every exposure, reducing the total number of events forwarded. First exposures are calculated daily and forwarded to integrations at around 7pm UTC. ### Enable first exposures Ensure the "first exposure" feature has been enabled for your company by contacting the support team, your sales contact, or the [Slack community](https://statsig.com/slack). Then go to the event filtering tab of the integration and enable the **First Exposure** setting. ================================================================================ # Census URL: https://docs.statsig.com/integrations/data-connectors/census Updated: 2026-07-11 ================================================================================ ## How the Census integration works Enabling the [Census](https://getcensus.com/) integration for Statsig allows Statsig to receive events from Census. This enables you to ingest data into Statsig from any sources that Census supports. You can find all events that Statsig receives from Census in the [Metrics](/metrics/introduction) tab in the Statsig console. Statsig automatically includes these events in [Pulse](/experiments/interpreting-results/read-results) and [Experiment](/experiments/monitor) results for your feature gates and experiments respectively. ## Configuring incoming events 1. From the [API Keys](https://console.statsig.com/api_keys) tab in the Statsig console, copy the Statsig “Server Secret Key”. 2. From Census, create a new [destination](https://docs.getcensus.com/destinations/overview) and select Statsig from the list of options. 3. Paste the Statsig secret into the field and click save. {% figure %} ![Census destination setup form with Statsig secret key input](/images/integrations/data-connectors/census/b3134399-288d-4a0f-b4d2-4b88980f0718.png) {% /figure %} 4. Create a Sync to the new Statsig destination (refer to the [Sync Configuration](#sync-configuration) section below) 5. On the Statsig [Integrations](https://console.statsig.com/integrations) page, enable the Census integration. ### Sync configuration You must provide a sync key to uniquely identify each event. {% figure %} ![Census sync configuration showing sync key selection](/images/integrations/data-connectors/census/e5d1154d-bd55-48d8-a300-13d96a89a0c8.png) {% /figure %} Map the following fields to Statsig events. * `User ID` -> `userID` * `Event Name` -> `eventName` * `Timestamp` -> `timestamp` * `Value` -> `value` {% figure %} ![Field mapping table aligning Census columns to Statsig event fields](/images/integrations/data-connectors/census/7fce9183-312c-4b47-90c4-b48b0479ecca.png) {% /figure %} Statsig includes all other fields in the `metadata` section of the mapped event. ### Custom ID mapping The Census integration allows the mapping of arbitrary fields to Statsig Custom IDs. To do this, visit the Census panel on the Statsig [Integrations](https://console.statsig.com/integrations) page and look for the "Map Identifier" section. Here you can choose fields you would like mapped to a Custom ID. {% callout type="note" %} The input Event Field must match the exact spelling as in the original Census event. {% /callout %} {% figure %} ![Statsig integration panel for Census custom ID mapping](/images/integrations/data-connectors/census/213269548-e6457527-c938-44fd-9360-1f3fd7af2fac.png) {% /figure %} ================================================================================ # Fivetran URL: https://docs.statsig.com/integrations/data-connectors/fivetran Updated: 2026-07-11 ================================================================================ ## How the Fivetran integration works Enabling the Fivetran integration for Statsig allows Statsig to push events to your Fivetran account through a webhook. This allows you to forward Statsig data to any connectors available from Fivetran. ## Configuring outbound events 1. Follow the steps in the [Fivetran Webhook Setup Guide](https://fivetran.com/docs/events/webhooks/setup-guide) to create a new Webhook URL. 2. On the Statsig [Integrations](https://console.statsig.com/integrations) page, enable the Fivetran integration by pasting in the Fivetran Webhook URL and click **Confirm**. ### Event format Events are sent in batches in JSON format. Each Statsig event has the following structure: | Field | Type | Description | | --------------- | ------ | -------------------------------------------------------------------- | | eventName | String | Name of the event provided | | user | JSON | [Statsig User Object](/sdks/user) | | userID | String | User ID provided | | timestamp | Number | Timestamp in MS of the event | | value | String | Value of the event provided | | metadata | JSON | Custom Metadata provided | | statsigMetadata | JSON | Metadata related to the logging of this event added by Statsig | | timeUUID | String | UUID for the event | | unitID | String | Unit ID of the exposure (e.g. userID, stableID, or the customID) | #### Custom event formatting - logEvent > ```json { "eventName": "my_custom_event", "user": { "userID": "a_user", "email": "a.user@email.com" }, "userID": "a_user", "timestamp": "1655231253265", "statsigMetadata": { ... }, "value": "a_custom_value", "metadata": { "key_a": "value_a", "key_b": "123" }, "timeUUID": "abd2a983-ec0f-11ec-917a-fb8cdaeda578" } ``` #### Feature gate exposure formatting - checkGate > ```json { "eventName": "statsig::gate_exposure", "user": { ... }, "userID": "a_user", "timestamp": "1655231253265", "statsigMetadata": { ... }, "value": "", "metadata": { "gate": "a_gate", "gateValue": "false", "ruleID": "default", "reason": "Network", "time": "1655231249644" }, "timeUUID": "8d7c1040-ec11-11ec-g123-abe2c32fcf46", "unitID": "userID" } ``` #### Dynamic config exposure formatting - getConfig > ```json { "eventName": "statsig::config_exposure", "user": { ... }, "userID": "a_user", "timestamp": "1655231253265", "statsigMetadata": { ... }, "value": "", "metadata": { "config": "a_config", "ruleID": "default", "reason": "Network", "time": "1655231249644" }, "timeUUID": "af379f60-ec11-22ad-8e0a-05c3ee70bd0c", "unitID": "userID" } ``` #### Experiment exposure formatting - getExperiment > ```json { "eventName": "statsig::experiment_exposure", "user": { ... }, "userID": "a_user", "timestamp": "1655232119734", "statsigMetadata": { ... }, "value": "", "metadata": { "config": "an_experiment", "ruleID": "4SauZJcM1T7zNvh1igBjwE", "reason": "Network", "time": "1655231249644", "experimentGroupName": "Control" }, "timeUUID": "af379f61-ab22-11ec-8e0a-05c3ee70bd0c", "unitID": "userID" } ``` #### Example batch > ```json [ { "eventName": "page_view", "user": {"userID": "user_1", "country": "US"}, "userID": "user_1", "timestamp": 1644520566967, "value": "example_value", "metadata": {"page": "home_page"}, "statsigMetadata": {}, "timeUUID": "f4c414a0-8ab5-11ec-a8a3-0242ac120002" }, { "eventName": "statsig::gate_exposure", "user": {"userID": "user_1", "country": "US"}, "userID": "user_1", "timestamp": 1644520566968, "value": "", "metadata": {"gate": "test_gate", "gateValue": "true", "ruleID": "default"}, "statsigMetadata": {}, "timeUUID": "f4c414a0-8ab5-11ec-a8a3-0242ac120003", "unitID": "userID" }, { "eventName": "statsig::experiment_exposure" "user": {"userID": "user_1", "country": "US"}, "userID": "user_1", "timestamp": 1644520566969, "value": "", "metadata": { "config": "an_experiment", "ruleID": "4SauZJcM1T7zNvh1igBjwE", "reason": "Network", "time": "1655231249644", "experimentGroupName": "Control" }, "statsigMetadata": {}, "timeUUID": "f4c414a0-8ab5-11ec-a8a3-0242ac120004", "unitID": "userID" } ] ``` ## Filtering events After you enable outbound events to Fivetran, you can select which categories of Statsig events to export by clicking the **Event Filtering** button and checking the appropriate boxes as shown below. {% figure %} ![Event filtering configuration interface](/images/integrations/data-connectors/fivetran/150854805-c70a1e01-5d3e-407f-9f2b-2eccafbe04a3.png) {% /figure %} {% figure %} ![Event category selection checkboxes](/images/integrations/data-connectors/fivetran/150855038-fc6add6c-48ed-4063-8fdf-b210b43a3308.png) {% /figure %} ================================================================================ # Google Analytics URL: https://docs.statsig.com/integrations/data-connectors/google-analytics Updated: 2026-07-11 ================================================================================ Enabling the Google Analytics 4 integration allows Statsig to send logged events and exposures to GA4. This enhances your existing Google Analytics tracking with additional data collected by Statsig's logging SDKs. After you enable the integration, Statsig forwards exposures and logged events to a configured Data Stream. You can filter these events with [event filtering.](/integrations/data-connectors/google-analytics#filtering-events) ### Benefits of the Google Analytics 4 integration The GA4 integration lets you log additional events without orchestrating two libraries, which simplifies your code. You can join experiment data from Statsig to existing analytics events in Google Analytics, giving you greater insight into how experiments affect user interactions. ## Configuring outbound events to Google Analytics 4 To send events collected by Statsig's SDKs to GA4, you must configure a Data Stream and provide a few pieces of information. 1. Navigate to the GA4 admin settings. Under your app's property click Data Streams and select the stream you'd like to use. If you don't have a stream you'll need to create one. 2. Statsig requires an API secret to send data to your stream. Navigate to your stream and create a new secret: {% figure %} ![GA4 Data Stream details with Measurement Protocol API secrets section](/images/integrations/data-connectors/google-analytics/263364181-a672aec0-15c1-4030-a4c8-2e2a58083d7e.png) {% /figure %} {% figure %} ![Create API secret dialog in GA4](/images/integrations/data-connectors/google-analytics/263364616-65a054d6-c0b7-48ba-84ee-cbdb7f5eaa9a.png) {% /figure %} {% figure %} ![List of API secrets showing newly generated key](/images/integrations/data-connectors/google-analytics/263364730-b419ac78-01cb-453c-8cd3-c0b5f7a3cb6a.png) {% /figure %} {% figure %} ![Measurement ID highlighted on GA4 stream setup](/images/integrations/data-connectors/google-analytics/263364942-1fb5da05-dbfc-4362-86f8-8d77bdd23cd4.png) {% /figure %} 3. After creating the secret, copy the API secret and the measurement ID (optional). Navigate to your Statsig Project, then Project Settings, then Integrations, then Google Analytics (click enable), then Google Analytics 4. Provide your API Secret and measurement ID from the previous step and click *confirm*: {% figure %} ![Statsig integration configuration form for GA4 API secret and measurement ID](/images/integrations/data-connectors/google-analytics/263366565-c9f97636-8bd3-428f-b2ee-e542776b50ab.png) {% /figure %} 4. Verify that you are receiving events by checking the Realtime overview report for the event with name `statsig`. Events may take a couple of days to appear in other reports. 5. You can also add the following custom event dimensions. Other custom IDs and custom user attributes are available as user dimensions. <ul> <li>`config` - Name of the experiment/gate/dynamic config</li> <li>`group` - Name of the exposed group (e.g. Control)</li> <li>`value` - Value for custom events</li> <li>`statsig_session_id` - Session ID</li> <li>`category` - Type of exposure or name of the custom event (e.g. `statsig_gate_exposure`)</li> <li>`unit_id` - Value of the unit ID (e.g. '123')</li> <li>`unit_id_type` - Type of the unit ID (e.g. 'stableID')</li> </ul> ## Filtering events After you enable the outgoing integration, you can optionally configure event filtering to control which events populate the GA4 Data Stream: {% figure %} ![Event filtering UI specifying which Statsig events flow to GA4](/images/integrations/data-connectors/google-analytics/264458718-69f0d248-7df3-4360-bdb4-dcffb1503e3c.png) {% /figure %} ================================================================================ # Heap URL: https://docs.statsig.com/integrations/data-connectors/heap Updated: 2026-07-11 ================================================================================ ## How the Heap integration works Enabling the Heap integration allows you to export Statsig events to your configured Heap app with information on the status of each user's feature gate and experimentation groups. Statsig sends events to Heap when a client SDK is initialized, and also forwards events as they are received. ## Client SDK initialize events Statsig sends the following events to your Heap app every time you call the `initialize` API from a Statsig client SDK. | Event Name | Properties | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Statsig Feature Gates | For each [Statsig Feature Gate](/feature-flags/overview), this field contains a property that maps the name of the feature gate to `true` or `false`, stating whether the user passes or does not pass the feature gate. | | Statsig Experiments | For each [Statsig Experiment](/experiments/overview), this field contains a property that maps the name of the experiment to the variant that the user is assigned to. | ## Exposures and custom event forwarding Statsig can forward events as they are received from SDKs, Integrations or the HTTP API. Events include exposures (Gate, Experiment, Config) and custom events. ### Event format Events are sent in batches in JSON format. Each Statsig event has the following structure: | Field | Type | Description | | --------------- | ------ | -------------------------------------------------------------------- | | eventName | String | Name of the event provided | | user | JSON | [Statsig User Object](/sdks/user) | | userID | String | User ID provided | | timestamp | Number | Timestamp in MS of the event | | value | String | Value of the event provided | | metadata | JSON | Custom Metadata provided | | statsigMetadata | JSON | Metadata related to the logging of this event added by Statsig | | timeUUID | String | UUID for the event | | unitID | String | Unit ID of the exposure (e.g. userID, stableID, or the customID) | #### Custom event formatting - logEvent > ```json { "eventName": "my_custom_event", "user": { "userID": "a_user", "email": "a.user@email.com" }, "userID": "a_user", "timestamp": "1655231253265", "statsigMetadata": { ... }, "value": "a_custom_value", "metadata": { "key_a": "value_a", "key_b": "123" }, "timeUUID": "abd2a983-ec0f-11ec-917a-fb8cdaeda578" } ``` #### Feature gate exposure formatting - checkGate > ```json { "eventName": "statsig::gate_exposure", "user": { ... }, "userID": "a_user", "timestamp": "1655231253265", "statsigMetadata": { ... }, "value": "", "metadata": { "gate": "a_gate", "gateValue": "false", "ruleID": "default", "reason": "Network", "time": "1655231249644" }, "timeUUID": "8d7c1040-ec11-11ec-g123-abe2c32fcf46", "unitID": "userID" } ``` #### Dynamic config exposure formatting - getConfig > ```json { "eventName": "statsig::config_exposure", "user": { ... }, "userID": "a_user", "timestamp": "1655231253265", "statsigMetadata": { ... }, "value": "", "metadata": { "config": "a_config", "ruleID": "default", "reason": "Network", "time": "1655231249644" }, "timeUUID": "af379f60-ec11-22ad-8e0a-05c3ee70bd0c", "unitID": "userID" } ``` #### Experiment exposure formatting - getExperiment > ```json { "eventName": "statsig::experiment_exposure", "user": { ... }, "userID": "a_user", "timestamp": "1655232119734", "statsigMetadata": { ... }, "value": "", "metadata": { "config": "an_experiment", "ruleID": "4SauZJcM1T7zNvh1igBjwE", "reason": "Network", "time": "1655231249644", "experimentGroupName": "Control" }, "timeUUID": "af379f61-ab22-11ec-8e0a-05c3ee70bd0c", "unitID": "userID" } ``` #### Example batch > ```json [ { "eventName": "page_view", "user": {"userID": "user_1", "country": "US"}, "userID": "user_1", "timestamp": 1644520566967, "value": "example_value", "metadata": {"page": "home_page"}, "statsigMetadata": {}, "timeUUID": "f4c414a0-8ab5-11ec-a8a3-0242ac120002" }, { "eventName": "statsig::gate_exposure", "user": {"userID": "user_1", "country": "US"}, "userID": "user_1", "timestamp": 1644520566968, "value": "", "metadata": {"gate": "test_gate", "gateValue": "true", "ruleID": "default"}, "statsigMetadata": {}, "timeUUID": "f4c414a0-8ab5-11ec-a8a3-0242ac120003", "unitID": "userID" }, { "eventName": "statsig::experiment_exposure" "user": {"userID": "user_1", "country": "US"}, "userID": "user_1", "timestamp": 1644520566969, "value": "", "metadata": { "config": "an_experiment", "ruleID": "4SauZJcM1T7zNvh1igBjwE", "reason": "Network", "time": "1655231249644", "experimentGroupName": "Control" }, "statsigMetadata": {}, "timeUUID": "f4c414a0-8ab5-11ec-a8a3-0242ac120004", "unitID": "userID" } ] ``` ## Configuring outbound events 1. Navigate to your [Heap Projects](https://heapanalytics.com/app/manage/projects) page to find and copy the App ID for your project. {% figure %} ![Heap projects page showing App ID value to copy](/images/integrations/data-connectors/heap/outgoing_1.png) {% /figure %} 2. Paste the App ID into the App ID input field for the Heap configuration in the Statsig [Integrations](https://console.statsig.com/integrations) page and save your changes. ## First exposures [First exposures](/experiments/interpreting-results/export#first-exposures-file-description) are an enterprise-tier feature that simplifies your project insights. {% callout type="info" %} This is available for Enterprise contracts. Reach out to the support team, your sales contact, or through the [Slack community](https://statsig.com/slack) if you want this enabled. {% /callout %} ### How first exposures work The Heap integration can forward first exposures instead of every exposure, reducing the total number of events forwarded. First exposures are calculated daily and forwarded to integrations at around 7pm UTC. ### Enable first exposures Ensure the "first exposure" feature has been enabled for your company by contacting the support team, your sales contact, or the [Slack community](https://statsig.com/slack). Then go to the event filtering tab of the integration and enable the **First Exposure** setting. ================================================================================ # Hightouch URL: https://docs.statsig.com/integrations/data-connectors/hightouch Updated: 2026-07-11 ================================================================================ ## How the Hightouch integration works Enabling the [Hightouch](https://hightouch.com/) integration allows you to send events and keep segments up-to-date in Statsig using Hightouch. You can ingest data into Statsig from [any source](https://hightouch.com/integrations) that Hightouch supports. If you’re on a [Pro plan](https://www.statsig.com/pricing), you can find all events that Statsig receives from Hightouch in the [Metrics](/metrics/introduction) tab in the Statsig console. Statsig automatically includes these events in [Pulse](/experiments/interpreting-results/read-results) and [Experiment](/experiments/monitor) results for your feature gates and experiments. You can view your updated segment lists on the [Segments](https://console.statsig.com/segments) page. ## Event configuration 1. From the **[API Keys](https://console.statsig.com/api_keys)** tab in the Statsig console, copy the Statsig **Client-SDK API key**. 2. Go to the Hightouch [**Destinations** overview page](https://app.hightouch.com/destinations) and click the **Add destination** button. Select **Statsig** and click **Continue**. Enter the **Client-SDK API key** and click **Continue**. 3. Give your destination a descriptive name, for example, "Statsig prod." 4. After you set up your Statsig destination and have a [model](https://hightouch.com/docs/getting-started/concepts#models) to pull data from, you can set up your sync configuration to begin syncing data. Go to the [**Syncs** overview page](https://app.hightouch.com/syncs) and click the **Add sync** button to begin. Then, select the relevant model and the Statsig destination you previously set up. 5. Select **Events** as the sync type. 6. Enter either a static value or select a column that contains **Event names**. 7. Optionally, select a column that contains the event timestamp. If empty, Statsig uses the time the event arrives at the server. 8. Choose source columns to sync as event metadata and user attributes, such as the **User ID**. 9. Finally, select whether you want the first sync to backfill event data or not. For more information about configuration options, refer to [Hightouch’s Statsig docs](https://hightouch.com/docs/destinations/statsig#events). 10. Run the sync and check that events appear in your Statsig **[Metrics](/metrics/introduction)** tab. ## Segment configuration The Hightouch integration lets you keep your segments up-to-date by adding or removing members based on changes in your source dataset. 1. From the **[API Keys](https://console.statsig.com/api_keys)** tab in the Statsig console, copy the Statsig **Console API key.** 2. Go to the Hightouch [**Destinations** overview page](https://app.hightouch.com/destinations) and click the **Add destination** button. Select **Statsig** and click **Continue**. Enter the **Client-SDK API key** and click **Continue**. 3. Give your destination a descriptive name, for example, "Statsig prod." 4. After you set up your Statsig destination and have a [model](https://hightouch.com/docs/getting-started/concepts#models) to pull data from, you can set up your sync configuration to begin syncing data. Go to the [**Syncs** overview page](https://app.hightouch.com/syncs) and click the **Add sync** button to begin. Then, select the relevant model and the Statsig destination you previously set up. 5. Select **Segment** as the sync type. 6. Select whether you would like to create a new audience or use an existing audience to sync data to. If creating a new audience, you can give it a name. If you leave this input blank, Hightouch uses the name of your model for the audience. 7. Select the audience's ID type: either **userID** or **stableID**. 8. Select the source column and Statsig field to match records on. For more information, refer to Hightouch's docs on [record matching](https://hightouch.com/docs/syncs/record-matching). 9. [Schedule your sync](https://hightouch.com/docs/syncs/schedule-sync-ui) to run as frequently as you need. You can view your updated segment lists on the **[Segments](https://console.statsig.com/segments)** page. ## Troubleshooting For questions, contact [Hightouch support](https://www.notion.so/Hightouch-page-in-Statsig-docs-42b88b32b82b491d9baf1694049955ab) for assistance. ================================================================================ # Mixpanel URL: https://docs.statsig.com/integrations/data-connectors/mixpanel Updated: 2026-07-11 ================================================================================ ## How the Mixpanel integration works The [Mixpanel](https://mixpanel.com/) integration has two functions. * Incoming: Statsig can sync your Mixpanel user cohorts with a Statsig ID list segment. * Outgoing: Statsig can forward Statsig events to Mixpanel. ## Cohort syncing {% callout type="note" %} Ingestion with this integration is available only for Statsig Cloud. For Warehouse Native, create a metrics source that references this data in your warehouse. {% /callout %} Statsig can ingest user information through [Mixpanel Cohort Syncing](https://developer.mixpanel.com/docs/cohort-webhooks) 1. On Statsig, navigate to [Segments](https://console.statsig.com/segments) on the left navigation menu and create a segment. * **Name**: Must match the name of your Mixpanel cohort. * **Type of segment**: Should be ID List. {% figure %} ![statsig-segment-config](/images/integrations/data-connectors/mixpanel/id_list_segments.png) {% /figure %} 2. On Mixpanel, click on the Data Management navbar item and choose Integrations from the dropdown. {% figure %} ![mixpanel-integration-menu](/images/integrations/data-connectors/mixpanel/195173611-170f02df-543f-4198-8589-7db313f4dd9f.png) {% /figure %} 3. In the list of integrations, scroll until you find Custom Webhook and then select it. {% figure %} ![mixpanel-custom-webhook](/images/integrations/data-connectors/mixpanel/195173845-73ac1d5b-94b4-4daf-bf36-102eabd5d1fe.png) {% /figure %} 4. In the dialog that appears, paste the url below, substituting the SERVER\_SECRET\_KEY with a "Server Secret Key" found in [Project Settings](https://console.statsig.com/api_keys), then click Continue. ``` https://api.statsig.com/v1/webhooks/mixpanel?statsig-api-key=SERVER_SECRET_KEY ``` {% figure %} ![custom-webhook-dialog](/images/integrations/data-connectors/mixpanel/195174446-d3e8078c-ebaf-4ed4-bb5b-f8195694bade.png) {% /figure %} 5. Click "Enable" (or "Confirm" if you are updating the integration). 6. You can now start a cohort sync job on Mixpanel from the Cohorts page. ## Configuring outbound events To export your Statsig events to Mixpanel: 1. Get a copy of your "Project Token" from Mixpanel by following this [guide](https://help.mixpanel.com/hc/en-us/articles/115004502806-Find-Project-Token-). 2. Paste your project token into the Outgoing configuration on the Statsig integration panel. 3. Select your **Data Residency Region** based on your Mixpanel project's data residency configuration: * **US** (default): Events are sent to the global `api.mixpanel.com` endpoint * **EU**: For projects using [EU data residency](https://docs.mixpanel.com/docs/privacy/eu-residency) * **India**: For projects using [India data residency](https://docs.mixpanel.com/docs/privacy/in-residency) 4. Click "Enable" (or "Confirm" if you are updating the integration). {% figure %} ![mixpanel-outgoing-configuration](/images/integrations/data-connectors/mixpanel/mixpanel-outgoing-data-residency.png) {% /figure %} 5. Verify Statsig is forwarding your events by visiting the Events tab on Mixpanel. {% figure %} ![mixpanel-event-tab](/images/integrations/data-connectors/mixpanel/195172678-8ff14bfe-6400-4660-b1dd-fef1780ddcd5.png) {% /figure %} ### Filtering events You can customize which events are sent to Mixpanel using [Event Filtering](/integrations/event_filtering#outgoing-event-filtering). ================================================================================ # mParticle URL: https://docs.statsig.com/integrations/data-connectors/mparticle Updated: 2026-07-11 ================================================================================ ## How the mParticle integration works Enabling the [mParticle](https://www.mparticle.com/) integration for Statsig allows Statsig to receive events from mParticle. You can find all events that Statsig receives from mParticle in the [Metrics](/metrics/introduction) tab in the Statsig console. Statsig automatically includes these events in [Pulse](/experiments/interpreting-results/read-results) and [Experiment](/experiments/monitor) results for your feature gates and experiments. ## Configuring incoming events {% callout type="note" %} Ingestion with this integration is available only for Statsig Cloud. For Warehouse Native, create a metrics source that references this data in your warehouse. {% /callout %} 1. From the [API Keys](https://console.statsig.com/api_keys) tab in the Statsig console, copy the Statsig `Server Secret Key`. 2. Use your Statsig `Server Secret Key` to configure a Statsig Event Integration via mParticle's integrations directory. ### Mapping user IDs To associate your mParticle events with your Statsig Feature Gates or Experiments, use mParticle's [IDSync framework](https://docs.mparticle.com/guides/idsync/introduction/) to ensure your mParticle events pass along the same user IDs used with the Statsig SDK. ## Configuring outbound events To export your Statsig events to mParticle: 1. Go to your mParticle account and choose `Setup` then `Inputs` on the left-hand column to start configuring your integration. {% figure %} <img src="/images/integrations/data-connectors/mparticle/155385379-f111a536-d8e4-4ca6-ad37-47d926c254b9.png" width="200px" alt="mParticle navigation drawer highlighting the Setup to Inputs path" /> {% /figure %} 2. Click on the `Feeds` tab within the page that loads, click on `Add Feed Input` button, and then search for `Statsig` and click on the option. {% figure %} <img src="/images/integrations/data-connectors/mparticle/155385384-b75e327d-96e8-49fc-8cb0-07516f5d3c79.png" alt="Add Feed Input dropdown with Statsig option selected" /> {% /figure %} 3. Provide a name for your `Statsig Feed` and click `Save`. {% figure %} <img src="/images/integrations/data-connectors/mparticle/155385386-aa982102-63ff-4f1a-905d-9889684cf3ae.png" width="400px" alt="Statsig feed configuration form showing name field and active toggle" /> {% /figure %} 4. Copy the `Server to Server Key` and `Server to Server Secret` for the next step. 5. Log into the Statsig console and navigate to the [**Integrations**](https://console.statsig.com/integrations) page. 6. Click on the `mParticle` card and switch to the `Outbound` tab. Paste the `Server to Server Key` and `Server to Server Secret` in their respective boxes and **Enable** the integration. ================================================================================ # RevenueCat URL: https://docs.statsig.com/integrations/data-connectors/revenuecat Updated: 2026-07-11 ================================================================================ {% callout type="note" %} Ingestion with this integration is available only for Statsig Cloud. For Warehouse Native, create a metrics source that references this data in your warehouse. {% /callout %} ## How the RevenueCat integration works Enabling the RevenueCat integration allows Statsig to pull billing, subscription, and revenue metrics into your Statsig projects. This lets you optimize purchases and revenue using Statsig's feature gates or experimentation tools without any additional logging. Statsig integrates with RevenueCat through a webhook and receives data as described [in the RevenueCat documentation](https://docs.revenuecat.com/docs/webhooks). ## Configuring incoming metrics 1. Copy your **Statsig Server Secret Key** from the [API Keys](https://console.statsig.com/api_keys) tab in the Statsig console. 2. Navigate to your app in the RevenueCat dashboard and choose **Statsig** from the Integrations menu. 3. Enter your **Statsig Server Secret** and click **Save**. {% figure %} ![RevenueCat integration settings entering Statsig secret](/images/integrations/data-connectors/revenuecat/f8b5f66-Screen_Shot_2021-11-05_at_9.20.40_AM.png) {% /figure %} 4. On the Statsig [Integrations](https://console.statsig.com/integrations) page, enable the RevenueCat integration. If you're running an experiment with the user as your unit type, you must set the RevenueCat `appUserID` to match the `userID` that you log with the Statsig SDK. For example, set this when you expose the user to a Statsig feature gate or experiment. Refer to [how to set the `appUserID` on RevenueCat](https://docs.revenuecat.com/docs/user-ids#provided-app-user-id). By default, Statsig doesn't ingest [Sandbox events](https://docs.revenuecat.com/docs/webhooks#testing) from RevenueCat, to reduce noise from test events. You can explicitly enable ingestion of Sandbox events using the Statsig [Integrations](https://console.statsig.com/integrations) page while debugging. ================================================================================ # RudderStack URL: https://docs.statsig.com/integrations/data-connectors/rudderstack Updated: 2026-07-11 ================================================================================ ## How the RudderStack integration works Enabling the RudderStack integration for Statsig allows Statsig to pull in your RudderStack events. This allows you to run your experiment analysis on Statsig with all of your existing events from RudderStack without requiring any additional logging. When Statsig receives events from RudderStack, the events are visible and aggregated in the [Metrics](/metrics/introduction) tab in the Statsig console. These events are automatically included in your [Pulse](/experiments/interpreting-results/read-results) results for A/B tests with Statsig's [feature flags](/feature-flags/overview) and all your [Experiment](/experiments/monitor) results. ## Configuring incoming events {% callout type="note" %} Ingestion with this integration is available only for Statsig Cloud. For Warehouse Native, create a metrics source that references this data in your warehouse. {% /callout %} To ingest your events from RudderStack, 1. On [app.rudderstack.com](https://app.rudderstack.com/), navigate to "Connections" and click **Add Destination** . 2. Search for “Statsig” in the Destinations Catalog, and select the “Statsig” destination. 3. Give your connection a name and choose which Source should send data to the “Statsig” destination. 4. From the [Statsig dashboard](https://console.statsig.com/api_keys), copy the Statsig "Server Secret Key”. 5. Enter the Statsig “Server Secret Key” in the “Statsig” destination settings in RudderStack. 6. On the Statsig [Integration page](https://console.statsig.com/integrations) enable the RudderStack integration. 7. As your RudderStack events flow into Statsig, you see a live **Log Stream** in the [Metrics](/metrics/introduction) tab in the Statsig console. You can click one of these events to see the details logged as part of the event. {% figure %} ![Statsig metrics log stream interface](/images/integrations/data-connectors/rudderstack/171243268-29d4717f-05f9-4ea7-b658-dfc434d06da6.png) {% /figure %} #### User IDs and custom IDs Statsig automatically detects the `event` and `userID` fields that you log through your RudderStack events. If you're running an experiment with the user as your unit type, this userID should match the user identifier that you log with the Statsig SDK. If you're using a [custom ID](/guides/experiment-on-custom-id-types) as the unit type for your experiment, you can provide this identifier using the key `statsigCustomIDs` as part of the RudderStack `properties` field as shown below. ```bash title="JSON Body" { ... properties: { "statsigCustomIDs": [ "companyID", "<this_company_id>", "stableID", "<this_stable_id>",] } } ``` The `statsigCustomIDs` field in properties should be an array, where the even index is the name of the user ID type and the odd index is the value of the previous element in the array. After you create this custom ID type on Statsig (under **ID Type Settings** in your [Project Settings](https://console.statsig.com/settings)), Statsig automatically recognizes these custom identifiers to compute your experiment results. #### Environments By default, all events are treated as "production" events, but you can differentiate your event traffic by specifying the environment the events are coming from. This prevents non-production data from appearing in your production metrics. To include the environment tier, add it to the properties object of your event. The required format is below: ```json { ... "properties": { "statsigEnvironment": { "tier": "staging" } } } ``` To learn more about environments, refer to [Using Environment](/guides/using-environments). ## Configuring outbound events To export your Statsig events to RudderStack, 1. Log into the Statsig console and navigate to the [**Integrations**](https://console.statsig.com/integrations) page. 2. Click on the **RudderStack** card and switch to the **Outbound** tab. 3. Follow the steps outlined in [RudderStack's Webhook Source](https://www.rudderstack.com/docs/stream-sources/webhook-source/) to get the required "Write Key" and "Data Plane URL". 4. After filling in the fields, click enable to save your changes. ## Filtering events You can customize which events are sent and received through RudderStack using [Event Filtering](/integrations/event_filtering). ================================================================================ # Segment URL: https://docs.statsig.com/integrations/data-connectors/segment Updated: 2026-07-11 ================================================================================ Enabling the Segment integration for Statsig allows Statsig to pull in your Segment events. This allows you to run your experiment analysis on Statsig with all of your existing events from Segment without requiring any additional logging. When Statsig receives events from Segment, the events are visible and aggregated in the [Metrics](/metrics/introduction) tab in the Statsig console. These events are automatically included in your [Pulse](/experiments/interpreting-results/read-results) results for A/B tests with Statsig's feature gates and all your [Experiment](/experiments/monitor) results. ### Supported Segment event types * [Track](https://segment.com/docs/connections/spec/track/) * [Page](https://segment.com/docs/connections/spec/page/) * [Group](https://segment.com/docs/connections/spec/group/) * [Screen](https://segment.com/docs/connections/spec/screen/) * [Identify\*](https://segment.com/docs/connections/spec/identify/) {% callout type="info" %} Identify calls are only supported for syncing Segment Engage Audiences with Statsig Segments {% /callout %} ### Benefits of using the Segment integration The Segment integration has several benefits over other methods of event ingestion: * Customers ingesting data with Segment can quickly populate Statsig with metrics and can typically get started within a day. * Customers only need to use Statsig's assignment SDKs (gate/experiment allocation), which simplifies code and engineering workflows. * Additional logging can be done through the [event logging SDKs](/guides/logging-events#logging-events-via-sdks), but requires additional code orchestration and a collection window. * With [event filtering](/integrations/event_filtering) you can control which events are ingested and make billing more predictable. * If you have [Segment Replay](https://segment.com/docs/guides/what-is-replay/), you can forward up to 7 days of historical events to Statsig for analysis. ## Configuring inbound events into Statsig {% callout type="note" %} Ingestion with this integration is available only for Statsig Cloud. For Warehouse Native, create a metrics source that references this data in your warehouse. {% /callout %} To send events collected from Segment into Statsig, you must configure the Statsig Destination within your Segment account: ### Using OAuth The easiest way to connect Statsig to Segment is through OAuth. 1. To get started, within the Statsig Console, go to **project settings → integrations → Segment → Enable** 2. Click “Configure Segment OAuth” {% figure %} ![Statsig integration settings showing Segment OAuth button](/images/integrations/data-connectors/segment/262495276-2fa7d870-0f02-44cf-aa29-9f04b0b12220.png) {% /figure %} 3. Select the workspace and source that will send data to Statsig. Click “Allow”: {% figure %} ![Segment OAuth consent screen selecting workspace and source](/images/integrations/data-connectors/segment/262495274-71e8dbf3-590f-44bf-90bd-5723dd3c60ed.png) {% /figure %} 4. After the integration is enabled, you can configure event filtering and map additional identifiers. This is useful for mapping device-level identifiers and `anonymousIds` generated from Segment. Statsig recommends creating a custom ID called `segmentAnonymousId` and mapping the `anonymousId` from Segment to it. {% figure %} ![Statsig Segment integration panel with identifier mapping options](/images/integrations/data-connectors/segment/279802493-6956e8f7-28f1-4ac9-88d1-96710240373b.png) {% /figure %} ### Manual configuration If you are unable to connect to Segment through OAuth, you can manually connect Statsig to Segment by configuring the following: 1. Within the [Segment App](https://app.segment.com), navigate to your Destinations, and select "**Add Destination**" 2. Search for “**Statsig**” and select the destination {% figure %} ![Segment destination catalog listing Statsig](/images/integrations/data-connectors/segment/262495271-68b0959b-b1d8-4041-a413-e0b2c3802377.png) {% /figure %} 3. Select "Statsig" from the list of available integrations, and then select **sources** that will send data to Statsig. {% figure %} ![Segment destination setup selecting sources](/images/integrations/data-connectors/segment/262495270-832e8d93-1896-4c24-bf42-c56f627eecbc.png) {% /figure %} 4. You must provide a Statsig Server SDK key. You can copy an existing server key or create a new one from the [Statsig console settings](https://console.statsig.com/api_keys). * Create or copy a server SDK key {% figure %} ![Statsig API keys page showing server key](/images/integrations/data-connectors/segment/262495269-88b47af6-7313-47e9-a767-e1eb12b4e47b.png) {% /figure %} {% figure %} ![Segment destination configuration entering Statsig server key](/images/integrations/data-connectors/segment/262504950-c5b21cc0-5b51-4a55-997a-74b8a45b2807.png) {% /figure %} * Put your Server Secret Key in the “API Key” field in the Statsig Destination {% figure %} ![Segment destination API key input](/images/integrations/data-connectors/segment/262506044-77bef6e4-c79d-47b2-94cc-3f6b3dc06489.png) {% /figure %} 5. To set up mappings, you must enable the Segment Integration in Statsig. Go to the Statsig Console, then Project Settings, then the Integrations tab, then Segment, and click **Enable**: {% figure %} ![Statsig Segment integration enable toggle](/images/integrations/data-connectors/segment/262506930-12225ae2-a12e-4b0d-9efc-34c03a840f9f.png) {% /figure %} * After the integration is enabled, you can configure event filtering and map additional identifiers. This is useful for mapping device-level identifiers and `anonymousIds` generated from Segment. {% figure %} ![Statsig identifier mapping form](/images/integrations/data-connectors/segment/279802493-6956e8f7-28f1-4ac9-88d1-96710240373b.png) {% /figure %} 6. Verify that Statsig is receiving events using the [Segment Event Tester](https://segment.com/docs/connections/test-connections/). * In the event tester, send a test event to verify any filtering and mapping you have set up: {% figure %} ![Segment integration panel showing outgoing toggle](/images/integrations/data-connectors/segment/262495256-5097e8a0-ca4f-4e1a-b2b6-3337d6fe7bff.png) {% /figure %} * In the your [Statsig Console Events Explorer](https://console.statsig.com/metrics/events), click on the test event in the log stream and verify the event: {% figure %} ![Segment destination configuration for outgoing data](/images/integrations/data-connectors/segment/262501895-001d1179-510a-4171-9ab9-f14544c94374.png) {% /figure %} * In this example, the `anonymousId` is mapped to Statsig’s `segmentAnonymousId` based on the above mapping. Statsig infers the User ID from the Segment `userID` field. {% figure %} ![Statsig integration for Segment Engage audience mapping](/images/integrations/data-connectors/segment/279802493-6956e8f7-28f1-4ac9-88d1-96710240373b.png) {% /figure %} 7. As your Segment events flow into Statsig, you see a live **Log Stream** in the [Metrics](/metrics/introduction) tab in the Statsig console. You can click one of these events to see the details logged as part of the event. {% figure %} ![Log details panel showing Segment event payload fields and metadata](/images/integrations/data-connectors/segment/150830169-17564060-816b-4c5c-ade9-10bf6274265a.png) {% /figure %} ## Working with users Statsig joins incoming user identifiers to whichever [unit of randomization](/experiments/overview#choosing-the-right-randomization-unit) you choose. This allows you to be flexible with your experimentation and enables testing on known (userID) and unknown (anonymousID) traffic as well as any custom identifiers your team may have (deviceID, companyID, vehicleID, etc). ### User IDs and custom IDs Statsig automatically detects the `event` and `userId` fields logged through your Segment events (refer to [`track`](https://segment.com/docs/connections/spec/track/) for an example). If you're running an experiment with the userId as your unit type, this `userID` should match the user identifier that you log with the Statsig SDK. If you're using a [custom ID](/guides/experiment-on-custom-id-types) as the unit type for your experiment, you can provide this identifier using the key `statsigCustomIDs` as part of the Segment `properties` field as shown below. ```bash title="JSON Body" { ... properties: { "statsigCustomIDs": [ "companyID", "<this_company_id>"] } } ``` The `statsigCustomIDs` field in properties should be an array, where the even index is the name of the user ID type and the odd index is the value of the previous element in the array. After you create this custom ID type on Statsig (under **ID Type Settings** in your [Project Settings](https://console.statsig.com/settings)), Statsig automatically recognizes these custom identifiers to compute your experiment results. ### Anonymous users #### Mapping anonymous users The Segment integration also allows the mapping of top-level fields to custom IDs you define in Statsig. To do this, visit the Segment panel on the Statsig Integrations page and look for the "Map Identifier" section. Here you can choose fields to map to a [Custom ID](/guides/experiment-on-custom-id-types). This is useful for working with the [Segment anonymous ID](https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/identity/#anonymous-ids), which is passed as an event field `anonymousId`. By defining a custom ID called `segmentAnonymousId`, Statsig can map your anonymous Segment traffic to your metric data. {% callout type="warning" %} Values passed in `properties.statsigCustomIDs` take precedence over mapped identifiers below. {% /callout %} ![Segment integration card with Map Identifiers form mapping anonymousId to custom ID](/images/integrations/data-connectors/segment/279802493-6956e8f7-28f1-4ac9-88d1-96710240373b.png) #### Experimenting on anonymous traffic For example, if you're running experiments on anonymous users, you can use Segment's `anonymousId` as the unit of randomization. First, [add a new customer identifier to Statsig](/guides/experiment-on-custom-id-types#step-1---add-companyid-as-a-new-id-type-in-your-project-settings). In this example, the new custom ID is called `segmentAnonymousId`. Then, when [initializing](/client/javascript-sdk) the Statsig SDK, pass the Segment `anonymousId` to Statsig as a custom ID if it is available. For example, your Statsig initialization may look like this: ```jsx import { StatsigClient } from '@statsig/js-client'; await Statsig.initialize( "client-sdk-key", ); const client = new StatsigClient(sdkKey, { userID: "some_user_id", customIDs: { segmentAnonymousId: analytics.user().anonymousId() } }, { environment: { tier: "production" } } ); ``` You can access Segment's `anonymousId` using `analytics.user().anonymousId()` as [outlined in the Segment docs here](https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/identity/). {% callout type="note" %} The Segment SDK may initialize after the Statsig SDK, and the `anonymousId` may not be available in that scenario. Check this in your specific implementation. If you're using vanilla JavaScript, wait for the `anonymousId` from the Segment SDK after initialization, then call the `updateUser()` method on the Statsig SDK to update this ID. {% /callout %} #### Example mapping flow Refer to the following diagram for an overview of mapping `anonymousIds` in Segment to a custom ID representing anonymous users in Statsig: {% figure %} ![Identifier mapping flowchart](/images/integrations/data-connectors/segment/283278011-3c22e6e8-ab36-4844-aee2-b6630ecda4de.png) {% /figure %} 1. Initialize the Statsig SDK with your [Statsig User](/sdks/user), which contains an optional `userID` value and a `customID` that you've created in the Statsig UI - `segmentAnonymousId` in this example. 2. As you orchestrate features/experiments, Statsig associates this user to a variant using the unit of randomization chosen. For anonymous users, Statsig uses `segmentAnonymousId`. 3. Your existing Segment implementation tracks user traffic and associates anonymous users to the top-level field `anonymousId`. 4. This `anonymousId` is mapped in Statsig (to `segmentAnonymousId`), properly associating the identifier used in experiment exposures to the same identifier used to track user actions. ### Syncing Statsig Segment ID lists with Segment Engage Audiences Using [Segment Engage Audiences](https://segment.com/docs/engage/audiences/), you can maintain a list of users for targeting using [Statsig Feature Gates](/feature-flags/overview). To configure this: 1. Create a [Statsig ID List Segment](/segments/create-new) on the Statsig Console. 2. Follow the [Segment guide for Audiences](https://segment.com/docs/engage/audiences/) to create a new Audience and choose `Statsig` as a Destination. The `audience_key` must match the ID of the `Statsig ID List Segment` created. After you complete these steps, Statsig syncs your Segment Audience, and you can target those users for features you develop or experiments you run. ### Custom properties Passing [custom properties to a Statsig User](/sdks/user#user-attributes) (see `custom` field) enables targeting on specific cohorts of users in feature gates and experimentation. Custom user properties also let you filter results to specific populations (for example: android/iOS, isVIP) when [reading pulse results](/experiments/interpreting-results/custom-queries#running-a-custom-query). If you're using custom fields to [target users](/feature-flags/conditions#custom) in your feature gates, provide these properties through Segment using the key `statsigCustom` as part of the Segment `properties` field, as an array of key-value pairs: `[key1, value1, key2, value2, ...]`. For example: ```bash title="JSON Body" { ... properties: { "statsigCustom": [ "isVIP", "true", "marketing_campaign", "abx343", ...] } } ``` ## Configuring outbound events to Segment To export your Statsig events to Segment, 1. In your Segment app, add a new source → search for Statsig → click **Next** {% figure %} ![Segment outbound configuration selecting Statsig destination](/images/integrations/data-connectors/segment/263066497-1856270d-adb9-4ca7-9349-1b4b393b1aa2.png) {% /figure %} 2. Name your source → Create Source → click **Done**: {% figure %} ![Segment workspace auth prompt for outbound integration](/images/integrations/data-connectors/segment/263066506-cbe5beca-5971-4baf-ae41-571bd7c1ae52.png) {% /figure %} 3. Locate your **Write Key** and copy it. 4. Log into the Statsig console and navigate to the [**Integrations**](https://console.statsig.com/integrations) page. 5. Click on the **Segment** card and switch to the **Outbound** tab, paste the **Write Key** into the **API Key** text box shown below, and click **Enable**. {% figure %} ![Segment outbound integration configuration interface](/images/integrations/data-connectors/segment/150827399-333d9064-de1c-4f4e-bc33-51a46a83531d.png) {% /figure %} ### Outbound event schema Statsig exports log events and exposure events to segment as `track` events: ``` { type: 'track', userId: event.userID, timestamp: Number(event.timestamp), event: event.eventName, context: { user: event.user, value: event.value, metadata: event.metadata, library: { name: 'statsig', version: '1.0', }, stableID: event.statsigMetadata.stableID // stableID, if you are relying on that for anonymous users }, properties: { value: event.value, metadata: event.metadata, }, }; } ``` Config Change events follow this schema: ``` { userId: statsigUserID, timestamp: Date.now(), event: 'statsig::config_change', context: { library: { name: 'statsig', version: '1.0', }, }, properties: { author: author, configName: configName, description: changeDescription, environment: environment, }, }, ``` ## Environments By default, all events are treated as "production" events, but you can differentiate your event traffic by specifying the environment the events are coming from. This prevents non-production data from appearing in your production metrics. To include the environment tier, add it to the properties object of your event. The required format is below: ```json { ... "properties": { "statsigEnvironment": { "tier": "staging" } } } ``` To learn more about environments, refer to [Using Environment](/guides/using-environments). ## Working with Segment metrics in the Statsig UI Segment events are piped into Statsig and are accessible in the metrics console like any other event. These metrics are also available as monitoring metrics in your feature gates and experiments, so you can use your existing metric collection through Segment with Statsig's experimentation platform. {% callout type="note" %} Segment events are prepended with `segment::` so they can be easily distinguished from other sources {% /callout %} {% figure %} ![Segment destination configuration for first exposures](/images/integrations/data-connectors/segment/263068916-a7bb398b-dd5f-4a0c-9c72-924f384a54b7.png) {% /figure %} These metrics appear in pulse results among other monitoring metrics: {% figure %} ![Segment HTTP endpoint settings](/images/integrations/data-connectors/segment/263068905-12265a7e-e13a-4350-8bda-76ecfb3a805c.png) {% /figure %} ## Filtering events You can customize which events are sent and received through Segment using [Event Filtering](/integrations/event_filtering). ================================================================================ # Stitch URL: https://docs.statsig.com/integrations/data-connectors/stitch Updated: 2026-07-11 ================================================================================ ## How the Stitch integration works Enabling the Stitch integration for Statsig allows Statsig to push events to your Stitch account through a webhook. This allows you to forward Statsig data to any connectors available from Stitch. ## Configuring outbound events 1. Follow the steps in the [Stitch Webhook Setup Guide](https://www.stitchdata.com/docs/integrations/webhooks/stitch-incoming-webhooks#setup) to create a new Webhook URL. 2. On the Statsig [Integrations](https://console.statsig.com/integrations) page, enable the Stitch integration by pasting in the Stitch Webhook URL and click **Confirm**. ### Event format Events are sent in batches in JSON format. Each Statsig event has the following structure: | Field | Type | Description | | --------------- | ------ | ------------------------------------------------------------------------------------------------ | | event | String | Name of the event provided | | user | JSON | [Statsig User Object](/sdks/user) | | userId | String | User ID provided | | stableId | String | Stable ID | | timestamp | Number | Timestamp in MS of the event | | value | String | Value of the event provided | | metadata | JSON | Both custom metadata provided and metadata related to the logging of this event added by Statsig | #### Custom event formatting - logEvent > ```json { "userId": "a_user", "stableId": "123", "timestamp": 1655231253265, "event": "my_custom_event", "context": { "user": { "userID": "a_user", "email": "a.user@email.com" }, "value": "a_custom_value", "metadata": { }, "library": { "name": "statsig", "version": "1.0" } } } ``` #### Feature gate exposure formatting - checkGate > ```json { "userId": "a_user", "stableId": "123", "timestamp": 1655231253265, "event": "statsig::gate_exposure", "context": { "user": { "userID": "a_user", "email": "a.user@email.com" }, "value": "", "metadata": { "gate": "a_gate", "gateValue": "false", "ruleID": "default", "reason": "Network", "time": "1655231249644" }, "library": { "name": "statsig", "version": "1.0" } } } ``` #### Dynamic config exposure formatting - getConfig > ```json { "userId": "a_user", "stableId": "123", "timestamp": 1655231253265, "event": "statsig::config_exposure", "context": { "user": { "userID": "a_user", "email": "a.user@email.com" }, "value": "", "metadata": { "config": "a_config", "ruleID": "default", "reason": "Network", "time": "1655231249644" }, "library": { "name": "statsig", "version": "1.0" } } } ``` #### Experiment exposure formatting - getExperiment > ```json { "userId": "a_user", "stableId": "123", "timestamp": 1655231253265, "event": "statsig::experiment_exposure", "context": { "user": { "userID": "a_user", "email": "a.user@email.com" }, "value": "", "metadata": { "config": "an_experiment", "ruleID": "4SauZJcM1T7zNvh1igBjwE", "reason": "Network", "time": "1655231249644", "experimentGroupName": "Control" }, "library": { "name": "statsig", "version": "1.0" } } } ``` ## Filtering events After you enable outbound events to Stitch, you can select which categories of Statsig events to export by clicking the **Event Filtering** button and checking the appropriate boxes as shown below. {% figure %} ![Initial Setup Dialog](/images/integrations/data-connectors/stitch/183725742-b58c87a7-fbb4-4f30-be9a-9d20c08cd7d0.png) {% /figure %} {% figure %} ![Event Filtering](/images/integrations/data-connectors/stitch/183725752-b79b5c3f-d275-4dec-80ba-04fa9a73da0d.png) {% /figure %} ================================================================================ # Data Warehouse Ingestion URL: https://docs.statsig.com/data-warehouse-ingestion/introduction Updated: 2026-07-11 ================================================================================ {% figure %} ![Slide 4_3 - 2](/images/data-warehouse-ingestion/introduction/187794828-333622ec-6db2-4936-987d-efbef4ba9a47.png) {% /figure %} ## How data warehouse ingestion works Statsig Cloud can directly ingest data from your Data Warehouse. This lets you send raw events and pre-computed metrics for tracking and experimental measurement. Statsig supports ingestion from the following providers: {% callout type="warning" %} Statsig supports multiple data connections to your project, but only a single export connection. {% /callout %} 1. [BigQuery](/data-warehouse-ingestion/bigquery) 2. [Redshift](/data-warehouse-ingestion/redshift) 3. [Snowflake](/data-warehouse-ingestion/snowflake) 4. [Databricks](/data-warehouse-ingestion/databricks) 5. [Synapse](/data-warehouse-ingestion/synapse) 6. [S3](/data-warehouse-ingestion/s3) 7. [Athena](/data-warehouse-ingestion/athena) {% callout type="tip" %} Warehouse Native users: You're viewing the Cloud docs for this page. If you configured your project as [Statsig Warehouse Native](/statsig-warehouse-native/introduction/), your data is already available if you completed the [quickstart](../statsig-warehouse-native/guides/quick-start). {% /callout %} ### How it works In Statsig console, you can: 1. Set up connection to your data warehouse 2. Query your data warehouse for appropriate data 3. Map your data fields to Statsig's expected schema 4. Bulk ingest & schedule future ingestions Ingestion runs on a daily schedule. Statsig runs a query you provide on your data warehouse, downloads the result set, and materializes the results into your console the same as those that came in through the SDK. If data lands late or is updated, Statsig detects this change and reloads the data for that day (details below). ### Begin data ingestion To begin ingestion from a Data Warehouse: 1. Go to your Statsig Console 2. Navigate to Data tab on the side navigation bar 3. Go to the "Ingestion" tab {% figure %} <img src="/images/ingestions.png" alt="Statsig Ingestions page prompting you to connect a data warehouse" width="700" /> {% /figure %} Set up connections with the required credentials and map your data fields to the fields Statsig expects to ingest. Refer to the warehouse-level setup documentation for more details. ### Connection flow Go to the docs sidebar to find the documentation for the data warehouse of your choice. After connecting, provide a SQL query to generate a view of data for Statsig to ingest. {% figure %} <img src="/images/ingestions-2.png" alt="Query Event Data Columns interface showing SQL editor and expected columns panel" width="700" /> {% /figure %} ### Data mapping After connecting and providing a SQL query, map columns in your data output to the fields Statsig expects. Statsig runs a small sample query to check for basic data type issues. To process data correctly, each ingestion must include columns for unit_id, event_name, timestamp, and metadata. {% figure %} <img src="/images/ingestions-3.png" alt="Event data column mapping workflow with required fields for timestamp, event name, and user ID" width="700" /> {% /figure %} For more information, refer to [data mapping](/data-warehouse-ingestion/data_mapping). ### Scheduling ingestion and backfilling Statsig supports multiple schedules for ingestion. At the scheduled window, Statsig checks if data is present in your warehouse for the latest date, and loads it if it exists. Statsig checks the underlying source table for changes. For up to 3 days after initial ingestion, Statsig checks for >5% changes in row counts and reloads the data if Statsig detects a change. Statsig also supports a user-triggered backfill. This is useful if a specific metric definition has changed, or you want to resync data older than a few days. To change your ingestion schedule or start a backfill, click the ellipses at the end of the data connection and navigate to these menus. Statsig bills reloading data and backfilling metrics and events as any other [custom event](/metrics/raw-events#billing). {% callout type="note" %} Statsig doesn't support auto-generated **User Accounting Metrics** for data warehouse ingestions. {% /callout %} ### Troubleshooting ingestions If any ingestion errors occur, Statsig notifies you in the project and directs you to the Ingestions page. You can diagnose an error directly in Statsig by following the step-by-step triage flow. Common errors include missing permissions and outdated credentials. ### API triggered ingestion (mark_data_ready) Enterprise customers can trigger ingestion for `metrics` or `events` using the Statsig API. Triggering ingestion runs your daily ingestion immediately. This is useful for companies whose data availability timing varies day over day and need data to land in Statsig as soon as possible. Enable this by selecting "API Triggered" as your ingestion schedule. With API Triggered enabled, there's no automatic ingestion, but Statsig still re-syncs data after the initial ingestion if Statsig detects a change. To trigger ingestion, send a post request to the `https://api.statsig.com/v1/mark_data_ready_dwh` endpoint using your statsig API key. An example would be: ``` curl \ --header "statsig-api-key: <YOUR-SDK-KEY>" \ --header "Content-Type: application/json" \ --request POST \ --data '{"datestamps": "2023-02-20", "type": "events", "sources":["source1", "source2]}' \ "https://api.statsig.com/v1/mark_data_ready_dwh" ``` | Parameter | Required | Description | |---|---|---| | `datestamps` | Yes | The date of the data being triggered | | `type` | Yes | `metrics` or `events` | | `sources` | Only for multi-source ingestions | Array of strings representing the sources to trigger | {% callout type="note" %} Statsig rate limits this endpoint to one call every two hours. After triggering, there may be a few minutes' delay before status updates appear while Statsig creates compute resources. {% /callout %} ### Frequently asked questions Refer to the [FAQ page](/data-warehouse-ingestion/faq) for frequently asked questions. ================================================================================ # BigQuery URL: https://docs.statsig.com/data-warehouse-ingestion/bigquery Updated: 2026-07-11 ================================================================================ ## How BigQuery ingestion works To set up a connection with BigQuery, you need to: * Grant permissions to a Statsig-owned service account * Provide your BigQuery Project ID Enable the BigQuery source under Metrics > Ingestion > Add Source. ## Grant permissions to Statsig's service account Grant the following permissions from your Google Cloud console to allow Statsig to access your BigQuery data. 1. In your BigQuery's [IAM & Admin settings](https://console.cloud.google.com/iam-admin/), add the Statsig service account you copied in the Statsig Console as a new principal for your project, and give it the following roles: * `BigQuery User` {% figure %} ![BigQuery IAM permissions configuration](/images/statsig-warehouse-native/connecting-your-warehouse/bigquery/198107543-b3bcc19a-3231-4128-be42-a5dd52fb168a.png) {% /figure %} <br /> 2. Navigate to your [BigQuery SQL Workspace](https://console.cloud.google.com/bigquery), choose the dataset, click on "+ Sharing" -> "Permissions" -> "Add Principal" to give the same Statsig service account "BigQuery Data Viewer" role on the dataset. {% figure %} ![BigQuery dataset permissions setup](/images/integrations/data-imports/bigquery/175113611-90e618ad-f6e8-4005-933e-2a5660a14466.png) {% /figure %} The service account now has the required permissions to export data from this dataset. ## Find your BigQuery Project ID 1. Click on your Project Dropdown inside your Cloud Console. {% figure %} ![Frame 4](/images/statsig-warehouse-native/connecting-your-warehouse/bigquery/187518062-7027f682-d1fd-445e-9947-897e44ea929e.png) {% /figure %} 2. Copy the relevant Project ID from the modal. {% figure %} ![Frame 5](/images/statsig-warehouse-native/connecting-your-warehouse/bigquery/187517901-9e7fd237-8325-4254-a1bd-c75f0ea08497.png) {% /figure %} ================================================================================ # Redshift URL: https://docs.statsig.com/data-warehouse-ingestion/redshift Updated: 2026-07-11 ================================================================================ ## Connect Redshift to Statsig To set up a connection with Redshift, Statsig needs the following * Cluster Endpoint * Admin User Name * Admin User Password {% callout type="note" %} SHA256 passwords aren't supported. Use MD5 to avoid issues. {% /callout %} You can find this information in your AWS console within your specific cluster, as shown below. {% figure %} ![AWS Redshift cluster details highlighting endpoint and admin user](/images/statsig-warehouse-native/connecting-your-warehouse/redshift/187515405-17fa7d90-44e1-422f-87a7-cfde090637ed.png) {% /figure %} Statsig uses the admin user name and password to create a user with restricted access to query from your data warehouse. ## SSH tunneling For Redshift connections, Statsig supports creating an SSH tunnel into your Redshift cluster for more secure and private database access. To enable SSH tunneling, Statsig requires: * SSH Host * SSH Port * SSH User Statsig uses this information to generate an SSH key. Add this generated key to your `~/.ssh/authorized_keys` file on your SSH proxy machine to enable SSH tunneling. ### Custom user privileges To create a custom user with specific privileges instead of using an admin user, run the following code in your Redshift cluster with your admin user. Replace `<USER>` and `<PASSWORD>` with your values, which you copy into the Statsig console. ```sql # Create Statsig User CREATE USER <USER> WITH PASSWORD <PASSWORD> SYSLOG ACCESS UNRESTRICTED; # Give access to any Schemas that the Statsig User needs to read from GRANT USAGE ON SCHEMA <SCHEMA> to <USER>; GRANT SELECT ON ALL TABLES IN SCHEMA <SCHEMA> to <USER>; # Create a Schema for Statsig User to write temporary data to CREATE SCHEMA IF NOT EXISTS statsig_ingestion_staging; GRANT ALL ON SCHEMA <SCHEMA> TO <USER>; ``` After running the script, enter the `<USER>` and `<PASSWORD>` you created in the Statsig console during the Connection Set Up stage, under the Advanced settings options. {% figure %} ![Statsig Redshift connection setup form showing cluster endpoint and credentials fields](/images/data-warehouse-ingestion/redshift/188943178-6b541962-62da-4529-b5af-ff59b20b7a8d.png) {% /figure %} ================================================================================ # Snowflake URL: https://docs.statsig.com/data-warehouse-ingestion/snowflake Updated: 2026-07-11 ================================================================================ ## Connect Snowflake to Statsig To set up a connection with Snowflake, Statsig needs the following * Account Name * Database Name * Schema Name * Admin User Name * If authenticating through login credentials: * Admin User Password * If authenticating through key-pair authentication: * Private Key * Private Key Passphrase (Optional) Statsig uses the admin user name and password to create a user with restricted access to query from your data warehouse. To use a custom user instead, refer to [Custom user privileges](/data-warehouse-ingestion/snowflake#custom-user-privileges). ### Account name For the Account Name field, enter the value in the format `<id>.<region>.<provider>`. For example: `xy12345.us-central1.gcp` To get this information, navigate to the bottom left of your Snowflake console and copy the link URL, as shown below: {% figure %} ![Snowflake console footer showing account identifier link](/images/statsig-warehouse-native/connecting-your-warehouse/snowflake/187517221-4bb3dce3-8b8f-4f30-b4d4-fd12e5249722.png) {% /figure %} The copied URL will look something like this: `https://xy12345.us-central1.gcp.snowflakecomputing.com` Extract the required fields for Account Name from this URL. In this example, the Account Name is `xy12345.us-central1.gcp`. {% callout type="info" %} Using `<orgname>-<account_name>` for Account Name For the Account Name field, you can also enter your Snowflake [account identifier](https://docs.snowflake.com/en/user-guide/admin-account-identifier.html#format-1-preferred-account-name-in-your-organization), which typically takes the form `<orgname>-<account_name>`. To find the `<orgname>` in the Snowflake console, click on your account profile (usually at the bottom left) to view account details as shown below. {% figure %} ![Snowflake account profile interface](/images/statsig-warehouse-native/connecting-your-warehouse/snowflake/195217037-ad630f37-a8fe-4b61-823f-ce0e8c984ed0.png) {% /figure %} {% /callout %} ### Database and schema name For each data type, provide the database/schema of the table(s) you are ingesting from. {% figure %} ![Snowflake database explorer highlighting schema selection](/images/statsig-warehouse-native/connecting-your-warehouse/snowflake/187517225-017b4626-eaea-443b-a042-59fd474ae657.png) {% /figure %} ### Key-pair authentication To set up key-pair authentication, follow the [Snowflake documentation](https://docs.snowflake.com/en/user-guide/key-pair-auth) to generate the private and public keys, then set the public key on the service user. Enter the private key in the field below: {% figure %} ![Private key authentication configuration interface](/images/data-warehouse-ingestion/snowflake/8bb56be6-1aca-4d8a-98de-6b82d251d59b.png) {% /figure %} ### Custom user privileges To create a custom user with specific privileges instead of using an admin user, run the following code in your Snowflake worksheet that has sysadmin and securityadmin roles. Replace `<USER>` and `<PASSWORD>` with your values, which you will enter in the Statsig console. ```sql BEGIN; -- set up variable values to be used in statements later -- make sure to configure user_name and user_password with your own values SET user_name = '<USER>'; -- REPLACE WITH YOUR OWN VALUE SET user_password = '<PASSWORD>'; -- REPLACE WITH YOUR OWN VALUE SET role_name = 'STATSIG_ROLE'; -- CAN BE ANYTHING, BUT THE USER NEEDS TO -- HAVE THIS ROLE AND THE ROLE NEEDS ACCESS TO THE TABLES PER THE GRANTS BELOW -- change role to sysadmin for warehouse / database steps USE ROLE sysadmin; -- create a warehouse, database, schema and tables for Statsig CREATE OR REPLACE WAREHOUSE STATSIG_INGESTION WITH warehouse_size='XSMALL'; CREATE DATABASE IF NOT EXISTS STATSIG_STAGING; -- change current role to securityadmin to create role and user for Statsig's access USE ROLE securityadmin; -- create role for Statsig CREATE ROLE IF NOT EXISTS identifier($role_name); GRANT ROLE identifier($role_name) TO ROLE SYSADMIN; -- create a user for Statsig CREATE USER IF NOT EXISTS identifier($user_name) password = $user_password default_role = $role_name default_warehouse = STATSIG_INGESTION; GRANT ROLE identifier($role_name) TO USER identifier($user_name); -- grant Statsig role access to create warehouse and schema GRANT USAGE ON WAREHOUSE STATSIG_INGESTION TO ROLE identifier($role_name); GRANT CREATE SCHEMA, MONITOR, USAGE ON DATABASE STATSIG_STAGING TO ROLE identifier($role_name); -- grant Statsig role read access to database and schema passed in GRANT USAGE ON DATABASE <DATABASE> TO ROLE identifier($role_name); GRANT USAGE ON SCHEMA <DATABASE>.<SCHEMA> TO ROLE identifier($role_name); GRANT SELECT ON ALL TABLES IN DATABASE <DATABASE> TO ROLE identifier($role_name); GRANT SELECT ON FUTURE TABLES IN DATABASE <DATABASE> TO ROLE identifier($role_name); GRANT SELECT ON ALL VIEWS IN DATABASE <DATABASE> TO ROLE identifier($role_name); GRANT SELECT ON FUTURE VIEWS IN DATABASE ACTUAL_DATA TO ROLE identifier($role_name); COMMIT; ``` After running the script, enter the `<USER>` and `<PASSWORD>` you created in the Statsig console during the Connection Set Up stage, under the Advanced settings options. {% figure %} ![Advanced connection settings for warehouse ingestion showing credential inputs](/images/data-warehouse-ingestion/redshift/188943178-6b541962-62da-4529-b5af-ff59b20b7a8d.png) {% /figure %} ================================================================================ # Databricks URL: https://docs.statsig.com/data-warehouse-ingestion/databricks Updated: 2026-07-11 ================================================================================ ## How Databricks ingestion works To set up a connection with Databricks, Statsig needs the following: * API Key * Server Hostname * HTTP Path You can use any cluster in your project to connect to your data, but Statsig recommends using a Databricks [SQL warehouse/endpoint](https://docs.databricks.com/sql/admin/sql-endpoints.html) so that the cluster doesn't need to start for every pull. ### API Key To generate a new API key, go to **User Settings** in your Databricks console and generate a new token as shown below. {% figure %} ![databricks info](/images/statsig-warehouse-native/connecting-your-warehouse/databricks/188731186-ecdc0872-de06-4576-b387-fa08bdca447d.png) {% /figure %} You can also use a personal access token for a service principal. Generate one by following the steps in [Manage personal access tokens for a service principal](https://docs.databricks.com/en/administration-guide/users-groups/service-principals.html#manage-personal-access-tokens-for-a-service-principal). ### Server Hostname & HTTP Path Find your Server Hostname and HTTP Path in your Databricks console by navigating to your cluster, selecting the **Configuration** tab, and expanding **Advanced options**. {% figure %} ![credentials](/images/statsig-warehouse-native/connecting-your-warehouse/databricks/242474157-e6329ea8-92ae-43af-95dc-7bce2a26a3e6.png) {% /figure %} ================================================================================ # Synapse URL: https://docs.statsig.com/data-warehouse-ingestion/synapse Updated: 2026-07-11 ================================================================================ ## Connect Azure Synapse to Statsig To set up a connection with Azure Synapse, Statsig needs the following * Workspace SQL Endpoint * Database Name * Admin User Name * Admin User Password Statsig uses the admin user name and password to create a user with restricted access to query from your data warehouse. You can find this information in your Azure console on the Synapse workspace overview page, as shown below. {% figure %} ![Azure Synapse workspace overview page](/images/data-warehouse-ingestion/synapse/198148122-6488a470-5394-46ee-aeb1-ef2425fa743e.png) {% /figure %} ================================================================================ # S3 URL: https://docs.statsig.com/data-warehouse-ingestion/s3 Updated: 2026-07-11 ================================================================================ ## Connect S3 to Statsig To set up a connection with S3, Statsig needs the following * Region * Bucket Name * Granting Bucket Read Access Permissions to a Statsig-owned Service Account You can find the region and bucket name of your S3 bucket in your AWS console on the S3 Buckets overview page, as shown in the image below. {% figure %} ![AWS S3 console showing bucket regions and names](/images/data-warehouse-ingestion/s3/228698847-864e4607-95e4-44c0-bc0e-e15abb5b43fd.png) {% /figure %} Statsig provides an IAM user that you must grant S3 bucket permissions to. {% figure %} ![Statsig IAM user configuration interface](/images/data-warehouse-ingestion/s3/228700279-0e4f265e-32d8-4b8e-b516-496479952870.png) {% /figure %} The IAM user needs read access permissions to your bucket. Use the bucket policy below, replacing STATSIG\_IAM\_USER and YOUR\_S3\_BUCKET with your values. ``` { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::999689269917:user/STATSIG_IAM_USER" }, "Action": [ "s3:ListBucket", "s3:GetBucketLocation" ], "Resource": "arn:aws:s3:::YOUR_S3_BUCKET" }, { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::999689269917:user/STATSIG_IAM_USER" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::YOUR_S3_BUCKET/*" } ] } ``` ### S3 bucket format For each dataset you're ingesting through S3, Statsig expects a top-level folder in the S3 bucket matching the name of the dataset (for example: metrics, events), with subfolders for each day of data. Each subfolder must contain parquet files with data for that day's import. Refer to the screenshot below for an example folder structure. {% figure %} ![S3 bucket folder structure example](/images/data-warehouse-ingestion/s3/228701015-56804b30-522a-4293-b48e-4da3c02b9b6d.png) {% /figure %} ### S3 export permissions For exports, the IAM user needs bucket-level permissions to list the bucket and retrieve its location, as well as object-level permissions to read, write, and delete objects (including managing multipart uploads). Use the bucket policy below, replacing STATSIG\_IAM\_USER and YOUR\_S3\_BUCKET with your values. ``` { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::999689269917:user/STATSIG_IAM_USER" }, "Action": [ "s3:ListBucket", "s3:GetBucketLocation" ], "Resource": "arn:aws:s3:::YOUR_S3_BUCKET" }, { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::999689269917:user/STATSIG_IAM_USER" }, "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:DeleteObjectVersion", "s3:AbortMultipartUpload", "s3:ListBucketMultipartUploads", "s3:ListMultipartUploadParts" ], "Resource": "arn:aws:s3:::YOUR_S3_BUCKET/*" } ] } ``` ================================================================================ # Athena Ingestion URL: https://docs.statsig.com/data-warehouse-ingestion/athena Updated: 2026-07-11 ================================================================================ ## How Athena ingestion works To set up a connection with Athena, Statsig needs the following: * Region * Athena access permissions granted to a Statsig-owned service account Instead of granting access permissions to a Statsig-owned service account, you can provide the following: * IAM User Access Key * IAM Secret Access Key The IAM user must have permissions to query from Athena. The following is a sample policy with the required permissions: ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "athena:StartQueryExecution", "athena:GetQueryExecution", "athena:GetQueryResults", "athena:CreatePreparedStatement", "athena:DeletePreparedStatement", "athena:GetPreparedStatement", "athena:GetQueryResultsStream", "s3:GetObject", "s3:ListBucket", "s3:GetBucketLocation", "glue:GetTable", "glue:GetDatabase" ], "Resource": [ "arn:aws:athena:*:<ACCOUNT_ID>:workgroup/*", "arn:aws:glue:<REGION>:<ACCOUNT_ID>:*" ] } ] } ``` ================================================================================ # FAQ & Troubleshooting URL: https://docs.statsig.com/data-warehouse-ingestion/faq Updated: 2026-07-11 ================================================================================ ## What IP addresses does Statsig access data warehouses from? Statsig accesses data warehouses from both the Statsig console service and Statsig data pipelines. If your data warehouse is IP protected, refer to the [Statsig IP range documentation](/infrastructure/statsig_ip_ranges) for IPs to allowlist. Contact Statsig on Slack if you have any issues. ## Does event data from ingestion count towards User Accounting Metrics? No, event data from ingestions doesn't count towards Statsig's User Accounting Metrics such as DAU or Retention. Customers typically send Statsig a subset of their events, which could result in multiple competing values for fact data such as daily active users in your Statsig project. Statsig recommends sending your own precomputed metric for DAU or as a daily event per user (1 `daily_active` event if a user was active that day). ## How long does data take to load? For most customers, data ingestions take 1-2 hours to materialize in the Statsig console after you schedule the ingestion. The schedule is in PST, not PDT, so ingestions may start an hour later or earlier depending on daylight saving time. ## Does Statsig load data incrementally every day? Statsig loads data incrementally every day. Statsig also monitors data over several follow-up windows for up to two weeks, and reloads data for a given day if the data has changed more than 1%. ## Can I ingest multiple metrics in the same scheduled ingestion? Yes, you can ingest multiple metrics (and event types) in the same scheduled ingestion. Statsig enables you to run a SQL query against your data warehouse cluster to join multiple tables and generate a view with all your precomputed metrics. Use this view as the source for your scheduled data ingestion to import multiple metrics at the same time. For example, your dataset could import both `metric-1` and `metric-2`, with `metric-2` including multiple units of analysis, such as user\_id and alphabet\_id. ## How do missing metric values affect experiment calculations? If the metric value is unavailable for a given user on a given day, Statsig treats it as `zero` for additive metrics such as counts and sums. For metrics that depend on a user participating in the metric, such as conversion rate, Statsig excludes the user. Additive metrics typically have a single `metric_value` column in the ingested data, while ratio (participating) metrics typically have separate `numerator` and `denominator` columns. ## Does Statsig notify about ingestion status? Statsig shows the status of your daily ingestion on the console under the **Ingestions** tab. Statsig reports three kinds of ingestion statuses: * ingestion succeeded for a given day * ingestion succeeded for a given day, but no data was detected * ingestion failed for a given day Statsig also sends email notifications with these status updates to the Statsig user who set up the ingestion. This user can also enable Slack direct message (DM) notifications in their Statsig [Account settings](https://console.statsig.com/account_notifications). ## Does Statsig automatically backfill data? Statsig looks back 3 days from the initial ingestion to check whether data has changed (>5% increase in the number of rows) and automatically triggers a backfill if it has. Outside of this window, the customer must trigger a backfill for the range of dates. ================================================================================ # Data Mapping URL: https://docs.statsig.com/data-warehouse-ingestion/data_mapping Updated: 2026-07-11 ================================================================================ ## How data mapping works Statsig requires a specific data schema for processing. Statsig supports three types of datasets: 1. Custom Events 2. Precomputed Metrics 3. Exposure Events During setup, map the columns in your data output to the fields Statsig expects. Statsig runs a sample query to check for issues with data types, the mapping, or the base query. {% callout type="note" %} Statsig casts fields into the appropriate type. For example, Statsig accepts string IDs, but an ID field left as an integer is also accepted. {% /callout %} *** ### Custom events Custom events are sent by your application to measure the ongoing impact of your features and experiments. #### Required | Column | Description | Format/Rules | | ---------- | -------------------------------------- | --------------------------------------------------------------------------------- | | timestamp | The unix time your event was logged at | BIGINT. Cast timestamps into epoch time in seconds. | | event\_name | The name of the event | STRING/VARCHAR. Not null. Length \< 128 characters | | unit\_id | Unique unit identifier | STRING/VARCHAR. User ID, Stable ID, etc. The same event row can have multiple IDs | #### Optional | Column | Description | Rules | | -------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | event\_value | The value of the event | STRING/VARCHAR. Length \< 128 characters. Statsig detects numeric values. | | event\_metadata | Metadata columns about the event | (MANY) STRING:STRING. Statsig generates a metadata json field from however many metadata columns you provide. | | metadata\_json | Metadata json about the event | JSON STRING. Statsig unpacks this json 1 level deep. Nested values are stored as strings. | <br /> An example dataset for events might look like this: | unit\_id | visit\_id | event | timestamp | value | metadata\_blob | user\_type | | ------- | -------- | -------- | ---------- | ----- | -------------------------------------------------------- | --------------- | | 331444 | | click | 1676484875 | | `{"click_target": "exit_details_button"}` | power\_user | | 331444 | | click | 1676484860 | | `{"click_target": "open_details_button"}` | power\_user | | 265113 | | click | 1676484333 | | `{"click_target": "button", "button_color": "green"}` | churn\_risk\_user | | 445332 | aeeer43d | visit | 1676483821 | | `{"page": "landing_page"}` | new\_user | | 224448 | | checkout | 1676482222 | 33.22 | `{"product_id": "11eefj", "product_category": "clothing"}` | power\_user | Custom events have the following characteristics: * One user can send multiple of the same event, with or without any changes in metadata. Statsig aggregates these together. * You can send metadata in both of a json-formatted (only one-level deep) string, and/or pull in fields from columns. You can use metadata and values to generate custom metrics in the console, like sum(value) where "product\_category"="clothing". * You can send multiple IDs on a single event. For example, the visit above would count for both user and visit level metrics and experiments. During the mapping flow, specify which unit types your different IDs correspond to in Statsig. *** <br /> ### Precomputed metrics Use precomputed metrics to send complex metrics, or metrics that require delays due to attribution windows or long baking periods, to Statsig for experiment results. Precomputed metrics in Statsig are expected to be calculated at a user-day granularity. #### Required | Column | Description | Format/Rules | | ------------ | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | unit\_id | The unique user identifier this metric is for. This might not necessarily be a user\_id - it could be a custom\_id of some kind | STRING | | id\_type | The id\_type the unit\_id represents. | STRING. A valid ID type from your project | | date | Date of the daily metric | DATE or ISOFORMATTED STRING. The date of the metric value for the unit\_id provided | | metric\_name | The name of the metric | STRING (Not null). Length \< 128 characters | | metric\_value | A numeric value for the metric | DOUBLE/NUMERIC. Metric value **OR** both numerator and denominator need to be provided for Statsig to process the metric. | | numerator | Numerator for metric calculation | DOUBLE/NUMERIC. If present along with a denominator in any record, Statsig treats the metric as a ratio and only calculates it for users with non-null denominators. | | denominator | Denominator for metric calculation | DOUBLE/NUMERIC. If present along with a numerator in any record, Statsig treats the metric as a ratio and only calculates it for users with non-null denominators. | <br /> An example dataset for metrics might look like this: | unit\_id | unit\_type | date | metric\_name | metric\_value | numerator | denominator | | -------- | --------- | ---------- | ----------------------- | ------------ | --------- | ----------- | | 331444 | user\_id | 2023-02-13 | clicks | 2 | | | | aeeer43d | visit\_id | 2023-02-13 | visits | 1 | | | | 224448 | user\_id | 2023-02-13 | checkout\_rate | | 2 | 15 | | 224448 | user\_id | 2023-02-13 | clothing\_checkout\_value | 33.22 | | | Precomputed metrics have the following characteristics: * In this dataset, unit types are in different rows from each other * Metrics can either have a value or a numerator/denominator pair. Statsig calculates any metric with a numerator/denominator pair as a ratio metric. Ratio takes priority over value; if you provide all 3 fields, Statsig treats it as a ratio metric. * For users with null values, Statsig infers 0 for metric\_value, and excludes null value users for ratio metrics. *** <br /> ### Exposure events {% callout type="note" %} Exposure event import is deprecated. If this is an important use case, refer to [Statsig Warehouse Native](https://www.statsig.com/blog/announcing-statsig-warehouse-native), available to Enterprise customers. {% /callout %} Exposure events are generated by your assignment tool when it assigns users to a variant of an experiment (for example, show ad vs. hide ad). #### Required | Column | Description | Format/Rules | | ---------- | ------------------------------------------- | --------------------------------------------------------------------------------- | | timestamp | The unix time your event was logged at | BIGINT. Cast timestamps into timezoneless unix time. | | experiment | Your experiment identifier | STRING/VARCHAR. Not null. Length \< 128 characters | | group\_id | Unique identifier for the experiment groups | STRING/VARCHAR. Not null. | | unit\_id | Unique user identifier | STRING/VARCHAR. User ID, Stable ID, etc. The same event row can have multiple IDs | #### Optional | Column | Description | Rules | | ------------- | ----------------------------- | ------------------------------------------------------------------------------------------------ | | metadata\_json | Metadata json about the event | JSON STRING. Statsig unpacks this json 1 level deep. Nested values are stored as strings. | ================================================================================ # Google Tag Manager (GTM) URL: https://docs.statsig.com/integrations/gtm Updated: 2026-07-11 ================================================================================ ## Inbound integration: Events from GTM dataLayer to Statsig This integration lets customers using Statsig on the web use their existing Google Tag Manager configuration to track events to Statsig. Customers don't need to retag their web properties with calls specific to Statsig's SDK. The integration uses a global listener to consume all GTM triggers and send a corresponding event to Statsig. {% figure %} ![Statsig log stream showing GTM events](/images/gtm-logstream.png) {% /figure %} *(statsig log stream showing GTM events flowing in)* ## Set up the GTM integration ### Step 1: Broadcast Statsig client readiness to GTM The tracking code in GTM must know when the Statsig client is ready for tracking. Broadcast a window-level event and pass the statsig instance for the GTM tag code to use. In your initialize call, implement the `initCompletionCallback` callback as follows: #### Using @statsig/js-client ```js window.statsig = new Statsig.StatsigClient('<CLIENT-SDK-KEY>', {/* USER */}, {/* OPTIONS */}); statsig.on('values_updated', function(evt) { // bind before init is called if(evt.status && evt.status === 'Ready') { window.dispatchEvent(new CustomEvent("statsig:ready", { detail: { statsig: statsig } })); } }); await statsig.initializeAsync(); ``` #### Using statsig-js ```js await statsig.initialize('<CLIENT-SDK-KEY>', '<USER-OBJECT>', { initCompletionCallback: function (duration, success, message) { window.dispatchEvent(new CustomEvent("statsig:ready", { detail: { statsig: statsig } })); } }); ``` ### Step 2: Create new tag {% figure %} ![GTM create new tag interface](/images/gtm-create-tag.png) {% /figure %} ### Step 3: Choose tag type Choose "Custom HTML" for tag type, and paste [the GTM code below](#gtm-code) (including the script tag). {% figure %} ![GTM tag configuration screen](/images/gtm-tag-configuration.png) {% /figure %} ### Step 4: Adjust fire options Under Advanced Settings, under "Tag Firing options", select **Once per page**. {% figure %} ![GTM tag firing options settings](/images/gtm-create-tag-options.png) {% /figure %} ### Step 5: Set tag trigger Below the "Tag Configuration" section, set the Trigger to **Initialization - All Pages**. {% figure %} ![GTM tag trigger configuration](/images/gtm-tag-trigger.png) {% /figure %} ### Step 6: Save tag and test After saving and publishing the GTM tag, tracking runs automatically without additional configuration. To debug the integration, set a local storage entry `debug_ss_gtm` with any value on your webpage. Console log statements appear for each tracking call sent to Statsig. You can also inspect your browser's network traffic to see events being tracked. ### GTM code {% callout type="info" %} The code below assumes that the statsig client lives at `window.statsig` {% /callout %} ```html <script type="text/javascript"> /* dataLayer helper */ (function(){ var f=/\[object (Boolean|Number|String|Function|Array|Date|RegExp|Arguments)\]/;function g(a){return null==a?String(a):(a=f.exec(Object.prototype.toString.call(Object(a))))?a[1].toLowerCase():"object"}function m(a,b){return Object.prototype.hasOwnProperty.call(Object(a),b)}function n(a){if(!a||"object"!=g(a)||a.nodeType||a==a.window)return!1;try{if(a.constructor&&!m(a,"constructor")&&!m(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}for(var b in a);return void 0===b||m(a,b)};function p(a,b){var c={},d=c;a=a.split(".");for(var e=0;e<a.length-1;e++)d=d[a[e]]={};d[a[a.length-1]]=b;return c}function q(a,b){var c=!a._clear,d;for(d in a)if(m(a,d)){var e=a[d];"array"===g(e)&&c?("array"===g(b[d])||(b[d]=[]),q(e,b[d])):n(e)&&c?(n(b[d])||(b[d]={}),q(e,b[d])):b[d]=e}delete b._clear}; function r(a,b,c){b=void 0===b?{}:b;"function"===typeof b?b={listener:b,listenToPast:void 0===c?!1:c,processNow:!0,commandProcessors:{}}:b={listener:b.listener||function(){},listenToPast:b.listenToPast||!1,processNow:void 0===b.processNow?!0:b.processNow,commandProcessors:b.commandProcessors||{}};this.a=a;this.l=b.listener;this.j=b.listenToPast;this.g=this.i=!1;this.c={};this.f=[];this.b=b.commandProcessors;this.h=u(this);var d=this.a.push,e=this;this.a.push=function(){var k=[].slice.call(arguments, 0),l=d.apply(e.a,k);v(e,k);return l};b.processNow&&this.process()}r.prototype.process=function(){this.registerProcessor("set",function(){var c={};1===arguments.length&&"object"===g(arguments[0])?c=arguments[0]:2===arguments.length&&"string"===g(arguments[0])&&(c=p(arguments[0],arguments[1]));return c});this.i=!0;for(var a=this.a.length,b=0;b<a;b++)v(this,[this.a[b]],!this.j)};r.prototype.get=function(a){var b=this.c;a=a.split(".");for(var c=0;c<a.length;c++){if(void 0===b[a[c]])return;b=b[a[c]]}return b}; r.prototype.flatten=function(){this.a.splice(0,this.a.length);this.a[0]={};q(this.c,this.a[0])};r.prototype.registerProcessor=function(a,b){a in this.b||(this.b[a]=[]);this.b[a].push(b)}; function v(a,b,c){c=void 0===c?!1:c;if(a.i&&(a.f.push.apply(a.f,b),!a.g))for(;0<a.f.length;){b=a.f.shift();if("array"===g(b))a:{var d=a.c;g(b[0]);for(var e=b[0].split("."),k=e.pop(),l=b.slice(1),h=0;h<e.length;h++){if(void 0===d[e[h]])break a;d=d[e[h]]}try{d[k].apply(d,l)}catch(w){}}else if("arguments"===g(b)){e=a;k=[];l=b[0];if(e.b[l])for(d=e.b[l].length,h=0;h<d;h++)k.push(e.b[l][h].apply(e.h,[].slice.call(b,1)));a.f.push.apply(a.f,k)}else if("function"==typeof b)try{b.call(a.h)}catch(w){}else if(n(b))for(var t in b)q(p(t, b[t]),a.c);else continue;c||(a.g=!0,a.l(a.c,b),a.g=!1)}}r.prototype.registerProcessor=r.prototype.registerProcessor;r.prototype.flatten=r.prototype.flatten;r.prototype.get=r.prototype.get;r.prototype.process=r.prototype.process;window.DataLayerHelper=r;function u(a){return{set:function(b,c){q(p(b,c),a.c)},get:function(b){return a.get(b)}}};})(); window.StatsigLogger = (function () { var statsigInstance; var log = function () { if (typeof localStorage !== 'undefined' && typeof console !== 'undefined' && localStorage.getItem('debug_ss_gtm')) { console.log.apply(console, arguments); } }; var handleGTMMessage = function (model, message) { if (typeof message === 'object' && typeof message.event === 'string') { var metadata = {}; for (var prop in message) { if(!(message[prop] instanceof HTMLElement) && typeof(message[prop]) !== 'object') { metadata[prop] = message[prop]; } } log('++ handleGTMMessage', message.event, message.conversionValue || null, metadata); statsigInstance.logEvent(message.event, message.conversionValue || null, metadata); } else { log('++ handleGTMMessage / skip'); } } var init = function(condition) { log('+++ Statsig:ready ->', condition); // this may have past messages that haven't yet been handled var globalGTMListener = new DataLayerHelper(dataLayer, { listener: handleGTMMessage, listenToPast: true, }); } var clientReady = false; try { // The code below assumes that the statsig client lives at `window.statsig` clientReady = (statsig && statsig.getClientX && statsig.getClientX().ready) || statsig.loadingStatus === 'Ready'; } catch(err) { } if(clientReady) { // if client is already initialized, proceed statsigInstance = statsig; init('pre-GTM'); } else { // otherwise wait for statsig window.addEventListener('statsig:ready', function(evt) { statsigInstance = evt.detail.statsig; init('post-GTM'); }); } })(); </script> ``` ## Outbound integration: GTM data enriched with Statsig test assignments This pattern lets you enrich your GTM `dataLayer` with experiment assignment information. {% figure %} ![gtm datalayer outbound](/images/gtm-outbound.png) {% /figure %} Before your Statsig `initialize` call, bind an EventEmitter listener that captures the assigned experiment name and test group, then push it into the `dataLayer`. The argument passed to the callback contains context about the assignment. Modify the GTM `dataLayer` properties as needed. ```js // use event emitter to listen for experiment assignments statsigClient.on("experiment_evaluation", function(evt) { window.dataLayer.push({ 'event': 'experiment_viewed', 'experiment_name': evt.experiment.name, 'variant_name': evt.experiment.groupName }) }); await statsigClient.initializeAsync(); ``` ================================================================================ # Event Filtering URL: https://docs.statsig.com/integrations/event_filtering Updated: 2026-07-11 ================================================================================ After you enable an integration, you can select specific events to send and/or receive by selecting the **Event Filtering** button. The steps below use the Segment integration as an example, but the steps to enable **Event Filtering** are the same across integrations. ## Incoming Event Filtering If your integration includes "Incoming" events, you can filter which ones Statsig ingests. Statsig doesn't log all others. * Go to the [integrations](https://console.statsig.com/integrations) page on console.statsig.com. * Select the integration you want to filter events for. * In the dialog that appears, select **Event Filtering**. {% figure %} ![Event filtering dialog interface](/images/integrations/event_filtering/150829446-149dc7c5-0025-451a-8fae-09760b4f0566.png) {% /figure %} * Search for specific events and select or deselect the events you want Statsig to ingest. {% figure %} ![Event selection interface for incoming events](/images/integrations/event_filtering/150829346-e2f29d7e-bca3-4427-8d54-02e96f37951d.png) {% /figure %} ## Outgoing Event Filtering If your integration includes "Outgoing" events, you can select which events sent to Statsig are forwarded to the integration. * Go to the [integrations](https://console.statsig.com/integrations) page on console.statsig.com. * Select the integration you want to filter events for. * In the dialog that appears, select **Event Filtering**. {% figure %} ![Outgoing event filtering configuration](/images/integrations/event_filtering/150853774-6112c939-d101-4e15-9f74-3d872e6ba6f3.png) {% /figure %} ================================================================================ # Event Webhook URL: https://docs.statsig.com/integrations/event_webhook Updated: 2026-07-11 ================================================================================ ## Incoming The Statsig Event Webhook lets you log event data to Statsig from third-party apps or other external sources to provide additional context to your Statsig experiments and metrics. Before using the webhook, obtain your project's server secret key. An example call to the Statsig Event Webhook: ```bash title="HTTP" POST https://api.statsig.com/v1/webhooks/event_webhook ``` ```bash title="Headers" Content-Type: application/json Accept: */* STATSIG-API-KEY: {STATSIG_SERVER_SECRET} ``` ```bash title="JSON Body" { "user": { "userID": {USER_ID}, ... }, "event": {EVENT_NAME}, "value": {VALUE}, "metadata": { "example_field_1": {EXAMPLE_VALUE_1}, "example_field_2": {EXAMPLE_VALUE_2}, ... }, timestamp: {TIMESTAMP} } ``` <div style="{ paddingBottom: "32px" }" /> --- ## Outgoing <div style="{ display: "flex", justifyContent: "center", marginBottom: "16px" }"> ![Statsig Webhook integration card with Enable button](/images/integrations/event_webhook/162286552-c257a736-4050-4d0a-8223-67097c731c0b.png) </div> If you're using a service that doesn't have an official Statsig integration, you can use the Generic Webhook integration. This integration sends raw events to the provided webhook URL. ### Setup In your *Project Settings*, under the *Integrations* tab, enable the Generic Webhook integration. In the dialog that appears, enter the URL of your destination webhook and select **Enable** to save the URL and enable this integration. {% figure %} ![integration-dialog](/images/integrations/event_webhook/162327234-c9a683af-3c36-4da9-a66d-d16bf0ad09bc.png) {% /figure %} You can then configure which events are forwarded to your webhook using the [Event Filtering](#filtering-events) dialog. ### Runtime event webhooks Statsig triggers these webhooks at runtime as it assigns users to gates and experiments or as they trigger events. ### Event format Statsig sends events in batches in JSON format. The structure of a Statsig Event looks like the following: | Field | Type | Description | | --------------- | ------ | ---------------------------------------------------------------- | | eventName | String | Name of the event provided | | user | JSON | [Statsig User Object](/sdks/user) | | userID | String | User ID provided | | timestamp | Number | Timestamp in MS of the event | | value | String | Value of the event provided | | metadata | JSON | Custom Metadata provided | | statsigMetadata | JSON | Metadata related to the logging of this event added by Statsig | | timeUUID | String | UUID for the event | | unitID | String | Unit ID of the exposure (e.g. userID, stableID, or the customID) | #### Custom event formatting - logEvent > ```json { "eventName": "my_custom_event", "user": { "userID": "a_user", "email": "a.user@email.com" }, "userID": "a_user", "timestamp": "1655231253265", "statsigMetadata": { ... }, "value": "a_custom_value", "metadata": { "key_a": "value_a", "key_b": "123" }, "timeUUID": "abd2a983-ec0f-11ec-917a-fb8cdaeda578" } ``` #### Feature gate exposure formatting - checkGate > ```json { "eventName": "statsig::gate_exposure", "user": { ... }, "userID": "a_user", "timestamp": "1655231253265", "statsigMetadata": { ... }, "value": "", "metadata": { "gate": "a_gate", "gateValue": "false", "ruleID": "default", "reason": "Network", "time": "1655231249644" }, "timeUUID": "8d7c1040-ec11-11ec-g123-abe2c32fcf46", "unitID": "userID" } ``` #### Dynamic config exposure formatting - getConfig > ```json { "eventName": "statsig::config_exposure", "user": { ... }, "userID": "a_user", "timestamp": "1655231253265", "statsigMetadata": { ... }, "value": "", "metadata": { "config": "a_config", "ruleID": "default", "reason": "Network", "time": "1655231249644" }, "timeUUID": "af379f60-ec11-22ad-8e0a-05c3ee70bd0c", "unitID": "userID" } ``` #### Experiment exposure formatting - getExperiment > ```json { "eventName": "statsig::experiment_exposure", "user": { ... }, "userID": "a_user", "timestamp": "1655232119734", "statsigMetadata": { ... }, "value": "", "metadata": { "config": "an_experiment", "ruleID": "4SauZJcM1T7zNvh1igBjwE", "reason": "Network", "time": "1655231249644", "experimentGroupName": "Control" }, "timeUUID": "af379f61-ab22-11ec-8e0a-05c3ee70bd0c", "unitID": "userID" } ``` #### Example batch > ```json [ { "eventName": "page_view", "user": {"userID": "user_1", "country": "US"}, "userID": "user_1", "timestamp": 1644520566967, "value": "example_value", "metadata": {"page": "home_page"}, "statsigMetadata": {}, "timeUUID": "f4c414a0-8ab5-11ec-a8a3-0242ac120002" }, { "eventName": "statsig::gate_exposure", "user": {"userID": "user_1", "country": "US"}, "userID": "user_1", "timestamp": 1644520566968, "value": "", "metadata": {"gate": "test_gate", "gateValue": "true", "ruleID": "default"}, "statsigMetadata": {}, "timeUUID": "f4c414a0-8ab5-11ec-a8a3-0242ac120003", "unitID": "userID" }, { "eventName": "statsig::experiment_exposure" "user": {"userID": "user_1", "country": "US"}, "userID": "user_1", "timestamp": 1644520566969, "value": "", "metadata": { "config": "an_experiment", "ruleID": "4SauZJcM1T7zNvh1igBjwE", "reason": "Network", "time": "1655231249644", "experimentGroupName": "Control" }, "statsigMetadata": {}, "timeUUID": "f4c414a0-8ab5-11ec-a8a3-0242ac120004", "unitID": "userID" } ] ``` <a name="event-filtering-dialog" /> ### Config change webhooks Statsig triggers these webhooks when configuration changes take place within the Console. Each webhook request body contains a batch of change events in the following format: `{"data": [<config-change>, <config-change>]}`. Batches may contain 1 or more config change events. Below are a few examples of config change payloads. To capture the full range of config webhook types and their payloads, run a local HTTP tunnel such as ngrok to log incoming webhooks. #### Gate change ```json { "user": { "name": "Test User", "email": "testuser@email.com" }, "timestamp": 1709660061095, "eventName": "statsig::config_change", "metadata": { "type": "Gate", "name": "layout_v2", "description": "Description: Change default page layout", "action": "created" } } ``` #### Experiment change ```json { "user": { "name": "Test User", "email": "testuser@email.com" }, "timestamp": 1709658507446, "eventName": "statsig::config_change", "metadata": { "type": "Experiment", "name": "heading_test", "description": "- Updated experiment settings\n - Groups updated: control, test\n - Parameters added: heading\n - Parameters updated: directives", "action": "updated" } } ``` ### Filtering events After you enable outbound events to your webhook, you can select which categories of Statsig events to export by selecting the **Event Filtering** button and checking the appropriate boxes as shown below. There are 2 main types of events: *Exposures* (events logged through the SDK) and *Config Changes* (changelogs for Statsig Console). {% figure %} ![event-filter-button](/images/integrations/event_webhook/162329973-6f229186-1126-4824-9e29-1ff029f6553e.png) {% /figure %} {% figure %} ![event-filter-dialog](/images/integrations/event_webhook/162329983-169703fc-c838-40a2-81dc-dd3c83886d00.png) {% /figure %} ### Webhook signature You can verify that a webhook request originates from Statsig using the Webhook Signature. Follow these steps to verify the signature: 1. Grab your webhook signing secret from your Webhook integration card {% figure %} <img src="/images/integrations/event_webhook/209035738-bbbc0944-b428-4454-bf19-8845892c2044.png" alt="Webhook integration card showing signing secret" width="738" /> {% /figure %} 2. Extract the request time header 'X-Statsig-Request-Timestamp' from the webhook request. 3. Concatenate the version number ("v0"), the timestamp, and the request body together, using a colon (:) as a delimiter to create a signature basestring. Here's an example of a possible base string: ``` v0:1671672194836:{"data":[{"user":{"name":"Joe Zeng","email":"joe@statsig.com"},"timestamp":1671672134833,"eventName":"statsig::config_change","metadata":{"type":"Gate","name":"test","description":"- Updated Rule test rollout from 100.00% to 10.00%","environment":"production"}}]} ``` 4. Hash the signature basestring, using the signing secret as a key, and take the hex digest of the hash. Create the full signature by prefixing the hex digest with the version number ("v0") and an equals sign. See sample pseudo code below. ``` statsig_signature = 'v0=' + hmac.compute_hash_sha256( webhook_signing_secret, signature_basestring ).hexdigest() >>> 'v0=05c50d1513d49f884df8b0469befbbd432bd30364e81f16a606dec69f29e8f18' ``` 5. Compare the resulting signature to the 'X-Statsig-Signature' header on the request ### Developing and testing webhooks {% callout type="info" %} The actual event payload may look different from the examples above. To test webhook configuration and see payloads before starting development, you can use a local HTTP tunnel such as ngrok. {% /callout %} You can also use the **Debug** tool to: 1. View requests made to the webhook, including diagnostic information such as number of events forwarded/filtered, request header and body, and more. 2. Send example requests to the webhook using any recently logged event or exposure. {% figure %} ![Debug Tool](/images/integrations/event_webhook/5e14e128-1fd4-46f9-8543-364f9b9819bb.png) {% /figure %} ================================================================================ # OpenAI URL: https://docs.statsig.com/integrations/openai Updated: 2026-07-11 ================================================================================ ## How this integration works When using a pre-trained large language model, several inputs influence user experience: the prompts used, inference parameters like temperature, length penalties, and repetition penalties, and the model selected. Statsig can assign users to experiments that modify these inputs and can identify when changes have a statistically significant impact on user experience metrics. This enables efficient iteration by tweaking model choices, prompts, and inference parameters. This guide shows how to log both implicit indicators of user feedback (like response time) and explicit ones, like self-reported satisfaction. The example Python code demonstrates the interaction between OpenAI's GPT and Statsig to experiment with model inputs and log user events. This example uses OpenAI's ChatCompletion feature to answer questions, plus a Statsig integration to experiment with model versions and log user feedback. This example assumes you have a funded OpenAI account and a Statsig experiment that varies the model selected between "gpt-3.5-turbo" and "gpt-4". For more information on setting up a Statsig experiment, refer to the [experiments](/experiments/overview) page. ## Code breakdown ### Initial configuration Install both the Statsig and OpenAI Python packages before starting: ```bash pip3 install openai, statsig ``` Then add the following to a Python file: ```python import openai from statsig import statsig, StatsigEvent, StatsigUser import time openai.api_key = "your_openai_key" # Replace with your own key statsig.initialize("your_statsig_secret") # Replace with your Statsig secret user = StatsigUser("user-id") #This is a placeholder ID - in a normal experiment Statsig recommends using a user's actual unique ID for consistency in targeting. See /concepts/user ``` ### The ask_question function The following code all occurs in one function titled `ask_question` (refer to the [final code](#final-code)). 1. Get User Input ```python #ask the user for a question to query GPT with question = input("\nWhat is your question? ") ``` First, prompt the user for a question to ask ChatGPT. 2. Query OpenAI's GPT ```python #track the start time so we can check response time later start_time = time.time() completion = openai.ChatCompletion.create( model=statsig.get_experiment(user, "statsig_openai_integration").get("model", 'gpt-4'), messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": question} ] ) ``` Request a completion that queries the GPT model specified by the Statsig experiment (either gpt-3.5-turbo or gpt-4). The timer started earlier tracks the response time in events. 3. Display Response & Log Implicit Feedback ```python #log "implicit" indicators to Statsig c = completion.choices[0] stats = completion.usage #completion object has the number of tokens used - which is that what GPT usage is charged on. statsig.log_event(StatsigEvent(user, "chat_completion", value=c.finish_reason, metadata={"response_time": time.time() - start_time, "completion_tokens": stats["completion_tokens"], "prompt_tokens": stats["prompt_tokens"], "total_tokens": stats["total_tokens"]})) #print the message back to the user print(f"\nAnswer: {c.message['content']}") ``` The OpenAI response contains the first set of useful information to log to Statsig, like the response time and tokens used. Log this information using Statsig's SDK, stored in the metadata of the request. 4. Collect User Feedback & Log to Statsig ```python #track explicit feedback: if the user is satisfied with the answer satisfaction = input("\nDid this answer your question? (y/n): ") # Log "explicit" indicators to Statsig if satisfaction == 'y': statsig.log_event(StatsigEvent(user, "satisfaction")) elif satisfaction == 'n': statsig.log_event(StatsigEvent(user, "dissatisfaction")) ``` Log a more explicit indicator of feedback: the user's self-reported satisfaction or dissatisfaction. The satisfaction metric provides a strong indicator of the model's overall quality. Run this Python program with the following code, outside of the `ask_question` function. ```python if __name__ == "__main__": while input("Would you like to ask a question? (y/n): ").lower() == 'y': ask_question() ``` ### Tips for using Statsig with AI 1. Experimentation: Test other model parameters like temperature, top_p, or initial prompts. 2. Log useful data: Log other user interactions or feedback that may be informative. 3. Analyze and iterate: After collecting enough data, analyze the results on the Statsig dashboard. 4. User identification: Integrate a mechanism to uniquely identify each user or session. {% callout type="note" %} Always ensure you're in compliance with user privacy regulations and that you have user consent where necessary. {% /callout %} ### Related resources - [OpenAI Documentation](https://platform.openai.com/docs/api-reference/chat/create) ## Final code ```python import openai from statsig import statsig, StatsigEvent, StatsigUser import time openai.api_key = "your_openai_key" statsig.initialize("your_statsig_secret") user = StatsigUser("user-id") #This is a placeholder ID - in a normal experiment Statsig recommends using a user's actual unique ID for consistency in targeting. See /concepts/user def ask_question(): #ask the user for a question to query GPT with question = input("\nWhat is your question? ") #track the start time so we can check response time later start_time = time.time() #query GPT with the OpenAI Python library for chat completions completion = openai.ChatCompletion.create( model=statsig.get_experiment(user, "statsig_openai_collab").get("model", 'gpt-4'), #experiment is setup to return either "gpt-3.5-turbo" or "gpt-4". #other than varying the model selected, other attributes could be varied like "temperature", "top_p", "presence_penalty" and more. #See the "Create chat completions" section of the OpenAI documentation for more: https://platform.openai.com/docs/api-reference/chat/create messages=[ {"role": "system", "content": "You are a helpful assistant."}, #Initial prompts are another candidate for experimentation {"role": "user", "content": question} ] ) #log "implicit" indicators to Statsig c = completion.choices[0] #we've only requested one choice, so selecting the first stats = completion.usage #completion object has the number of tokens used - which is that what GPT usage is charged on. statsig.log_event(StatsigEvent(user, "chat_completion", value=c.finish_reason, metadata={"response_time": time.time() - start_time, "completion_tokens": stats["completion_tokens"], "prompt_tokens": stats["prompt_tokens"], "total_tokens": stats["total_tokens"]})) #print the message back to the user print(f"\nAnswer: {c.message['content']}") #track explicit feedback: if the user is satisfied with the answer satisfaction = input("\nDid this answer your question? (y/n): ") #log "explicit" indicators to Statsig if satisfaction == 'y': statsig.log_event(StatsigEvent(user, "satisfaction")) elif satisfaction == 'n': statsig.log_event(StatsigEvent(user, "dissatisfaction")) if __name__ == "__main__": #Let the user abandon the question-asking process when they are done while input("Would you like to ask a question? (y/n): ").lower() == 'y': ask_question() ``` ================================================================================ # Statsig Lite URL: https://docs.statsig.com/integrations/statsiglite Updated: 2026-07-11 ================================================================================ ## What Statsig Lite does Statsig Lite is a free experiment calculator powered by the Statsig stats engine. Statsig Lite lets you visualize experiment results for data from experiments you have already run. Upload anonymized experiment exposure and metrics or events in CSVs to preview results in the Statsig Console without connecting your production applications or data warehouses. ## FAQ ### Should I anonymize my data? Yes. Use anonymized data with Statsig Lite. ### Who can see my data? Statsig emails you a link to your results with an embedded secret. You can share this link with anyone you want to have access to the results. ### When would I do this? The most common case is teams that use tools like Optimizely for experiment assignment but can't use the same tool for experiment analysis. This often happens because the data to be analyzed sits in a warehouse or system Optimizely can't access, so teams fall back to manual analysis. Statsig automates that analysis. Statsig Lite is also a way to quickly preview what your experimental data will look like on Statsig. ### I don't have real data to upload. Can I get some sample data? Yes. The Statsig Lite website includes sample data to start with. Tools like ChatGPT can also generate a sample dataset using a prompt like [this example](https://chatgpt.com/share/67bf3105-b984-800c-99b4-02935deb5f5b). ### Is Statsig Lite missing features? Statsig Lite is intended as a quick preview. Sign up for a Statsig account to access features including automated health checks (balanced exposures, pre-experimental bias, outlier detection), advanced statistical methodologies by default (stratified sampling, differential impact detection, sequential testing), and the ability to explore results with custom queries. ================================================================================ # Statsig ID Resolver URL: https://docs.statsig.com/guides/statsig-id-resolver Updated: 2026-07-11 ================================================================================ ## How Statsig ID Resolver works Statsig ID Resolver is a project-level integration that brings your ID names into the console. IDs appear throughout the console, but without additional context it's hard to tell at a glance who or what an ID belongs to. Consider Feature Gate rules as an example: {% figure %} ![Feature gate rules with raw user IDs](/images/id_resolver1.png) {% /figure %} Each of the IDs shown represents a superhero with a name and other identifying information. After you set up ID Resolver, Statsig shows an ID’s “name” next to each ID. In this example, the name is the superhero’s name followed by their publisher. You can define “name” as whatever string is most useful for your project. {% figure %} ![Feature gate rules with resolved ID names](/images/id_resolver2.png) {% /figure %} After setting up ID Resolver Autocomplete, you can begin typing an ID’s name and have it auto-resolve to the correct ID. {% figure %} ![ID autocomplete functionality in action](/images/id_resolver3.png) {% /figure %} You can use ID Resolver wherever you enter IDs, for example in Feature Gate rules, Overrides, the Users tab, and Segment ID lists. {% figure %} ![ID resolver usage across console interfaces](/images/id_resolver_4.png) {% /figure %} ## Step 1 - Create your ID Resolver webhook Create and host your own webhook for this integration. The webhook takes an `id` and a possibly null `unit_type` and returns `name`. `unit_type` is one of userID, stableID, or a custom ID type. `name` must be under 100 characters. ```js const inputId = req.body.id as string | null; const unitType = req.body.unit_type as string | null; if (!inputId) { res.status(200).json({ success: true, data: { name: "", }, }); } const result = IDResolverDatabase.find((d) => d.id === inputId); res.status(200).json({ success: true, data: { name: result ? result.name + ", " + result.Publisher : "", }, }); ``` ## Step 2 - Create your ID Resolver Autocomplete webhook This webhook takes a `name` (the current partially typed name) and a possibly null `unit_type`, and returns the array `results` containing potential matches in the shape of `{name: string, id: string}`. Return at most 100 results, with each item under 100 characters. ```js const partialName = req.body.name as string | null; const unitType = req.body.unit_type as string | null; if (!partialName) { res.status(200).json({ success: true, data: { results: [], }, }); } const results = IDResolverDatabase.filter((d) => d.name.match(new RegExp(`^${partialName}`)) ).limit(100); res.status(200).json({ success: true, data: { results: results.map((result) => { return { name: result.name + ", (" + result.Publisher + ")", id: result.id, }; }), }, }); ``` ## Step 3 - Integrate your webhooks with Statsig Navigate to *Project Settings > Integrations > Statsig ID Resolver*. {% figure %} ![ID resolver integration setup screen](/images/id_resolver_setup_1.png) {% /figure %} {% figure %} ![ID resolver webhook configuration interface](/images/id_resolver_setup_2.png) {% /figure %} ### Secure your webhook with an API key (optional, but recommended) Statsig accepts an optional API key in the integration configuration. If you provide a string, Statsig calls your webhook with the HTTP header `Authorization: Bearer <apiKey>`. The value can be any random string. Your server should reject any request that doesn't supply the same string you provided when setting up the integration. Generate one by running `openssl rand -hex 32`. Store and read this value securely on your server. Setup is complete. Statsig now shows resolved names next to IDs throughout the console. ================================================================================ # Imports Overview (Deprecated) URL: https://docs.statsig.com/integrations/data-imports/overview Updated: 2026-07-11 ================================================================================ {% callout type="note" %} This solution is still functional, but can be manual and time consuming to set up with minimal error handling. Check out the [Data Warehouse Ingestion](/data-warehouse-ingestion/introduction) solution instead. {% /callout %} ## How imports work Statsig supports several ways to send your own events or pre-computed metrics. Statsig can read directly from your data warehouse through native integrations, or you can write your data to Azure storage owned by Statsig. ## How metric imports work You write your metrics data at a user-day-metric granularity to a table (or API endpoint) with a specific schema. You write dates as rows to a signal table (or API endpoint) after you finish adding metrics for that day. Statsig polls this signal table hourly. After you mark your first day of data as ready, or mark the next day of your data as ready, Statsig pulls data from your table and processes it into metrics and experiment results. Notes: * New imported metrics may not appear in your catalog until the following morning. * The earliest signal date Statsig considers for your first day of imported metrics is 4 weeks ago. * Statsig doesn't show metrics that haven't had a value for 2 weeks in the Metrics Catalog. Start your imports on a recent date for them to appear. * Statsig imports dates in order without gaps. If you update the signal table for `2022-06-13` and `2022-06-15`, Statsig doesn't load data for `2022-06-15` until you mark `2022-06-14` and Statsig finishes ingesting that data. ## How event imports work Event imports work similarly to metric imports, with a schematized data source you own and triggers for ingestion. Unlike metric imports, Statsig pulls event data continuously to avoid large batch loads timing out. ## Support Contact Statsig in Slack if you have issues with imports or feature requests. ================================================================================ # BigQuery (Deprecated) URL: https://docs.statsig.com/integrations/data-imports/bigquery Updated: 2026-07-11 ================================================================================ {% callout type="note" %} This solution is still functional, but can be manual and time consuming to set up with minimal error handling. Check out the [Data Warehouse Ingestion](/data-warehouse-ingestion/introduction) solution instead. {% /callout %} ## How BigQuery integration works The BigQuery integration lets you export events and metrics from your BigQuery instance to Statsig. To enable the integration: 1. Set up tables in your BigQuery instance. 2. Grant Statsig's service account the required permissions on the tables. 3. Enable the BigQuery integration in the Statsig console. 4. Insert data into tables and mark data as ready for import. ## Set up tables in your BigQuery instance 1. In your project, create a new dataset where tables for Statsig should live. You can use an existing dataset, but you'll be granting the Statsig service account permissions on that dataset later. 2. Create a table for pre-computed metrics, and another for signalling when data has landed with the statement below: ``` -- Replace statsig with your dataset name, if not using statsig CREATE TABLE IF NOT EXISTS statsig.statsig_user_metrics( unit_id STRING NOT NULL, id_type STRING NOT NULL, -- stable_id, user_id, etc. date DATE NOT NULL, -- YYYY-MM-DD. Statsig calculates dates according to PST timeuuid STRING, --Generated unique UUID; we will generate if not provided metric_name STRING NOT NULL, metric_value NUMERIC, numerator NUMERIC, denominator NUMERIC ); -- Replace statsig with your dataset name, if not using statsig CREATE TABLE IF NOT EXISTS statsig.statsig_user_metrics_signal( finished_date DATE ); ``` ## Grant permissions to Statsig's service account 1. In your Statsig console, navigate to *Project Settings > Integrations > BigQuery*. Copy the Statsig service account shown there. 2. In your BigQuery's [IAM & Admin settings](https://console.cloud.google.com/iam-admin/), add the Statsig service account as a new principal for your project and assign the "BigQuery Read Session User" role. {% figure %} ![BigQuery IAM permissions configuration](/images/integrations/data-imports/bigquery/175113594-7b121b7e-60fd-4c03-b82e-ffe397c9efa7.png) {% /figure %} 3. Navigate to your [BigQuery SQL Workspace](https://console.cloud.google.com/bigquery), choose the dataset, and select "+ Sharing" -> "Permissions" -> "Add Principal" to assign the same Statsig service account the "BigQuery Data Viewer" role on the dataset. {% figure %} ![BigQuery dataset permissions setup](/images/integrations/data-imports/bigquery/175113611-90e618ad-f6e8-4005-933e-2a5660a14466.png) {% /figure %} 4. Return to the BigQuery integration dialog in the Statsig console and enter your BigQuery project and dataset name. Select "Enable". {% figure %} ![bq_permission_step_3](/images/integrations/data-imports/bigquery/175113629-35c9be3c-591b-46f3-b9bd-e04cc9ff4b50.png) {% /figure %} The service account now has the required permissions to read data from this dataset. ## Insert data for import and signal when it is ready To load data into Statsig, load data into `statsig_user_metrics` and then mark a day as completed in `statsig_user_metrics_signal` after all data for that day is loaded. Your data must conform to these definitions and rules to avoid errors or delays: | Column | Description | Rules | | ------------ | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | unit\_id | The unique user identifier this metric is for. This might not necessarily be a user\_id - it could be a custom\_id of some kind | | | id\_type | The id\_type the unit\_id represents. | Must be a valid id\_type. The default Statsig types are user\_id/stable\_id, but you may have generated custom id\_types | | date | Date of the daily metric | | | timeuuid | A unique timeuuid for the event | This should be a timeuuid, but using a unique id will suffice. If not provided, the table defaults to generating a UUID. | | metric\_name | The name of the metric | Not null. Length \< 128 characters | | metric\_value | A numeric value for the metric | Metric value, or both of numerator/denominator need to be provided for Statsig to process the metric. See details below | | numerator | Numerator for metric calculation | See above, and details below | | denominator | Denominator for metric calculation | See above, and details below | Metric ingestion is for user-day metric pairs. This is useful for measuring experimental results on complex business logic (for example, LTV estimates) that you generate in your data warehouse. ##### Note on metric values If you provide both a `numerator` and `denominator` value for any record of a metric, Statsig treats the metric as a ratio metric, filters out users who don't have a denominator value from analysis, and recalculates the metric value using the numerator and denominator fields. If you provide only a `metric_value`, Statsig uses the `metric_value` for analysis and imputes 0 for users without a metric value in experiments. #### Scheduling Because you may be streaming events to your tables or have multiple ETLs pointing to your metrics table, Statsig relies on you to signal that your metric/events for a given day are done. After a day is fully loaded, insert that date as a row in the appropriate signal table: `statsig_user_metrics_signal` for metrics or `statsig_events_signal` for events. For example, after all metrics data is loaded into `statsig_user_metrics` for `2022-04-01`, insert `2022-04-01` into `statsig_user_metrics_signal`. Statsig expects you to load data in order. For example, if you've loaded up to `2022-04-01` and signal that `2022-04-03` has landed, Statsig waits for you to signal that `2022-04-02` has landed, then loads that data before ingesting data from `2022-04-03`. {% callout type="note" %} This ingestion pipeline doesn't currently support automatic backfills or updates to data after Statsig ingests it. Only signal these tables are loaded after you've run data quality checks. {% /callout %} #### Checklist Check these common errors before going live: * The `id_type` is set correctly. * Default types are `user_id` or `stable_id`. If you have custom ids, confirm that capitalization and spelling match, because these values are case sensitive (you can find your custom ID types in *Project Settings* in the Statsig console). * Your ids match the format of ids logged from SDKs. * In some cases, your data warehouse may transform IDs. Transformed IDs can prevent Statsig from joining your experiment or feature gate data to your metrics to calculate pulse or other reports. Go to the *Metrics* page of your project and view the log stream to check the format of the ids being sent (either `User ID`, or a custom ID in `User Properties`) to confirm they match. If your data isn't showing up in the Statsig console: * Monitoring is limited, but you can check your BigQuery query history for the Statsig user to understand which data Statsig is pulling, and whether queries aren't executing (no history) or are failing. * You should see polling queries within a few hours of setting up your integration. * If you have a signal date in the last 28 days, you should see a select statement for data from the earliest signal date in that window. * If that query fails, try running it yourself to identify whether there is a schema issue. * If data is loading, Statsig is likely still processing. For new metrics, allow a day to process. If data hasn't loaded after a day or two, contact Statsig support. The most common reason for metrics catalog failures is `id_type` mismatches. ## Verify data appears in Statsig After setup is complete, your data starts flowing into Statsig's metrics tab and experiment results, and appears in your console by around noon the next day (PST). If you chose an early first date, this may take longer because Statsig loads historical data sequentially. Contact Statsig on [Slack](https://www.statsig.com/slack) if you have any questions or need help verifying your setup. ================================================================================ # Redshift (Deprecated) URL: https://docs.statsig.com/integrations/data-imports/redshift Updated: 2026-07-11 ================================================================================ {% callout type="note" %} This solution is still functional, but can be manual and time consuming to set up with minimal error handling. Check out the [Data Warehouse Ingestion](/data-warehouse-ingestion/introduction) solution instead. {% /callout %} ## How Redshift integration works There are 2 ways to integrate with Redshift: using a data connector, or ingesting events and metrics to Statsig through S3. ## Using a data connector To **ingest** events from Redshift, you can use the [Census integration](/integrations/data-connectors/census). To **export** events to Redshift, you can use the [Fivetran integration](/integrations/data-connectors/fivetran). ## Direct ingestion S3 imports use a custom setup flow. Contact Statsig through Slack or through your support contact to set up this integration. The steps below describe how to set up this integration. There are 3 main steps: 1. Create a pipeline to write your metric, event, and (optionally) signal data to an S3 bucket in parquet format 2. Create an IAM user with read and list access on that bucket and send that user's Key/Secret to Statsig. Statsig securely stores these in a keystore service 3. Schedule ingestion through a `signals` dataset or through the `mark_data_ready` API ### Set up a data pipeline to S3 #### Filesystem format Statsig expects data in your S3 bucket in parquet format. To allow for daily uploads, set up your bucket with the following folders: * `events/` for events data * `metrics/` for metrics data * `signals/` for signal flags after you finish uploading data for a day. You can omit this folder and use the [`mark_data_ready` API](/metrics/ingest) instead, but you must use one or the other. Statsig recommends writing folders by date partitions for easier debugging, for example storing daily data in folders with ISO-formatted names (`YYYY-MM-DD`). #### Data format Confirm your data conforms to the following schemas. <b> Events </b> ``` | Column | Description | Rules | | -------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | timestamp | UNIX timestamp of the event | UTC timestamp | | event_name | The name of the event | String under 128 characters, using `_` for spaces | | event_value | A string representing the value of a current event. Can represent a 'dimension' or a 'value' | Read as string format; Statsig converts numeric values into value | | event_metadata | A dictionary<string, string> in the form of a JSON string, containing named metadata for the event | String format | | user | A JSON object representing the user this event was logged for; see below | Escaped JSON string including the keys 'custom' and 'customIDs'. A userID or customID must be provided. | | timeuuid | A unique UUID or timeUUID used for deduping. If omitted, Statsig generates one but it won't be effective for deduping | UUID format | ``` Go to [Statsig User Object](/sdks/user#user-attributes) for available fields. An example user object: ``` { userID: "12345", customIDs: { stableID: "<device_id_here>", ... } email: "12345@gmail.com", userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.40 Safari/537.36", ip: "192.168.1.101", country: "US", locale: "en_US", appVersion: "1.0.1", systemName: "Android", systemVersion: "15.4", browserName: "Chrome", browserVersion: "45.0", custom: { new_user: "false", age: "22" ... }, } ``` <b> Metrics </b> Include all of `metric_value`, `numerator`, and `denominator`. Write `cast(null as double)` for `numerator` and `denominator` if you are omitting them (or for `metric_value` if sending `numerator`/`denominator`). | Column | Description | Rules | | ------------ | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | unit\_id | The unique user identifier this metric is for. This might not necessarily be a user\_id - it could be a custom\_id of some kind | String format. Make sure this is in the same format as your logged unit\_ids | | id\_type | The id\_type the unit\_id represents. | String format. Must be a valid id\_type. The default Statsig types are user\_id/stable\_id, but you may have generated custom id\_types. Make sure this matches (case sensitive) a customID in your project, or you won't get experiment results | | date | Date of the daily metric | Read as string format; can be written as ISO date. Statsig's dates are calculated in PST - Statsig loads custom metrics to whatever date you use here | | metric\_name | The name of the metric | String format. Not null. Length \< 128 characters | | metric\_value | A numeric value for the metric | Double format. Metric value, or both of numerator/denominator need to be provided for Statsig to process the metric. See details below | | numerator | Numerator for metric calculation | Double format. Required for ratio metrics. If present along with a denominator in any record, Statsig treats the metric as a ratio and calculates it only for users with non-null denominators. | | denominator | Denominator for metric calculation | Double format. See above | ### Set up and provide credentials * Navigate to your IAM console on AWS * Go to Users -> Add User * Select the `Access key - Programmatic access` credential type * Attach a policy that grants Read and List access to the appropriate bucket. Scope this policy so the user only has access to the intended data. Example policy: ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": "<BUCKET_ARN>" } ] } ``` Next, modify your bucket access policy (under `permissions` on your S3 bucket's page) to allow this user to access objects. Example policy: ``` { "Version": "2012-10-17", "Statement": [ { "Sid": "statement1", "Effect": "Allow", "Principal": { "AWS": "<IAM_ARN>" }, "Action": "s3:ListBucket", "Resource": "<BUCKET_ARN>" }, { "Sid": "statement2", "Effect": "Allow", "Principal": { "AWS": "<IAM_ARN>" }, "Action": "s3:GetObject", "Resource": "<BUCKET_ARN>/*" } ] } ``` To confirm your credentials are sufficient, add any data to your metrics folder and run the following code in PySpark with the IAM user credentials: ``` sc._jsc.hadoopConfiguration().set("fs.s3n.awsAccessKeyId", '<IAM_USER_ACCESS_KEY>') sc._jsc.hadoopConfiguration().set("fs.s3n.awsSecretAccessKey", '<IAM_USER_SECRET>') spark.read.parquet("s3://<BUCKET_NAME>/metrics/*",inferSchema=True).show() ``` ### Scheduling Because you may be streaming events to your tables or have multiple ETLs pointing to your metrics table, Statsig relies on you signaling that your metric/events for a given day are done. To signal completion, write a dataset with the single column `finished_date`, which contains all dates of data written to Statsig. For example, after writing data for `2022-06-22`, insert a record with `finished_date` of `2022-06-22` to trigger ingestion of data up to and including `2022-06-22`. Unlike Snowflake, Statsig skips dates for S3. If your latest finished date is `2022-06-22` and you insert `2022-07-01`, Statsig ingests all data as of `2022-07-01` and infers that intermediate dates (for example, `2022-06-25`) have data loaded. Alternatively, you can use the `mark_data_ready` API and send a timestamp indicating that all data before that timestamp has finished loading into S3. Statsig processes events in PST. When you mark data ready for `2022-06-20`, Statsig processes events from `2022-06-20T00:00` PST to `2022-06-20T23:59` PST. Account for this when scheduling your signals. ================================================================================ # Snowflake (Deprecated) URL: https://docs.statsig.com/integrations/data-imports/snowflake Updated: 2026-07-11 ================================================================================ {% callout type="note" %} This solution is still functional, but can be manual and time consuming to set up with minimal error handling. Check out the [Data Warehouse Ingestion](/data-warehouse-ingestion/introduction) solution instead. {% /callout %} ## How Snowflake integration works There are 2 ways to integrate with Snowflake: using a data connector, or through direct ingestion from Snowflake. ## Using a data connector To **ingest** events from Snowflake, you can use the [Census integration](/integrations/data-connectors/census). To **export** events to Snowflake, you can use the [Fivetran integration](/integrations/data-connectors/fivetran). ## Direct ingestion from Snowflake Statsig also supports direct data ingestion from Snowflake. After setup, Statsig automatically pulls data from Snowflake into your events. Complete the following steps. Follow the checklist below to avoid delays. ### 1. Set up your Snowflake data warehouse and user for Statsig integration Insert `USER` and `PASSWORD` values in the SQL below and run it in a Snowflake worksheet on an account with sysadmin and securityadmin roles. This creates the table schemas and configuration that Statsig's ingestion uses. Statsig uses the user you create to access tables in the new Statsig schema. Use a unique and secure username and password, and replace the placeholder values in the first 2 statements. ```sql BEGIN; -- set up variable values to be used in statements later -- make sure to configure user_name and user_password with your own values SET user_name = '<USER>'; -- REPLACE WITH YOUR OWN VALUE SET user_password = '<PASSWORD>'; -- REPLACE WITH YOUR OWN VALUE SET role_name = 'STATSIG_ROLE'; -- change role to sysadmin for warehouse / database steps USE ROLE sysadmin; -- create a warehouse, database, schema and tables for Statsig CREATE OR REPLACE WAREHOUSE statsig WITH warehouse_size='XSMALL'; CREATE DATABASE IF NOT EXISTS statsig; CREATE SCHEMA IF NOT EXISTS statsig.statsig; -- a table for ingestion of raw events CREATE TABLE IF NOT EXISTS statsig.statsig.statsig_events( time BIGINT NOT NULL, -- unix time timeuuid STRING NOT NULL DEFAULT UUID_STRING(), --generated unique timeuuid user STRING NOT NULL, --json user object event_name STRING NOT NULL, event_value STRING, event_metadata STRING NOT NULL, --json metadata object event_version BIGINT, record_number NUMBER AUTOINCREMENT START 1 INCREMENT 1 ); -- a table for ingestion of metrics/user outcomes CREATE TABLE IF NOT EXISTS statsig.statsig.statsig_user_metrics( unit_id STRING NOT NULL, id_type STRING NOT NULL, -- stable_id, user_id, etc. date DATE NOT NULL, -- YYYY-MM-DD. Statsig calculates dates according to PST timeuuid STRING NOT NULL DEFAULT UUID_STRING(), metric_name STRING NOT NULL, metric_value NUMBER, numerator NUMBER, denominator NUMBER ); CREATE TABLE IF NOT EXISTS statsig.statsig.statsig_events_signal( finished_date DATE ); CREATE TABLE IF NOT EXISTS statsig.statsig.statsig_user_metrics_signal( finished_date DATE ); -- change current role to securityadmin to create role and user for Statsig's access USE ROLE securityadmin; -- create role for Statsig CREATE ROLE IF NOT EXISTS identifier($role_name); GRANT ROLE identifier($role_name) TO ROLE SYSADMIN; -- create a user for Statsig CREATE USER IF NOT EXISTS identifier($user_name) password = $user_password default_role = $role_name default_warehouse = statsig; GRANT ROLE identifier($role_name) TO USER identifier($user_name); -- grant Statsig role access GRANT USAGE ON WAREHOUSE statsig TO ROLE identifier($role_name); GRANT USAGE ON DATABASE statsig TO ROLE identifier($role_name); GRANT USAGE ON SCHEMA statsig.statsig TO ROLE identifier($role_name); GRANT SELECT ON statsig.statsig.statsig_events TO ROLE identifier($role_name); GRANT SELECT ON statsig.statsig.statsig_user_metrics TO ROLE identifier($role_name); GRANT SELECT ON statsig.statsig.statsig_events_signal TO ROLE identifier($role_name); GRANT SELECT ON statsig.statsig.statsig_user_metrics_signal TO ROLE identifier($role_name); COMMIT; ``` Confirm all statements ran successfully. This creates the schema and user that Statsig's ingestion expects. ### 2. Provide the credentials to Statsig * Go to [console.statsig.com](https://console.statsig.com/) and log in. * Go to the settings page and navigate to the *Integrations* tab. * Find Snowflake in the integrations list and provide the requested credentials for the user you just created in step 1. * You can use the **Test Connection** button to confirm a connection to the table using the credentials provided. {% figure %} ![Snowflake integration configuration interface](/images/integrations/data-imports/snowflake/162287996-5f2e02f8-c461-4d69-8a36-fa1d79d913a3.png) {% /figure %} ### 3. Load data into the new Statsig tables Step 1 created 2 data tables and 2 signal tables. To load data into Statsig, load data into the data tables, and mark a day as completed in the corresponding signal table after all data for that day is loaded. The `statsig_events` table is for sending raw events that were not logged to Statsig through the API. After loading, these events are processed as though they were logged directly. The `statsig_user_metrics` table is for sending pre-computed metrics from your data warehouse. These metrics appear in the Statsig console and in your test results. You may only need one of these tables. Follow the steps for the relevant table and ignore the other. Your data must conform to these definitions and rules to avoid errors or delays: #### Events (statsig\_events) | Column | Description | Rules | | -------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | time | The unix time your event was logged at | Not null | | timeuuid | A unique timeuuid for the event | This should be a timeuuid, but using a unique id will suffice. If not provided, the table defaults to generating a UUID. | | user | A user json object. | See below | | event\_name | The name of the event | Not null. Length \< 128 characters | | event\_value | The value of the event | Length \< 128 characters | | event\_metadata | Metadata about the event | Not null. Length \< 16384 characters. Json-formatted - leave empty if none | | event\_version | The version of this event | | The user object is a stringified JSON representation. An example might look like: ```json { "os": "Mac OS X", "os_version": "10.15.7", "browser_name": "Electron", "browser_version": "11.5.0", "ip": "1.1.1.1", "country": "KR", "locale": "en-US", "userID": "bbbbb-bbbbb-bbbbb-bbbbb", "custom": { "locale": "en-US", "clientVersion": "23.10.23.10", "desktopVersion": "11.5.0" }, "customIDs": { "deviceId": "ddddd-ddddd-ddddd-ddddd", "stableID": "sssss-sssss-sssss-sssss" } } ``` Key components of the user object are the `userID`, `custom` fields, and the `customIDs` object (notably `stableID`) if you use any custom identifiers. Provide these fields where they exist and ensure field names are capitalized correctly. Not providing a unit identifier limits the utility of your events, because Statsig can't use them to build metrics like daily event users. #### Metrics (statsig\_user\_metrics) | Column | Description | Rules | | ------------ | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | unit\_id | The unique user identifier this metric is for. This might not necessarily be a user\_id - it could be a custom\_id of some kind | | | id\_type | The id\_type the unit\_id represents. | Must be a valid id\_type. The default Statsig types are user\_id/stable\_id, but you may have generated custom id\_types | | date | Date of the daily metric | | | timeuuid | A unique timeuuid for the event | This should be a timeuuid, but using a unique id will suffice. If not provided, the table defaults to generating a UUID. | | metric\_name | The name of the metric | Not null. Length \< 128 characters | | metric\_value | A numeric value for the metric | Metric value, or both of numerator/denominator need to be provided for Statsig to process the metric. See details below | | numerator | Numerator for metric calculation | See above, and details below | | denominator | Denominator for metric calculation | See above, and details below | Metric ingestion is for user-day metric pairs. This is useful for measuring experimental results on complex business logic (for example, LTV estimates) that you generate in your data warehouse. ##### Note on metric values If you provide both a `numerator` and `denominator` value for any record of a metric, Statsig treats the metric as a ratio metric, filters out users who don't have a denominator value from analysis, and recalculates the metric value using the numerator and denominator fields. If you provide only a `metric_value`, Statsig uses the `metric_value` for analysis and imputes 0 for users without a metric value in experiments. #### Scheduling Because you may be streaming events to your tables or have multiple ETLs pointing to your metrics table, Statsig relies on you to signal that your metric/events for a given day are done. After a day is fully loaded, insert that date as a row in the appropriate signal table: `statsig_user_metrics_signal` for metrics or `statsig_events_signal` for events. For example, after all metrics data is loaded into `statsig_user_metrics` for `2022-04-01`, insert `2022-04-01` into `statsig_user_metrics_signal`. Statsig expects you to load data in order. For example, if you've loaded up to `2022-04-01` and signal that `2022-04-03` has landed, Statsig waits for you to signal that `2022-04-02` has landed, then loads that data before ingesting data from `2022-04-03`. {% callout type="note" %} This ingestion pipeline doesn't currently support automatic backfills or updates to data after Statsig ingests it. Only signal these tables are loaded after you've run data quality checks. {% /callout %} <a name="checklist" /> #### Checklist Check these common errors before going live: * Field names are set incorrectly * Some field names may conflict with reserved keywords. Wrap them in quotes if needed. The SQL above works in the Snowflake console. * Run `SELECT *` on your tables to confirm there are no special characters in column names. Special characters cause ingestion to fail. * The `id_type` is set correctly. * Default types are `user_id` or `stable_id`. If you have custom ids, confirm that capitalization and spelling match, because these values are case sensitive (you can find your custom ID types in *Project Settings* in the Statsig console). * Your ids match the format of ids logged from SDKs. * In some cases, your data warehouse may transform IDs. Transformed IDs can prevent Statsig from joining your experiment or feature gate data to your metrics to calculate pulse or other reports. Go to the *Metrics* page of your project and view the log stream to check the format of the ids being sent (either `User ID`, or a custom ID in `User Properties`) to confirm they match. If your data isn't showing up in the Statsig console: * Monitoring is limited, but you can check your Snowflake query history for the Statsig user to understand which data Statsig is pulling, and whether queries aren't executing (no history) or are failing. * You should see polling queries within a few hours of setting up your integration. * If you have a signal date in the last 28 days, you should see a select statement for data from the earliest signal date in that window. * If that query fails, try running it yourself to identify whether there is a schema issue. * If data is loading, Statsig is likely still processing. For new metrics, allow a day to process. If data hasn't loaded after a day or two, contact Statsig support. The most common reason for metrics catalog failures is `id_type` mismatches. ================================================================================ # Azure Metrics Upload (Deprecated) URL: https://docs.statsig.com/integrations/data-imports/azure_upload Updated: 2026-07-11 ================================================================================ {% callout type="note" %} This solution is still functional, but can be manual and time consuming to set up with minimal error handling. Check out the [Data Warehouse Ingestion](/data-warehouse-ingestion/introduction) solution instead. {% /callout %} ## How Azure metrics upload works Statsig lets you upload pre-computed metrics data to a secure Azure blob that Statsig owns. Statsig ingests all uploaded metrics for a day after you signal that a given day is finished uploading. ## Getting started Reach out in Slack or to your primary Statsig point of contact. Statsig will set up an Azure blob storage container and provide credentials to connect. ## Filesystem format To allow for daily uploads, set up your blob storage container with the following folders: * `events/` for events data * `metrics/` for metrics data * `signals/` for signal flags when you've finished uploading data for a day. You can omit this folder and instead use the [`mark_data_ready` API](/metrics/ingest) instead, but you must use one or the other Statsig recommends writing folders by date partitions for easier debugging, for example storing daily data in folders with ISO-formatted names (`YYYY-MM-DD`). ### Data format Confirm your data conforms to the following schemas. <b> Events </b> ``` | Column | Description | Rules | | -------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | timestamp | UNIX timestamp of the event | UTC timestamp | | event_name | The name of the event | String under 128 characters, using `_` for spaces | | event_value | A string representing the value of a current event. Can represent a 'dimension' or a 'value' | Read as string format; Statsig converts numeric values into value | | event_metadata | A dictionary<string, string> in the form of a JSON string, containing named metadata for the event | String format. Not null. Length < 128 characters | | user | A JSON object representing the user this event was logged for; see below | Escaped JSON string including the keys 'custom' and 'customIDs'. A userID or customID must be provided. | | timeuuid | A unique UUID or timeUUID used for deduping. If omitted, Statsig generates one but it won't be effective for deduping | UUID format | ``` Go to [Statsig User Object](/sdks/user#user-attributes) for available fields. An example user object: ``` { userID: "12345", customIDs: { stableID: "<device_id_here>", ... } email: "12345@gmail.com", userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.40 Safari/537.36", ip: "192.168.1.101", country: "US", locale: "en_US", appVersion: "1.0.1", systemName: "Android", systemVersion: "15.4", browserName: "Chrome", browserVersion: "45.0", custom: { new_user: "false", age: "22" ... }, } ``` <b> Metrics </b> Include all of `metric_value`, `numerator`, and `denominator`. Write `cast(null as double)` for `numerator` and `denominator` if you are omitting them (or for `metric_value` if sending `numerator`/`denominator`). | Column | Description | Rules | | ------------ | ----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | unit\_id | The unique user identifier this metric is for. This might not necessarily be a user\_id - it could be a custom\_id of some kind | String format. Make sure this is in the same format as your logged unit\_ids | | id\_type | The id\_type the unit\_id represents. | String format. Must be a valid id\_type. The default Statsig types are user\_id/stable\_id, but you may have generated custom id\_types. Make sure this matches (case sensitive) a customID in your project, or you won't get experiment results | | date | Date of the daily metric | Read as string format; can be written as ISO date. Statsig's dates are calculated in PST - Statsig loads custom metrics to whatever date you use here | | metric\_name | The name of the metric | String format. Not null. Length \< 128 characters | | metric\_value | A numeric value for the metric | Double format. Metric value, or both of numerator/denominator need to be provided for Statsig to process the metric. See details below | | numerator | Numerator for metric calculation | Double format. Required for ratio metrics. If present along with a denominator in any record, Statsig treats the metric as a ratio and calculates it only for users with non-null denominators. | | denominator | Denominator for metric calculation | Double format. See above | ### Scheduling Because you may be streaming events to your tables or have multiple ETLs pointing to your metrics table, Statsig relies on you signaling that your metric/events for a given day are done. To signal completion, write a dataset with the single column `finished_date`, which contains all dates of data written to Statsig. For example, after writing data for `2022-06-22`, insert a record with `finished_date` of `2022-06-22` to trigger ingestion of data up to and including `2022-06-22`. Unlike Snowflake, Statsig skips dates for S3. If your latest finished date is `2022-06-22` and you insert `2022-07-01`, Statsig ingests all data as of `2022-07-01` and infers that intermediate dates (for example, `2022-06-25`) have data loaded. Alternatively, you can use the `mark_data_ready` API and send a timestamp indicating that all data before that timestamp has finished loading into your container. Statsig processes events in PST. When you mark data ready for `2022-06-20`, Statsig processes events from `2022-06-20T00:00` PST to `2022-06-20T23:59` PST. Account for this when scheduling your signals. <a name="checklist" /> #### Checklist Check these common errors before going live: * Field names are set incorrectly. * The `id_type` is set correctly. * Default types are `user_id` or `stable_id`. If you have custom ids, confirm that capitalization and spelling match, because these values are case sensitive (you can find your custom ID types in Project Settings in the Statsig console). * Your IDs match the format of IDs logged from SDKs. * In some cases, your data warehouse may transform IDs. Transformed IDs can prevent Statsig from joining your experiment or feature gate data to your metrics to calculate pulse or other reports. Go to the Metrics page of your project and view the log stream to check the format of the IDs being sent (either `User ID`, or a custom ID in `User Properties`) to confirm they match. ================================================================================ # Experiment Result Exports URL: https://docs.statsig.com/integrations/data-exports/experiment_result_exports Updated: 2026-07-11 ================================================================================ ## How experiment result exports work Statsig lets you export both the reports and the raw data your feature rollouts and experiments generate. ## Export methods 1. [Download experiment results](/experiments/interpreting-results/export#how-to-export-pulse-data) from the Console as a CSV file - including a summary view, exposures and the raw data. This is for one-off downloads and analysis. {% figure %} ![Pulse results export interface](/images/exports-pulse.png) {% /figure %} 2. [Programmatically export the data underlying Pulse](/console-api/introduction). 3. For ongoing data exports, Statsig supports [data integrations](/integrations/introduction) through customer data platforms like Segment, RudderStack, and mParticle. There is also a [generic webhook](/integrations/event_webhook) if you want to build your own integration. To set up a daily export into your data warehouse, go to [Data warehouse exports](/integrations/data-exports/data_warehouse_exports). {% figure %} ![Grid of event export integrations such as Amplitude, Braze, and Webhook](/images/exports-events.png) {% /figure %} ## Validate exported data Many teams audit and compare their data in Statsig with what they have in other systems. Statsig uses well-recognized statistical methods and industry best practices, so you can reproduce results yourself. Tips for validation: 1. Start small: Use a single day of data to reduce variables. When comparing experiments, use a full day (not days the experiment started or stopped, where there is partial data). 2. Third-party ad/tracking blockers can block events sent to third-party services. A server-side integration or [proxying requests through your domain](/infrastructure/api_proxy/custom_proxy) removes this issue. 3. Check for time zone conversion issues to confirm a consistent definition of "day" is being used. 4. Statsig applies [winsorization](/experiments/statistical-methods/variance-reduction#winsorization) on metrics to remove outliers. 5. Statsig applies CUPED to reduce variance on metrics using pre-experimental data. Disable CUPED when comparing console results to external data. ================================================================================ # Data Warehouse Exports URL: https://docs.statsig.com/integrations/data-exports/data_warehouse_exports Updated: 2026-07-11 ================================================================================ ## How data warehouse exports work You can export your data from Statsig to your data warehouse using a data connection. This lets you send exposures and events directly to your warehouse for further analysis. Statsig supports connections to Snowflake, Redshift, S3, BigQuery, and Databricks. {% callout type="info" %} Data Warehouse Exports are an Enterprise-only feature. If you're on the Developer or Pro tiers and want to upgrade to Enterprise, reach out to the Statsig team [here](https://www.statsig.com/contact/demo) or through support. {% /callout %} ## Set up a data warehouse export 1. Go to Statsig Console 2. Navigate to the Help and Tools Section on the side navigation bar 3. Go to "Exports List" {% figure %} ![Statsig Help and Tools menu showing Exports List option](/images/exports-list.png) {% /figure %} On the Exports page, you'll find an *Export History* tab and a *Schedule Export* tab. The *Export History* tab lists all previous experimentation exports triggered from the *Experiment Results* page. To export one-off experimentation results, refer to [Experiment Result Exports](/integrations/data-exports/experiment_result_exports). Under the *Scheduled Export* tab, set up a data warehouse connection. Configure the connection with the necessary credentials and grant Statsig Read, Write, and Delete permissions. The warehouse-specific tabs below show the fields Statsig requires and example SQL you can use to create each export table yourself. {% figure %} ![Scheduled export setup form for Snowflake connection with credential fields](/images/exports-schedule.png) {% /figure %} ## Export table schema Statsig can export both Exposures and Events to your destination. The exported columns are logically the same across SQL warehouses, but the physical types vary by warehouse. S3 is a file destination rather than a SQL table destination. {% tabs %} {% tab title="BigQuery" %} **Setup summary** Configure: * BigQuery Project ID * BigQuery Dataset ID * one destination table name per export type Permissions: * Grant the Statsig service account `BigQuery User` at the project level. * Grant the same service account `BigQuery Data Editor` on the target dataset. Statsig uses this access to create the table if needed and validate the connection by inserting and deleting a test row. If you prefer to create the export tables yourself, you can use SQL like this: ```sql BigQuery Exposures CREATE TABLE IF NOT EXISTS `PROJECT_ID.DATASET_ID.EXPOSURES_TABLE` ( company_id STRING, unit_id STRING, unit_type STRING, exposure_type STRING, name STRING, rule STRING, experiment_group STRING, first_exposure_utc TIMESTAMP, first_exposure_pst_date DATE, as_of_pst_date DATE, percent FLOAT64, rollout BIGINT, user_dimensions STRING, inserted_at TIMESTAMP, rule_name STRING, group_id STRING, non_analytics BOOLEAN ); ``` ```sql BigQuery Events CREATE TABLE IF NOT EXISTS `PROJECT_ID.DATASET_ID.EVENTS_TABLE` ( user_id STRING, stable_id STRING, custom_ids JSON, timestamp TIMESTAMP, event_name STRING, event_value STRING, user_object JSON, statsig_metadata JSON, company_metadata JSON ); ``` **Exposures** Statsig doesn't export exposures from rules that are 0%/100%. | Field name | Type | Mode | | ---------------------- | --------- | -------- | | company\_id | STRING | NULLABLE | | unit\_id | STRING | NULLABLE | | unit\_type | STRING | NULLABLE | | exposure\_type | STRING | NULLABLE | | name | STRING | NULLABLE | | rule | STRING | NULLABLE | | experiment\_group | STRING | NULLABLE | | first\_exposure\_utc | TIMESTAMP | NULLABLE | | first\_exposure\_pst\_date | DATE | NULLABLE | | as\_of\_pst\_date | DATE | NULLABLE | | percent | FLOAT64 | NULLABLE | | rollout | BIGINT | NULLABLE | | user\_dimensions | STRING | NULLABLE | | inserted\_at | TIMESTAMP | NULLABLE | | rule\_name | STRING | NULLABLE | | group\_id | STRING | NULLABLE | | non\_analytics | BOOLEAN | NULLABLE | **Events** | Field name | Type | Mode | | ----------------- | --------- | -------- | | user\_id | STRING | NULLABLE | | stable\_id | STRING | NULLABLE | | custom\_ids | JSON | NULLABLE | | timestamp | TIMESTAMP | NULLABLE | | event\_name | STRING | NULLABLE | | event\_value | STRING | NULLABLE | | user\_object | JSON | NULLABLE | | statsig\_metadata | JSON | NULLABLE | | company\_metadata | JSON | NULLABLE | {% /tab %} {% tab title="Snowflake" %} **Setup summary** Configure: * Account Name * Database Name * Schema Name * either Username and Password, or Private Key authentication * one destination table name per export type Permissions: * The configured user must be able to create tables and insert/delete rows in the target database and schema. If you prefer to create the export tables yourself, you can use SQL like this: ```sql Snowflake Exposures CREATE TABLE IF NOT EXISTS DATABASE_NAME.SCHEMA_NAME.EXPOSURES_TABLE ( company_id STRING, unit_id STRING, unit_type STRING, exposure_type STRING, name STRING, rule STRING, experiment_group STRING, first_exposure_utc TIMESTAMP, first_exposure_pst_date DATE, as_of_pst_date DATE, percent DOUBLE, rollout BIGINT, user_dimensions STRING, inserted_at TIMESTAMP, rule_name STRING, group_id STRING, non_analytics BOOLEAN ); ``` ```sql Snowflake Events CREATE TABLE IF NOT EXISTS DATABASE_NAME.SCHEMA_NAME.EVENTS_TABLE ( user_id STRING, stable_id STRING, custom_ids STRING, timestamp TIMESTAMP, event_name STRING, event_value STRING, user_object STRING, statsig_metadata STRING, company_metadata STRING ); ``` **Exposures** | Field name | Type | Mode | | ---------------------- | --------- | -------- | | company\_id | STRING | NULLABLE | | unit\_id | STRING | NULLABLE | | unit\_type | STRING | NULLABLE | | exposure\_type | STRING | NULLABLE | | name | STRING | NULLABLE | | rule | STRING | NULLABLE | | experiment\_group | STRING | NULLABLE | | first\_exposure\_utc | TIMESTAMP | NULLABLE | | first\_exposure\_pst\_date | DATE | NULLABLE | | as\_of\_pst\_date | DATE | NULLABLE | | percent | DOUBLE | NULLABLE | | rollout | BIGINT | NULLABLE | | user\_dimensions | STRING | NULLABLE | | inserted\_at | TIMESTAMP | NULLABLE | | rule\_name | STRING | NULLABLE | | group\_id | STRING | NULLABLE | | non\_analytics | BOOLEAN | NULLABLE | **Events** | Field name | Type | Mode | | ----------------- | --------- | -------- | | user\_id | STRING | NULLABLE | | stable\_id | STRING | NULLABLE | | custom\_ids | STRING | NULLABLE | | timestamp | TIMESTAMP | NULLABLE | | event\_name | STRING | NULLABLE | | event\_value | STRING | NULLABLE | | user\_object | STRING | NULLABLE | | statsig\_metadata | STRING | NULLABLE | | company\_metadata | STRING | NULLABLE | {% /tab %} {% tab title="Redshift" %} **Setup summary** Configure: * Cluster Endpoint * Username and Password * Staging Schema * one destination table name per export type Permissions: * The configured user must be able to create tables and insert/delete rows in the target database and schema. * If your cluster is not directly reachable, you can also configure SSH tunneling. If you prefer to create the export tables yourself, you can use SQL like this: ```sql Redshift Exposures CREATE TABLE IF NOT EXISTS "DATABASE_NAME"."STAGING_SCHEMA"."EXPOSURES_TABLE" ( "company_id" VARCHAR, "unit_id" VARCHAR, "unit_type" VARCHAR, "exposure_type" VARCHAR, "name" VARCHAR, "rule" VARCHAR, "experiment_group" VARCHAR, "first_exposure_utc" TIMESTAMP, "first_exposure_pst_date" DATE, "as_of_pst_date" DATE, "percent" DOUBLE PRECISION, "rollout" BIGINT, "user_dimensions" VARCHAR, "inserted_at" TIMESTAMP, "rule_name" VARCHAR, "group_id" VARCHAR, "non_analytics" BOOLEAN ); ``` ```sql Redshift Events CREATE TABLE IF NOT EXISTS "DATABASE_NAME"."STAGING_SCHEMA"."EVENTS_TABLE" ( "user_id" VARCHAR, "stable_id" VARCHAR, "custom_ids" VARCHAR, "timestamp" TIMESTAMP, "event_name" VARCHAR, "event_value" VARCHAR, "user_object" VARCHAR, "statsig_metadata" VARCHAR, "company_metadata" VARCHAR ); ``` {% callout type="note" %} The default size for VARCHAR is 256 and Statsig truncates all columns to 256 characters for Redshift. If you anticipate certain columns will exceed these limits, adjust accordingly and contact Statsig to remove these limits. Common longer columns are `user_dimensions` in exposures and `user_object`, `custom_ids`, and `company_metadata` in events. {% /callout %} **Exposures** | Field name | Type | Mode | | ---------------------- | ----------------- | -------- | | company\_id | VARCHAR | NULLABLE | | unit\_id | VARCHAR | NULLABLE | | unit\_type | VARCHAR | NULLABLE | | exposure\_type | VARCHAR | NULLABLE | | name | VARCHAR | NULLABLE | | rule | VARCHAR | NULLABLE | | experiment\_group | VARCHAR | NULLABLE | | first\_exposure\_utc | TIMESTAMP | NULLABLE | | first\_exposure\_pst\_date | DATE | NULLABLE | | as\_of\_pst\_date | DATE | NULLABLE | | percent | DOUBLE PRECISION | NULLABLE | | rollout | BIGINT | NULLABLE | | user\_dimensions | VARCHAR | NULLABLE | | inserted\_at | TIMESTAMP | NULLABLE | | rule\_name | VARCHAR | NULLABLE | | group\_id | VARCHAR | NULLABLE | | non\_analytics | BOOLEAN | NULLABLE | **Events** | Field name | Type | Mode | | ----------------- | ----------------- | -------- | | user\_id | VARCHAR | NULLABLE | | stable\_id | VARCHAR | NULLABLE | | custom\_ids | VARCHAR | NULLABLE | | timestamp | TIMESTAMP | NULLABLE | | event\_name | VARCHAR | NULLABLE | | event\_value | VARCHAR | NULLABLE | | user\_object | VARCHAR | NULLABLE | | statsig\_metadata | VARCHAR | NULLABLE | | company\_metadata | VARCHAR | NULLABLE | {% /tab %} {% tab title="Databricks" %} **Setup summary** Configure: * API Key * Server Hostname * HTTP Path * Database * one destination table name per export type Permissions: * The configured user or token must be able to create tables and insert/delete rows in the target database. If you prefer to create the export tables yourself, you can use SQL like this: ```sql Databricks Exposures CREATE TABLE IF NOT EXISTS DATABASE_NAME.EXPOSURES_TABLE ( company_id STRING, unit_id STRING, unit_type STRING, exposure_type STRING, name STRING, rule STRING, experiment_group STRING, first_exposure_utc TIMESTAMP, first_exposure_pst_date DATE, as_of_pst_date DATE, percent DOUBLE, rollout BIGINT, user_dimensions STRING, inserted_at TIMESTAMP, rule_name STRING, group_id STRING, non_analytics BOOLEAN ); ``` ```sql Databricks Events CREATE TABLE IF NOT EXISTS DATABASE_NAME.EVENTS_TABLE ( user_id STRING, stable_id STRING, custom_ids STRING, timestamp TIMESTAMP, event_name STRING, event_value STRING, user_object STRING, statsig_metadata STRING, company_metadata STRING ); ``` **Exposures** | Field name | Type | Mode | | ---------------------- | --------- | -------- | | company\_id | STRING | NULLABLE | | unit\_id | STRING | NULLABLE | | unit\_type | STRING | NULLABLE | | exposure\_type | STRING | NULLABLE | | name | STRING | NULLABLE | | rule | STRING | NULLABLE | | experiment\_group | STRING | NULLABLE | | first\_exposure\_utc | TIMESTAMP | NULLABLE | | first\_exposure\_pst\_date | DATE | NULLABLE | | as\_of\_pst\_date | DATE | NULLABLE | | percent | DOUBLE | NULLABLE | | rollout | BIGINT | NULLABLE | | user\_dimensions | STRING | NULLABLE | | inserted\_at | TIMESTAMP | NULLABLE | | rule\_name | STRING | NULLABLE | | group\_id | STRING | NULLABLE | | non\_analytics | BOOLEAN | NULLABLE | **Events** | Field name | Type | Mode | | ----------------- | --------- | -------- | | user\_id | STRING | NULLABLE | | stable\_id | STRING | NULLABLE | | custom\_ids | STRING | NULLABLE | | timestamp | TIMESTAMP | NULLABLE | | event\_name | STRING | NULLABLE | | event\_value | STRING | NULLABLE | | user\_object | STRING | NULLABLE | | statsig\_metadata | STRING | NULLABLE | | company\_metadata | STRING | NULLABLE | {% /tab %} {% tab title="S3" %} **Setup summary** Configure: * Region * Bucket * one folder name per export type Permissions: * Grant the Statsig IAM user bucket-level access to list the bucket and retrieve its location. * Grant object-level read, write, and delete permissions, including multipart upload management. S3 exports are file-based. Statsig writes export files to the configured bucket and folder for each export type. In the export UI, the per-export destination is configured as a folder rather than a table. **Export outputs** | Export type | Destination shape | | ----------- | ----------------- | | Exposures | Files written to the configured folder in your bucket | | Events | Files written to the configured folder in your bucket | {% /tab %} {% /tabs %} ## Troubleshoot exports Statsig can notify you if your data connection is failing. To enable notifications, go to *Settings > My Account > Email Notifications > Edit* and select **Alerts** to subscribe. If your exports are failing, confirm your warehouse is connected with up-to-date credentials and the necessary Read, Write, and Delete permissions. ================================================================================ # CDN Edge Testing for Cached Resources URL: https://docs.statsig.com/guides/cdn-edge-testing Updated: 2026-07-11 ================================================================================ ## Background Most customers with heavy web traffic use a CDN to serve resources from cache to reduce load on their web servers. This has historically made testing difficult because calling the SDK for all requests isn't practical. Edge compute (offered by most CDN providers) lets customers run code at their CDN edge, assign users to tests, and determine which resources to serve with low latency. This pattern is optimal for testing with cached content without sacrificing cache-hit ratio. When running the Statsig SDK at the edge isn't possible, implement the SDK on the origin server or use a client SDK instead. {% figure caption="Example only. Implementation may vary depending on your provider." %} ![architecture](/images/edgefcn.png) {% /figure %} ## Best practices * Use a provider-specific "Config Sync" [integration](https://console.statsig.com/integrations): Statsig automatically syncs your configuration data to your provider's edge storage. * Use a provider-specific Data Adapter: This lets the Statsig SDK initialize using configuration stored at the edge near your function. Find links for each provider below. * Use [statsig-node-lite](https://www.npmjs.com/package/statsig-node-lite): A reduced version of the Node SDK that includes only the essentials and significantly improves initialization performance for cold-start requests. After initialization, evaluate using Node Lite's sync methods, such as `checkGateSync`, `getFeatureGateSync`, `getExperimentSync`, and `getLayerSync`. * Persist a uuid: You need a user identifier that can consistently assign a user to test buckets. Generate a uuid in your function and store it in a cookie. * Persist assignments in a cookie: Store assignments in a cookie so your code can skip SDK calls when a user is already assigned to a test. Use a session cookie (a cookie that expires when the user closes their browser). * Persist client instance: [This pattern](/guides/serverless#usage) allows the Statsig client instance to persist across requests when the edge function remains warm, which improves performance. * Use [Target Apps](/sdks/target-apps): Target apps let you sync a specific subset of experiments and gates to your edge function, reducing the footprint of your project config and improving performance. Target Apps are compatible with all "config sync" integrations. * Consider disabling automatic exposure logging and logging exposure events only after a user has been exposed to the treatment. Logging earlier pollutes test results with users who didn't see the treatment. In Node Lite, use the explicit exposure-disabled sync methods, such as `getExperimentWithExposureLoggingDisabledSync`, `checkGateWithExposureLoggingDisabledSync`, `getFeatureGateWithExposureLoggingDisabledSync`, `getLayerWithExposureLoggingDisabledSync`, or `getConfigWithExposureLoggingDisabledSync`. If you use the top-level `Statsig` wrapper, log the exposure later with `manuallyLogExperimentExposure`, `manuallyLogGateExposure`, `manuallyLogLayerParameterExposure`, or `manuallyLogConfigExposure`; if you use a `StatsigServer` instance directly, use `logExperimentExposure`, `logGateExposure`, `logLayerParameterExposure`, or `logConfigExposure`. * Node Lite doesn't use the Node Core `EvaluationOptions` pattern for disabling exposure logging. Refer to the current [top-level `Statsig` API](https://github.com/statsig-io/node-js-lite-server-sdk/blob/main/src/index.ts) and [`StatsigServer` API](https://github.com/statsig-io/node-js-lite-server-sdk/blob/main/src/StatsigServer.ts) for the full method lists. * Consider how to manage exposure tracking downstream in your application. All SDKs support manual exposure logging. You can also use the [HTTP API for logging exposures](/http-api/overview#log-exposure-event). * If you log exposures from your edge function, use the `context.waitUntil` method if your edge provider supports it ([examples](/server/nodejsServerSDK#environment-specific-setup)). ## Cloudflare Implementation Cloudflare lets you start a serverless edge worker that runs before cache lookup and can modify the response to the viewer. The Worker architecture gives you full code control over how to map test assignments to resources served to the user. You can modify both the URL (used as the cache key) and headers used to fetch resources from the CDN, then pass the modified request to origin on cache-miss. The Cloudflare Worker runtime supports installing the [Statsig Node Lite SDK](https://github.com/statsig-io/node-js-lite-server-sdk) as a dependency to determine test assignments before fetching a resource. Statsig offers both an [integration with Cloudflare KV](/integrations/cloudflare) and a pre-built [KV Data Adapter](https://github.com/statsig-io/cloudflare-data-adapter-node/tree/master), so that SDK initialization is fast and doesn't depend on network requests back to Statsig. {/* Cloudflare promotes a standard pattern for determining test assignment at the edge as described [here](https://developers.cloudflare.com/pages/functions/examples/ab-testing/). */} ## Fastly Implementation Fastly Compute supports Functions at the Edge, which lets customers handle assignment at the edge. ### KV (fully supported) [Fastly's KV storage solution](https://www.fastly.com/blog/be-among-the-first-to-try-the-greatest-kv-store-ever-made) is touted as being highly-performant and is now their recommended solution over their legacy ConfigStore documented below. Statsig offers both a Config-Sync for Fastly KV as well as a KV DataAdapter which can be found [here](/integrations/fastly). ### ConfigStore (not recommended) The [integration with Fastly's ConfigStore](/integrations/fastly) was the original offering. ConfigStore values are limited to 8 kilobytes, a limit reached quickly as the number of gates and tests in your project grows. This is paired with the [Fastly Config Store Data Adapter](https://www.npmjs.com/package/statsig-node-fastly), which lets the SDK initialize from ConfigStore rather than making a network request back to Statsig. ### Recommended pattern The Fastly pattern differs from other providers. As described in [the Fastly AB Testing guide](https://www.fastly.com/documentation/solutions/tutorials/ab-testing-edge-compute/#use-the-allocations-on-your-origin-server), it doesn't involve modifying the cache key (resource URL). Instead, it attaches `Fastly-ABTest-<TEST ID>` headers to tell the origin server how to render the resource. The origin response should include a `Vary` response header to tell the Fastly CDN what to cache. Refer to the Fastly guide as the source of truth for this design pattern. {% callout type="info" %} ### Fastly VCL Implementation Their legacy Varnish-based platform only supports configuration based caching and minimal scripting at the edge using VCL scripting language. Practically speaking, no Statsig SDK (or any SDK) supports the edge for this reason. In this instance, you must implement the Statsig SDK on the origin server and configure cache rules to force a cache-miss and allow assignments to take place on the origin server. {% /callout %} ## AWS Implementation AWS has a variety of serverless solutions. The sections below detail the recommended pattern and limitations for each. ### Lambda@Edge Implementation Lambda@Edge functions are triggered by CloudFront events. Lambda@Edge implementations carry a few unique challenges: * AWS doesn't offer a KV store solution compatible with Lambda@Edge. Their [KeyValueStore](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/kvs-with-functions.html) offering is only compatible with CloudFront Functions. As a result, you must either: * Use the default Statsig SDK initialization, which incurs a hit over the network to Statsig to load configurations * Bundle your project config statically into the function package. You can consider using the [Config-Change Webhook](/integrations/event_webhook#config-change-webhooks) to automate this. You need to download the configuration JSON at the following URL to bundle it into your Lambda package: https://api.statsigcdn.com/v1/download\_config\_specs/SERVER\_SDK\_KEY.json * Store your project config in S3 and use Cloudfront as means to serve it optimally when fetching it from your Lambda@Edge function. * Function size is capped at 1MB in package size (for viewer request and viewer response triggers, which this setup requires). * Lambda model uses [four events triggers](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-cloudfront-trigger-events.html), calling your function at various stages of the request lifecycle. For this use case, you need two: `Viewer Request` and `View Response`. AWS provides documentation on how requests can be handled and manipulated [here](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-the-edge.html). #### Viewer Request function CloudFront calls Viewer Request before cache lookup. It defines a uuid, generates test assignment, and modifies the request URL through `Request.URI`. This function type can't modify response headers, so you must pass assignment and uuid information to the Viewer Response function to set cookies. #### Viewer Response function Viewer Response function is responsible only for setting uuid and assignment cookies as needed. ### CloudFront Functions Implementation Statsig doesn't support SDKs here. CloudFront Functions have a 1ms max runtime and 10KB max function size. Use Lambda@Edge instead, or accept cache misses to perform assignment at origin. ## Akamai Implementation [Akamai has a documented pattern](https://www.akamai.com/blog/developers/better-a-b-testing-with-edgeworkes-edgekv) for using EdgeWorkers and EdgeKV to run A/B tests with cached content, including code samples and an [architecture overview](https://www.akamai.com/solutions/edge/serverless-computing/use-cases). Add the Statsig SDK to the worker function as a dependency and call it for assignment, rather than using the basic random assignment shown in their code examples. The [Akamai Edge integration](/integrations/akamai) includes an integration with EdgeKV and a Node package tested and validated within the Akamai edge runtime. ================================================================================ # Serverless URL: https://docs.statsig.com/integrations/serverless Updated: 2026-07-11 ================================================================================ The serverless SDK is a lightweight JavaScript SDK optimized for serverless environments and compatible with any platform. The serverless SDK provides the same functionality as all other Statsig server SDKs. {% steps %} {% step title="Install the Statsig SDK" %} ```bash npm install @statsig/serverless-client ``` {% /step %} {% step title="Import the Statsig SDK" %} ```bash import {StatsigServerlessClient} from '@statsig/serverless-client' ``` {% /step %} {% step title="Create a StatsigServerlessClient instance" %} ```javascript const client = new StatsigServerlessClient(process.env.STATSIG_KEY) ``` The client instantiation takes two arguments: * `sdkKey : string` Your Statsig client API key. Available from the [Project Settings](https://console.statsig.com/api_keys) page in the Statsig Console. Statsig uses this to authenticate requests. * `options : StatsigOptions` Refer to [StatsigOptions](/client/javascript-sdk#statsig-options) for more options. {% /step %} {% step title="Client Initialization" %} This line initializes the client by loading feature gate and experiment configurations over the network. ```javascript const init = await client.initializeAsync(); ``` {% /step %} {% step title="Checking a Gate" %} ```javascript const GateResult = client.checkGate('pass_gate', user); ``` The `checkGate` method takes two arguments: * `name : string` The name of the Statsig gate that you are checking. * `user : StatsigUser` The Statsig user object for whom the gate is checked. For more information on the user object, refer to [StatsigUser](/sdks/user#introduction-to-the-statsiguser-object). Refer to the [JavaScript on-device evaluation SDK documentation](/client/jsOnDeviceEvaluationSDK) for how to check other entities like experiments and dynamic configs. {% /step %} {% step title="Logging an Event" %} ```javascript client.logEvent('gate_check', { userID: randomUserId }); ``` The `logEvent` method takes two parameters: * `eventOrName : string | StatsigEvent` The name and details of the event you are logging. * `user : StatsigUser` The Statsig user object for whom the event is logged. For more information on event logging, refer to [Logging an Event](/client/jsOnDeviceEvaluationSDK#logging-an-event). {% /step %} {% step %} ```javascript client.flush(); ``` This flushes all events from the SDK to Statsig. **Without this, diagnostic information and logged event data won't appear in the Statsig Console**. {% /step %} {% /steps %} ### Putting it all together ```javascript index.js import { StatsigServerlessClient } from '@statsig/serverless-client'; export default async function handler(request) { const client = new StatsigServerlessClient(process.env.STATSIG_KEY); let init = await client.initializeAsync(); const user = { userID: Math.random().toString().substring(2, 5) }; const passed = client.checkGate('pass_gate', user); client.logEvent('serverless_event', user, passed.toString()); client.flush(); return new Response( JSON.stringify({ passed, user }), ); } ``` ### Bootstrapping Use the serverless SDK to bootstrap your client SDK. ```javascript // Get client initialize response for a user const values = statsig.getClientInitializeResponse(user, options); // Pass values to a client SDK to initialize without a network request ``` For more information on bootstrapping, including options and a full code example, refer to [Client SDK Bootstrapping](/server-core/node-core#client-sdk-bootstrapping-|-ssr). ### Bring your own CDN The Statsig Serverless SDK also supports a bring-your-own-CDN model. Store your Statsig config specs anywhere and initialize your client directly from that source. This model has been tested and optimized for AWS Lambda/Lambda@Edge and S3, but is compatible with any other data source. The URL you provide must return the Statsig config specs for your project. ```javascript let init = await client.initializeViaURL("https://my-statsig-config.s3.us-east-1.amazonaws.com/my_dcs.json") ``` After initialization, use the SDK as usual. ================================================================================ # Vercel URL: https://docs.statsig.com/integrations/vercel Updated: 2026-07-11 ================================================================================ Statsig offers a suite of integration tools for use with Vercel: * Statsig automatically pushes project changes to Vercel's Edge Config, providing low-latency SDK startup. * Statsig provides a Vercel helper that handles client initialization and event flushing so you can focus on your business logic. {% steps %} {% step %} #### Configure integration 1. Go to the [Vercel Marketplace](https://vercel.com/integrations/statsig) and install the Statsig integration. 2. Statsig prompts you to create a new account or link an existing one. {% accordion-group %} {% accordion title="New Statsig Account" %} * Click **Accept and Create** * Enable Edge Config Syncing to have Statsig automatically push configs into Vercel's Edge Config {% figure %} ![Vercel marketplace installation interface](/images/vercel_marketplace_install.png) {% /figure %} After Statsig creates the account, select **Open in Statsig** in the top right to set up gates and experiments for your new Statsig project. {% figure %} ![Vercel marketplace project view interface](/images/vercel_marketplace_view.png) {% /figure %} Your Statsig Integration for Vercel is complete! {% /accordion %} {% accordion title="Existing Statsig Account" %} * Select your project and edge config * Click **Save** Your Statsig Integration is now complete! You can go to the **storage** tab to confirm your Statsig config specs have propagated to your Edge Config. {% /accordion %} {% /accordion-group %} {% /step %} {% step %} #### Install the Statsig SDK ```bash npm install @statsig/vercel-edge ``` {% /step %} {% step %} #### import the Vercel helper ```bash import {handleWithStatsig} from '@statsig/vercel-edge ``` {% /step %} {% step %} #### Use the SDK ```Javascript export default handleWithStatsig(handler, params) ``` The helper method takes two arguments: * `handler` Your Vercel function code * `params` | Parameter | Optional | Type | Description | |-----------|----------|------|-------------| | `configKey` | No | `string` | The Key associated with your Statsig specs in your Edge Config | | `envStatsigKey` | No | `string` | Your Statsig Client API Key | | `statsigOptions` | Yes | `StatsigOptions` | Refer to [StatsigOptions](/client/javascript-sdk#statsig-options) | Best practice: store `configKey` and `envStatsigKey` as environment variables in your Vercel project settings. {% /step %} {% /steps %} ### Example Usage ```javascript api/index.js import { handleWithStatsig } from "@statsig/vercel-edge"; export const config = {runtime: 'edge'}; async function myHandler(request, client){ const user = { userID: Math.random().toString().substring(2, 5) }; //Generates a random user id const passed = client.checkGate('test_vercel_edgeconfig', user); client.logEvent('vercel_wrapper', user, passed.toString()); return new Response( JSON.stringify({ passed, user }) ); export default handleWithStatsig(myHandler,{ configKey : process.env.EDGE_CONFIG_KEY, statsigSdkKey: process.env.STATSIG_KEY }) ``` {% accordion title="How it Works" %} The `handler` parameter is your Vercel function code: the same code you would normally export directly in your API route (for example, `myHandler` in the snippet above). Instead of exporting it directly, pass it into `handleWithStatsig`, which handles Statsig setup and cleanup. When your function runs: 1. The Statsig SDK initializes a client using the config specs stored in your Edge Config. 2. Your function code (the handler) runs as usual. 3. Statsig automatically flushes any events you log when execution finishes. {% /accordion %} The helper automatically: * Initializes the Statsig Client with config specs from your Edge Config * Executes your Vercel function code (your business logic and Statsig usage) * Flushes all events after your handler completes execution * Cleans up resources ## Advanced implementation {% accordion title="Advanced/manual usage" %} Use the advanced/manual setup if: * You need fine-grained control over initialization timing * You need fine-grained control over event flushing timing * You need to customize error handling behavior ### Prerequisites * Completed the [Statsig Vercel integration setup](#configure-integration) {% steps %} {% step title="Install the Statsig SDK" %} ```bash npm install @statsig/vercel-edge ``` {% /step %} {% step title="Import the Statsig SDK" %} ```bash import {StatsigVercelClient} from '@statsig/vercel-edge' ``` {% /step %} {% step title="Create a StatsigVercelClient instance" %} ```javascript const client = new StatsigVercelClient(process.env.STATSIG_KEY) ``` The client instantiation takes two arguments: * `sdkKey : string` Your Statsig client API key. Available from the [Project Settings](https://console.statsig.com/api_keys) page in the Statsig Console. Statsig uses this to authenticate requests. * `options : StatsigOptions` Refer to [StatsigOptions](/client/javascript-sdk#statsig-options) for more options. Best practice: store `sdkKey` as an environment variable in your Vercel project settings. {% /step %} {% step title="Client Initialization" %} This line initializes the client by loading feature gate and experiment configurations directly from your Vercel Edge Config. ```javascript const init = await client.initializeFromEdgeConfig(<YOUR_EDGE_CONFIG_KEY>); ``` The client initialization takes one argument: * `ConfigKey : string` The Key associated with your Statsig specs in your Edge Config {% /step %} {% step title="Checking a Gate" %} ```javascript const GateResult = client.checkGate('pass_gate', user); ``` The `checkGate` method takes two arguments: * `name : string` The name of the Statsig gate to check. * `user : StatsigUser` The Statsig user object for whom the gate is checked. For more information on the user object, refer to [StatsigUser](/sdks/user#introduction-to-the-statsiguser-object). Refer to the [JavaScript on-device evaluation SDK documentation](/client/jsOnDeviceEvaluationSDK) for how to check other entities like experiments and dynamic configs. {% /step %} {% step %} ```javascript client.logEvent('gate_check', { userID: randomUserId }); ``` The `logEvent` method takes two parameters: * `eventOrName : string | StatsigEvent` The name and details of the event to log. * `user : StatsigUser` The Statsig user object for whom the event is logged. For more information on event logging, refer to [Logging an Event](/client/jsOnDeviceEvaluationSDK#logging-an-event). {% /step %} {% step %} ```javascript waitUntil(statsig.flush()); ``` This flushes all events from the SDK to Statsig. **Without this, diagnostic information and logged event data won't appear in the Statsig Console**. {% /step %} {% /steps %} ### Putting it all together ```javascript api/index.js import { StatsigVercelClient } from '@statsig/vercel-edge'; import { waitUntil } from '@vercel/functions'; export const config = { runtime: 'edge', }; export default async function handler(request) { const client = new StatsigVercelClient(process.env.STATSIG_KEY); let init = await client.initializeFromEdgeConfig(process.env.EDGE_CONFIG_KEY); const user = { userID: Math.random().toString().substring(2, 5) }; const passed = client.checkGate('pass_gate', user); client.logEvent('vercel_event', user, passed.toString()); waitUntil(client.flush()) return new Response( JSON.stringify({ passed, user }), ); } ``` ## Other Considerations ### Polling for updates v5.13.0+ The SDK can't poll for updates across requests because Vercel Edge Functions don't allow timers outside the request handler. Statsig doesn't provide an API to detect config spec updates. When you change your project definition in the Statsig console, the changes propagate to your Edge Config and take effect the next time you initialize the Statsig client. ### Flushing events v4.16.0+ The SDK enqueues logged events and flushes them in batches. To ensure the SDK flushes events, call flush using `waitUntil()` from `@vercel/functions`. This keeps the request handler alive until events are flushed without blocking the response. ``` waitUntil(client.flush()); ``` ### Size Limits Vercel Edge Config has maximum size limits that may prevent Statsig from pushing configs into your Edge Config. Refer to [Vercel Edge Config limits](https://vercel.com/docs/concepts/edge-network/edge-config/edge-config-limits) for the current limits. ### Unsupported Features Statsig doesn't sync ID Lists into Vercel Edge Config. If you rely on large (>1000) ID lists, you can't check them in your Vercel edge functions. This is why the SDK sets `initStrategyForIDLists: 'none'` during initialization. {% /accordion %} To review evaluations, go to the gate you created and check the Diagnostics tab. {% figure %} ![Diagnostics Stream](/images/integrations/vercel/2b690181-0103-43be-b791-7cf0a7db66eb.png) {% /figure %} To check the events you logged, in the **Statsig Console**, go to **Data** -> **Events**. {% figure %} ![vercel_event example in Statsig showing lineage chart and log stream table](../images/client/vercel_event.png) {% /figure %} The Vercel Edge Config integration for Statsig is now working. ## Using Flags SDK in NextJS If you use NextJS in your Vercel project, you can use Statsig through Flags SDK and take advantage of built-in precompute patterns for improved performance. Refer to the [Statsig Adapter for Flags SDK docs](https://flags-sdk.dev/docs/api-reference/adapters/statsig) for setup steps. The marketplace app sets all required environment variables for the Flags SDK by default. ## Sending logs to Statsig You can connect your Vercel logs to Statsig with a Log Drain to start exploring them in Logs Explorer. 1. From the [Vercel dashboard](https://vercel.com/), go to **Settings -> Drains** and click **Add Drain -> Integration**. 2. Select **Statsig**, follow the configuration steps provided, and choose a project to connect with the service. 3. Navigate to [Statsig's Logs Explorer](https://console.statsig.com/logs) to see your logs flow through. ================================================================================ # Cloudflare KV URL: https://docs.statsig.com/integrations/cloudflare Updated: 2026-07-11 ================================================================================ Statsig offers a set of integrations that make usage with Cloudflare easy: * Automatically pushing changes to Cloudflare's KV store, for low-latency SDK startup * A helper pattern that handles Statsig SDK overhead, so you can focus on worker logic {% steps %} {% step title="Configure Integration" %} Navigate to *Project Settings > Integrations* in the Statsig Console, then select **Cloudflare** and enter: * **Cloudflare Account ID**: Can be found in Cloudflare portal on the Compute (Workers) page, under Account Details * **KV Namespace ID**: Create a new namespace, then go to Account Home -> Storage and Databases -> Workers KV, and copy the ID from the table view. * **Cloudflare API Key**: In Cloudflare portal under Account Home -> Profile -> API Tokens. Use a token with Account.Workers KV Storage Edit Permissions. {% accordion title="Advanced: Filtering by Target App" %} You can also filter the configs that are synced to your KV namespace by Target App. This becomes important as you add more configs to your project, but for now, you can leave this unchecked. {% /accordion %} {% /step %} {% step title="Enable Integration" %} Click **Enable**. The Statsig backend pushes a config to your KV namespace in under 60 seconds. In your KV namespace, navigate to **KV Pairs**. You'll see an entry starting with `statsig-`. This is the `key` associated with your KV storage. Copy or record this key for later. {% /step %} {% step title="Add the Statsig SDK to your Worker" %} Set up a worker to read experiments and gates from your KV namespace. If you haven't created a worker before, follow the instructions [here](https://developers.cloudflare.com/workers/). After creating your worker, you need to connect your KV store to your worker through a binding. Navigate to **Compute (Workers)** -> **Select Your Worker** -> **Bindings** -> **Add binding** -> **KV namespace**. Name your binding under **Variable name**. Under **KV namespace**, select your KV store name. For more information on connecting your worker to your KV store, you can follow the instructions [here](https://developers.cloudflare.com/pages/functions/bindings/). {% /step %} {% step title="Install the Statsig SDK" %} Install the Statsig serverless SDK: ```bash npm install @statsig/serverless-client ``` {% /step %} {% step title="Use the SDK" %} The helper method takes two arguments, a handler function, and a ParamsObject. **Put *all* of your worker logic in the handler function**, along with your Statsig usage. ```javascript highlight={5} import { handleWithStatsig } from '@statsig/serverless-client/cloudflare'; export default handleWithStatsig( async (request, env, ctx, client) => { // Your business, and Statsig logic here }, { kvKey: 'kv_key', envStatsigKey: 'statsig_key', envKvBindingName: 'STATSIG_KV' } ); ``` {% callout type="note" %} Store the required ParamsObject params (kvKey, envStatsigKey, envKvBindingName) as env variables, either in your wrangler.toml or as Cloudflare secrets. {% /callout %} {% accordion title="ParamsObject Definition" %} {% parameter name="kvKey" type="string" %} Environment variable name containing your KV pair key {% /parameter %} {% parameter name="envStatsigKey" type="string" %} Environment variable name containing your Statsig client key {% /parameter %} {% parameter name="envKvBindingName" type="string" %} Your KV binding name {% /parameter %} {% parameter name="statsigOptions" type="StatsigOptions" %} See StatsigOptions [here](/client/javascript-sdk#statsig-options) {% /parameter %} **Best practice:** * store `envStatsigKey` as a Cloudflare secret. You can set this in the Cloudflare dashboard under, **Worker → Settings → Variables and Secrets** * store `kvKey` and `envKvBindingName` in your wrangler.toml {% /accordion %} ### Example usage This is an example of an end-to-end worker function that uses the Statsig SDK and returns a flag value. This is all you need: it compiles as the index.js file in your worker. {% codetabs %} ```javascript index.js expandable import { handleWithStatsig } from '@statsig/serverless-client/cloudflare'; export default handleWithStatsig( async (request, env, ctx, client) => { const randomUserId = Math.floor(Math.random() * 100).toString(); const gate = client.getFeatureGate("test_cloudflare_sync", { userID: randomUserId }); const value = gate.value; client.logEvent('new_event', { userID: randomUserId }); return new Response(`Gate check result: ${value}`); }, { kvKey: 'kv_key', envStatsigKey: 'statsig_key', envKvBindingName: 'STATSIG_KV' } ); ``` ```wrangler wrangler.toml name = "test" main = "src/index.js" compatibility_date = "2025-09-10" [vars] kv_key = "statsig-1gh32fg61hds9876" [[kv_namespaces]] binding = "STATSIG_KV" id = "b76664aa8259481e834e7c549443c6541" [observability] enabled = true ``` {% /codetabs %} {% /step %} {% /steps %} The helper automatically: * Initializes the Statsig Client with config specs from your KV store * Executes your handler code (Your business logic + Statsig usage) * Flushes all events after your handler completes execution * Cleans up resources {% accordion title="Advanced/manual usage" %} **Use the advanced/manual setup if:** * You need fine-grained control over initialization timing * You need fine-grained control over event flushing timing * You need to customize error handling behavior ## Prerequisites 1. Completed the [Statsig Cloudflare KV integration setup](#configure-integration) 2. [Created and bound a KV namespace to your worker](#add-the-statsig-sdk-to-your-worker) ## Installation Install the Statsig serverless SDK. ```bash npm install @statsig/serverless-client ``` ## Import Import the Cloudflare client. ```bash import { StatsigCloudflareClient } from '@statsig/serverless-client/cloudflare'; ``` Connect the components. This involves: 1. Creating a `StatsigCloudflareClient` instance. 2. Initializing the Statsig client 3. Checking a Gate 4. Logging an event 5. Flushing events to Statsig These steps follow the same pattern as other Statsig SDKs. The only difference is that this SDK initializes from the KV store instead of the Statsig backend. The example checks a gate called "test\_cloudflare\_sync" set to a 50% pass rate. A random userID is generated on every request, so the gate evaluates to true approximately 50% of the time. ### 1. Creating a `StatsigCloudflareClient` instance ```bash const client = new StatsigCloudflareClient("<Your Statsig client key>"); ``` The client instantiation takes two arguments: * `sdkKey : string` This is your Statsig client API key. You can find it on the [Project Settings](https://console.statsig.com/api_keys) page in the Statsig Console. Statsig uses this to authenticate your requests. * `options : StatsigOptions` Refer to [StatsigOptions](/client/javascript-sdk#statsig-options) for available options. For best practice: * store `sdkKey` as a Cloudflare secret. You can set this in the Cloudflare dashboard under, **Worker → Settings → Variables and Secrets** ### 2. Client initialization The following line initializes the client by loading feature gate and experiment configurations directly from your Cloudflare KV store. ```bash const initResult = await client.initializeFromKV(env.<YOUR_KV_NAMESPACE_BINDING>, <YOUR_KV_KEY>); ``` The client initialization takes two arguments: * `KvBinding` This is the binding you named earlier. Remember to provide this argument as `env.YOUR_KV_NAMESPACE_BINDING` * `KvKey : string` This is the KV pair key that the Statsig integration generated. You can find it under **Workers KV** -> **Your KV namespace** -> **KV Pairs** For best practice: * store `kvBinding` and `kvKey` in your wrangler.toml ### 3. Checking a gate ```bash const value = client.checkGate("test_cloudflare_sync", { userID: randomUserId }); ``` This line checks the gate in code. The `checkGate` method takes two arguments: * `name : string` The name of the Statsig gate that you are checking. * `user : StatsigUser` The Statsig user object to check the gate against. For more information on the user object, refer to the [StatsigUser object documentation](/sdks/user#introduction-to-the-statsiguser-object). Refer to the [JavaScript on-device evaluation SDK documentation](/client/jsOnDeviceEvaluationSDK) for how to check other entities such as experiments and dynamic configs. ### 4. Logging an event ```bash client.logEvent('gate_check', { userID: randomUserId }); ``` This line logs an event in code. The `logEvent` method takes two parameters: * `eventOrName : string | StatsigEvent` This is the name and details of the event you are logging. * `user : StatsigUser` The Statsig user object to log the event for. For more information on event logging, refer to the [JavaScript on-device evaluation SDK documentation](/client/jsOnDeviceEvaluationSDK#logging-an-event). ### 5. Flushing events ```bash ctx.waitUntil(statsig.flush()); ``` This flushes all events from the SDK to Statsig. Without this step, diagnostic information and logged event data won't appear in the Statsig Console. ### Putting it all together {% tabs %} {% tab title="index.js" %} ```Javascript import { StatsigCloudflareClient } from '@statsig/serverless-client/cloudflare'; export default { async fetch(request, env, ctx) { try { const client = new StatsigCloudflareClient(env.statsig_key); const initResult = await client.initializeFromKV(env.STATSIG_KV, env.kv_key); const randomUserId = Math.floor(Math.random() * 100).toString(); //generates a random user id const value = client.checkGate("test_cloudflare_sync", { userID: randomUserId }); client.logEvent('gate_check', { userID: randomUserId }); ctx.waitUntil(client.flush()); return new Response(`Value: $\{value\}, userID: ${randomUserId}); } catch (error) { return new Response(`Error: ${error.message}`, { status: 500 }); } } }; ``` {% /tab %} {% tab title="wrangler.toml" %} ```wrangler name = "test" main = "src/index.js" compatibility_date = "2025-09-10" [vars] kv_key = "statsig-1gh32fg61hds9876" [[kv_namespaces]] binding = "STATSIG_KV" id = "b76664aa8259481e834e7c549443c6541" [observability] enabled = true ``` {% /tab %} {% /tabs %} If you want to check on the evaluations you are getting, you can go to the gate you created for this example and look at the evaluations in the Diagnostics tab. {% figure %} ![Diagnostics Stream](/images/integrations/cloudflare/1cc865ed-e15c-41a4-8979-24e1d457a7b1.png) {% /figure %} If you want to check the events you logged, in the **Statsig Console**, go to **Data** -> **Events** {% figure %} ![Statsig Events log stream showing gate_check events with timestamps and user IDs](../images/client/Events.png) {% /figure %} The Cloudflare KV integration for Statsig is now working. ## Other considerations ### Polling for updates The SDK can't poll for updates across requests since [**Cloudflare doesn't allow for timers**](https://developers.cloudflare.com/workers/reference/security-model/#step-1-disallow-timers-and-multi-threading). To optimize for edge use cases, Statsig doesn't provide an API to recognize updates to your config specs. When you change your project definition in the Statsig console, Statsig propagates the changes to the KV store, and they take effect the next time you initialize the Cloudflare client. ### Flushing events The SDK enqueues logged events and flushes them in batches. To ensure events are properly flushed, call flush using `context.waitUntil`. This keeps the request handler alive until events are flushed without blocking the response. ```bash context.waitUntil(client.flush()); ``` ### Size limits Cloudflare KV has maximum size limits that may prevent Statsig from pushing configs into your KV. Refer to the [Cloudflare KV limits documentation](https://developers.cloudflare.com/workers/platform/limits/#kv-limits) for current limits. If your payload continues to grow, filter the payload by a Target App in the integration settings. ### Unsupported features Statsig doesn't sync ID Lists into Cloudflare KVs. If you rely on large (>1000) ID lists, you can't check them in your Cloudflare Worker. {% /accordion %} ================================================================================ # Cloudflare Workers AI URL: https://docs.statsig.com/integrations/workersai Updated: 2026-07-11 ================================================================================ ## Statsig Cloudflare Workers AI integration By integrating Statsig with Cloudflare Workers AI, you can conduct experiments on different prompts and models, and gather real-time analytics on model performance and usage. Statsig provides tools to control variations dynamically, measure success metrics, and gain insights into your AI deployments at the edge. For general setup of Statsig with Cloudflare Workers (including KV namespace configuration and SDK installation), refer to the [Cloudflare Workers Integration documentation](/integrations/cloudflare). For setting up Workers AI, refer to the [Cloudflare Workers AI documentation](https://developers.cloudflare.com/workers-ai/). ### What this integration enables When you deploy a Cloudflare Worker running AI code, Statsig can automatically inject lightweight instrumentation to capture inference requests and responses. Statsig tracks key metadata for each request (models, latency, token usage). You can also include additional metadata you find valuable (success rates, user interactions, and so on). This integration enables: - **Experimentation:** Set up experiments (for example, prompt “A” vs. prompt “B”, llama vs. deepseek models) and define success metrics (conversion, quality rating, user retention). Statsig determines which variation each request uses, ensuring statistically valid traffic splits. - **Real-time Analytics:** The Statsig SDK sends anonymized event data (model outputs, user interactions, metrics) to Statsig’s servers in real time. Statsig gathers data at the edge with minimal overhead and streams it for analysis. ### Use case 1: Prompt and/or model experiments This use case demonstrates how to use Statsig experiments to test different prompts and AI models within your Cloudflare Worker. The example experiment has 4 groups: a control using the default prompt and llama model, and one group for each variant that switches to a different prompt or model (deepseek, in this case). #### Sample experiment setup in Statsig Console {% figure %} ![prompt and model experiment](/images/integrations/workersai/e5ed3e92-60af-4dc6-95a6-0f99eeae5152.png) {% /figure %} Refer to the sample code below for the experiment implementation in a Cloudflare Worker with AI. ### Use case 2: Model analytics Beyond experiments, the logging mechanism illustrated below provides insights into your AI model's performance and usage patterns. You can keep the default parameters for models and prompts and still get insights from the metadata you log to Statsig. #### What to track for model analytics - **Latency (`ai_inference_ms`):** Crucial for understanding user experience. You can monitor average, P90, P99 latencies in Statsig. - **Model Usage (e.g., `prompt_tokens`, `completion_tokens`):** If your AI provider returns token counts, logging these allows you to track cost and efficiency. - **Error Rates:** Log events when the AI model returns an error or an unexpected response. - **Output Quality (using custom events):** - **User Feedback:** If your application allows users to rate the AI's response (e.g., thumbs up/down), log these as Statsig events. - **Downstream Metrics:** Track how the AI's output influences key business metrics (e.g., conversion rates if the AI is generating product descriptions, or user engagement if it's a chatbot). #### View model analytics in Statsig By logging these metrics, you can create custom dashboards in Statsig to monitor the health and effectiveness of your AI models in real time. This lets you identify performance bottlenecks, cost inefficiencies, and areas for improvement. Within minutes of adding the logging from the example below to your function, you can see the breakdown of latency per model with a query like this: {% figure %} ![metrics explorer](/images/integrations/workersai/c18ffb62-ff91-4fec-b5e4-64eaab63d528.png) {% /figure %} #### Example worker code for prompt/model experimentation and analytics ```typescript import { CloudflareKVDataAdapter } from "statsig-node-cloudflare-kv"; import Statsig from "statsig-node"; import { StatsigUser } from "statsig-node"; export default { async fetch( request: Request, env: Env, ctx: ExecutionContext, ): Promise<Response> { await initStatsig(env); // ideally, use a logged in userid. In this example, I have the RayID from cloudflare const rayID = request.headers.get("cf-ray") || ""; const user = { userID: rayID, }; const promptExp = Statsig.getExperimentSync( user, "workers_ai_experiment", // Name of your experiment in Statsig Console ); // fetch the prompt and model to use for this ray ID // providing default values in case of failure to initialize statsig from the kv store const prompt = promptExp.get( "prompt", "What is the origin of the phrase Hello, World", ); const model = promptExp.get("model", "@cf/meta/llama-3.1-8b-instruct"); const start = performance.now(); const response = await env.AI.run(model, { prompt, }); const end = performance.now(); const aiInferenceMs = end - start; logUsageToStatsig(user, model, response, aiInferenceMs); ctx.waitUntil(Statsig.flush(1000)); return new Response(JSON.stringify(response.response)); }, } satisfies ExportedHandler<Env>; /** * Logs AI model usage and performance metrics to Statsig. * @param user The StatsigUser object. * @param model The name of the AI model used. * @param response The response object from the AI model (expected to contain a 'usage' field). * @param aiInferenceMs The time taken for AI inference in milliseconds. */ function logUsageToStatsig( user: StatsigUser, model: string, response: any, aiInferenceMs?: number, ) { const metadata = { ...(response?.usage || {}), ai_inference_ms: aiInferenceMs, }; Statsig.logEvent(user, "cloudflare_ai", model, metadata); } /** * Initializes the Statsig SDK. * Make sure you have the right bindings configured for the KV, and a secret for the Statsig API key * Refer to /integrations/cloudflare for more details on integrating Statsig with Cloudflare workers * @param env The Workers environment variables. */ async function initStatsig(env: Env) { const dataAdapter = new CloudflareKVDataAdapter( env.STATSIG_KV, "statsig-YOUR_STATSIG_PROJECT_ID", ); // Replace with your actual project ID await Statsig.initialize( env.STATSIG_SERVER_API_KEY, // Your Statsig secret key { dataAdapter: dataAdapter, postLogsRetryLimit: 0, initStrategyForIDLists: "none", initStrategyForIP3Country: "none", disableIdListsSync: true, disableRulesetsSync: true, // Optimizations for fast initialization in Cloudflare Workers }, ); } ``` **Code explanation:** 1. **`initStatsig(env)`**: Initializes the Statsig SDK using the `CloudflareKVDataAdapter` to fetch configurations from Cloudflare KV, providing low-latency access to your experiment setups. Replace `'statsig-YOUR_STATSIG_PROJECT_ID'` with your actual Statsig project ID, and configure `STATSIG_SERVER_API_KEY` and `STATSIG_KV` as environment variables in your Worker. 2. **`Statsig.getExperimentSync(...)`**: Retrieves the assigned experiment variant for the current user (based on `rayID`) for the `workers_ai_experiment` experiment. The `get()` method retrieves the `prompt` and `model` parameters defined in your Statsig experiment, falling back to default values if the experiment or parameter isn't found. 3. **`env.AI.run(model, { prompt })`**: Executes the AI model provided by Cloudflare Workers AI with the dynamically chosen `model` and `prompt`. 4. **Latency Measurement**: `performance.now()` captures the start and end times of the AI inference so you can track the `ai_inference_ms` metric. 5. **`logUsageToStatsig(...)`**: Logs a custom event (`cloudflare_ai`) to Statsig. The `model` is included as the event value, and Statsig attaches metadata such as `ai_inference_ms` and any `usage` information (for example, token counts) returned by the AI model. 6. **`ctx.waitUntil(Statsig.flush(1000))`**: Ensures all logged events reach Statsig asynchronously before the Worker's execution context ends, without blocking the response. ### Other use cases enabled by this integration - **Prompt Tuning:** An e-commerce app running on Workers AI tries two different prompt styles for product descriptions. Statsig tracks cart conversion and time on site, revealing which prompt yields higher sales. - **Model Selection:** A developer tests GPT-3.5 vs. GPT-4 within Cloudflare Workers AI. Statsig shows which model, combined with specific temperature or frequency penalty values, generates more accurate or user-satisfying results. - **Response Latency vs. Quality:** By varying max token length and frequency penalties within an experiment, Statsig helps optimize for speed without sacrificing accuracy, crucial for user-facing chat applications. - **Cost Optimization:** Monitor `prompt_tokens` and `completion_tokens` by model and prompt variant to identify the most cost-effective AI configurations. ================================================================================ # Fastly URL: https://docs.statsig.com/integrations/fastly Updated: 2026-07-11 ================================================================================ Statsig offers a suite of integration tools for use with Fastly: * Statsig automatically pushes project changes to Fastly KV/Config Store, providing low-latency SDK startup. * Statsig provides a Fastly helper that handles client initialization and event flushing, so you can focus on your business logic. {% steps %} {% step %} #### Configure integration First, enable the Fastly integration in the Statsig Console. Navigate to [Project Settings -> Integrations](https://console.statsig.com/integrations), and then select Fastly Input the following: * **Fastly API Key** - Can be found in Fastly portal under **Account** -> **API Tokens**. * Create an **Automation Token** with: * **global** and **global:read** scope * **Store Type** - Select **"Config Store"** or **"KV Store"** depending on your storage type. * **Config Store ID** OR **KV Store ID**- Your Store ID There is also an option to filter the configs synced into your KV namespace by a Target App. Enable this as the size of your config payload grows. For now, you can leave this unchecked. After filling this out, select **Enable**. Within a minute, the Statsig backend generates a config payload from your Statsig project and pushes it into your store. Under your Config or KV Store, look for a key starting with the prefix `statsig-`. This is the `key` associated with your Statsig config specs in your store. Note this key, as it is required later. {% /step %} {% step %} #### Install the Statsig SDK Install the Statsig serverless SDK: ```bash npm install @statsig/serverless-client ``` {% /step %} {% step %} #### Import the statsig SDK Import the Statsig Helper: ```javascript import { handleWithStatsig} from "@statsig/serverless-client/fastly"; ``` {% /step %} {% step %} #### Use the SDK ```javascript handleWithStatsig(handler, params) ``` The helper method takes two arguments: * `handler` This is your Fastly Compute code. * `params : StatsigFastlyHandlerParams` | Parameter | Optional | Type | Description | |-----------|----------|------|-------------| | `statsigSdkKey` | No | `string` | Your Statsig client API key | | `fastlyStoreType` | No | `string` | Either `kv` or `config`, signifying your Fastly store type | | `storeId` | No | `string` | Your KV or Config store id | | `keyId` | No | `string` | The key storing your Statsig config specs in your Fastly store | | `apiToken` | No | `string` | Your Fastly API token used to authenticate requests to the Fastly API | | `statsigOptions` | Yes | `StatsigOptions` | See StatsigOptions [here](/client/javascript-sdk#statsig-options) | For best practice: store `statsigSdkKey` and `apiToken` in a Fastly [Secret Store](https://www.fastly.com/documentation/guides/compute/edge-data-storage/working-with-secret-stores/) ### Example usage ```javascript index.js import { handleWithStatsig} from "@statsig/serverless-client/fastly"; async function myHandler(event, client) { const user = { userID: Math.random().toString().substring(2, 5) }; const res = client.checkGate("pass_gate", user); client.logEvent("pass_gate", user); return new Response( JSON.stringify({ res, user }), { status: 200, headers: { 'Content-Type': 'application/json' } } ); } const handleRequest = handleWithStatsig(myHandler,{ statsigSdkKey: "client-LhxVWHSeZt2uor***********", fastlyStoreType: "kv", storeId:"7b12fn*********", keyId:"statsig-3htllY8XxFsJ*****", apiToken:"7NaRxS6R**********" }) addEventListener('fetch', (event) => event.respondWith(handleRequest(event))); ``` {% /step %} {% /steps %} The helper automatically: * Initializes the Statsig Client with config specs from your KV or Config store * Executes your handler code (your business logic and Statsig usage) * Flushes all events after your handler completes execution * Cleans up resources ### Advanced usage {% accordion title="Advanced/manual usage" %} Use the advanced/manual setup if: * You need fine-grained control over initialization timing * You need fine-grained control over event flushing timing * You need to customize error handling behavior ### Prerequisites 1. Completed the [Statsig Fastly integration setup](#configure-integration) {% steps %} {% step %} #### Install the Statsig SDK Install the Statsig serverless SDK: ```bash npm install @statsig/serverless-client ``` {% /step %} {% step %} #### Import the statsig SDK Import the Fastly client: ```javascript import { StatsigFastlyClient} from "@statsig/serverless-client/fastly"; ``` {% /step %} {% step %} #### Creating a `StatsigFastlyClient` instance ```javascript const client = new StatsigFastlyClient("<Your Statsig client key>"); ``` The client instantiation takes two arguments: * `sdkKey : string` Your Statsig client API key. Available from the [Project Settings](https://console.statsig.com/api_keys) page in the Statsig Console. Used to authenticate your requests. * `options : StatsigOptions` For more options, go to [StatsigOptions](/client/javascript-sdk#statsig-options). For best practice: store `sdkKey` in a Fastly [Secret Store](https://www.fastly.com/documentation/guides/compute/edge-data-storage/working-with-secret-stores/) {% /step %} {% step %} ### Client initialization The following line initializes the client by loading feature gate and experiment configurations directly from your Fastly KV or Config store. ```javascript const initResult = await client.initializeFromFastly(<YOUR_FASTLY_STORE_TYPE>, <YOUR_STORE_ID>, <KEY_ASSOCIATED_WITH_STASIG_SPECS>, <YOUR_FASTLY_API_KEY> ); ``` The client initialization takes four arguments: * `fastlyStoreType : string` This is the Fastly store type you are using represented by `kv` or `config` * `storeId : string` The id of your Fastly store * `keyId : string` The key storing the Statsig config specs in your store * `apiToken : string` Your Fastly API token For best practice: store `apiToken` in a Fastly [Secret Store](https://www.fastly.com/documentation/guides/compute/edge-data-storage/working-with-secret-stores/) {% /step %} {% step %} #### Checking a Gate ```javascript const value = client.checkGate("pass_gate", user); ``` The `checkGate` method takes two arguments: * `name : string` The name of the Statsig gate you are checking. * `user : StatsigUser` The Statsig user object for whom the gate is being checked. For more information on the user object, refer to [StatsigUser introduction](/sdks/user#introduction-to-the-statsiguser-object). Refer to the [Javascript on-device evaluation SDK documentation](/client/jsOnDeviceEvaluationSDK) for how to check other entities like experiments and dynamic configs. {% /step %} {% step %} #### Logging an event ```javascript client.logEvent('fastly_gate_check', user, value.toString()); ``` The `logEvent` method takes two parameters: * `eventOrName : string | StatsigEvent` The name and details of the event you are logging. * `user : StatsigUser` The Statsig user object for whom the event is being logged. * `value : string` A value to associate with this event. For more information on event logging, refer to [Logging an event](/client/jsOnDeviceEvaluationSDK#logging-an-event). {% /step %} {% step %} #### Flushing events ```javascript event.waitUntil(client.flush()); ``` This flushes all events from the SDK to Statsig. Without this call, diagnostic information and logged event data don't appear in the Statsig Console. {% /step %} {% /steps %} #### Putting it all together ```javascript import { StatsigFastlyClient } from "@statsig/serverless-client/fastly"; addEventListener("fetch", (event) => event.respondWith(handleRequest(event))); async function handleRequest(event) { const client = new StatsigFastlyClient("client-LhxVWHSeZt2uor********"); const initResult = await client.initialzeFromFastly( "kv", "7b12fnfm7po7*********", "statsig-3htllY8X**********", "7NaRxS6RMGE-DTp*******" ); const user = { userID: Math.random().toString().substring(2, 5) }; const value = client.checkGate("pass_gate", user); client.logEvent("fastly_gate_check", user, value.toString()); event.waitUntil(client.flush()); return new Response(JSON.stringify({ kv, user }), { status: 200, headers: { "Content-Type": "application/json" }, }); } ``` ## Other considerations ### Polling for updates v5.13.0+ The SDK can't poll for updates across requests because Fastly doesn't allow timers. To optimize for edge use cases, there is no API to detect updates to your config specs. When you make a change to your project definition in the Statsig console, Statsig propagates the changes to the KV/Config store, and they're reflected the next time you initialize the StatsigFastly client. ### Flushing events v4.16.0+ The SDK enqueues logged events and flushes them in batches. To ensure events are properly flushed, call flush using `event.waitUntil()`. This keeps the request handler alive until events are flushed without blocking the response. ``` event.waitUntil(statsig.flush()); ``` {% /accordion %} To check evaluations, go to the gate you created for this example and view the evaluations in the Diagnostics tab. To check logged events, in the **Statsig Console**, go to **Data** -> **Events**. ### Size limits Fastly Config Store has maximum size limits that may prevent Statsig from pushing configs into Fastly. Go to [the Fastly documentation](https://docs.fastly.com/products/edge-data-storage) for the latest Config Store limits. If your payload continues to grow, you'll need to set the option to filter the payload by a Target App in the integration settings. ### Unsupported features Statsig doesn't currently sync ID Lists into Fastly KVs or Config Stores. If you rely on large (>1000) ID lists, you can't check them in your Fastly compute services. ================================================================================ # Akamai Edge KV URL: https://docs.statsig.com/integrations/akamai Updated: 2026-07-11 ================================================================================ ## How the Akamai Edge KV integration works Statsig’s Akamai Edge KV integration pushes Statsig Configs to Edge KV, providing low latency for gate and experiment evaluations directly in Akamai Edge KV. With the correct prerequisites in place, setup takes approximately 60 minutes. ## Prerequisites 1. An Akamai account with [EdgeWorkers added to your contract](https://techdocs.akamai.com/edgeworkers/docs/add-edgeworkers-to-contract) and a Statsig account 2. [The Akamai CLI](https://developer.akamai.com/getting-started/cli) ## Set up Akamai EdgeWorker 1. Create an [EdgeWorker ID](https://techdocs.akamai.com/edgeworkers/docs/create-an-edgeworker-id-1) 2. Add the [EdgeWorker Behavior](https://techdocs.akamai.com/edgeworkers/docs/add-the-edgeworker-behavior-1) 3. Install the [Akamai CLI](https://developer.akamai.com/getting-started/cli) 4. Install the [Edgeworkers CLI](https://techdocs.akamai.com/edgeworkers/docs/akamai-cli#edgeworkers-cli) 5. Generate [EdgeGrid credentials](https://techdocs.akamai.com/developer/docs/edgegrid) ## Configure integration First, enable the Akamai integration in the Statsig Console. Navigate to *Project Settings > Integrations* at https://console.statsig.com/integrations and select **Akamai**. ### Authentication Enter the following EdgeGrid credentials from the previous section: {% figure %} ![Authentication](/images/integrations/akamai/171a699c-2b78-435a-95c5-24b978738966.png) {% /figure %} ### Configuration Finalize the configuration by selecting a [namespace](https://techdocs.akamai.com/edgekv/docs/manage-access-to-edgekv) and [environment](https://techdocs.akamai.com/edgekv/docs/sandbox-support-edgekv). You can also filter the configs synced into your KV namespace by a [Target App](/sdks/target-apps). Enable this option if your config payload grows. Leave this unchecked for now. {% figure %} ![Configuration](/images/integrations/akamai/a2d63315-ab58-496d-901b-10aa6790979f.png) {% /figure %} After filling this out, click **Enable**. Within a minute, the Statsig backend generates a config payload from your project and pushes it into your KV namespace. Navigate to **KV Pairs** under your KV namespace. You should see an entry with the prefix `statsig-`. The next segment is your project ID. Copy it; you'll need it later. ## Add Statsig SDK to worker 1. Create a Statsig [server secret key](/access-management/api-keys#server-secret-keys) and [configure it on Akamai](https://techdocs.akamai.com/developer/docs/edgegrid#credentials-as-environment-variables) 2. Clone the [Statsig Akamai Edge KV](https://github.com/statsig-io/akamai-statsig-example?tab=readme-ov-file#getting-started) repo (or if you already have an Edge KV package, apply these changes to it) 3. Follow instructions in the repo ## Related resources [Akamai EdgeWorkers](https://developer.akamai.com/akamai-edgeworkers-overview) [Statsig Akamai Edge KV Repo](https://github.com/statsig-io/akamai-statsig-example?tab=readme-ov-file#getting-started) ================================================================================ # Github Code References URL: https://docs.statsig.com/integrations/github_code_references Updated: 2026-07-11 ================================================================================ {% callout type="warning" %} ### Deprecated: PAT-based integration Statsig has deprecated the Personal Access Token (PAT) based integration in favor of the new GitHub App-based integration. The new integration provides improved security and functionality, including AI-powered features like Knowledge Graph. Migrate to the [GitHub AI Integration](/integrations/github-ai-integration) for the best experience. {% /callout %} ## How the GitHub integration works The Statsig GitHub Integration lets you find [Feature Gate](/feature-flags/overview) and [Dynamic Config](/dynamic-config/overview) references within your codebase on the Statsig Console. The integration uses the GitHub API to access only Feature Gate or Dynamic Config references and doesn't store any sensitive information. ## Configure GitHub code references Create a new GitHub developer token, either Classic or Fine Grained, with at least read access to the organization or repositories where you use Statsig. Find this in GitHub under *Settings > Developer Settings > Personal Access Token*. Then, log in to the Statsig Console and navigate to the GitHub Code References integration on the Integrations page. Find this in *Project Settings > Integrations Tab > Github Code References*. The integration provides instructions on how to enable GitHub Code References. {% figure %} ![GitHub integration configuration interface](/images/github_code_references/github_integration.png) {% /figure %} After you enter your token and organization name, Statsig verifies it can access repositories and notifies you of any problems. Navigate to the Feature Gates or Dynamic Configs pages on the Statsig Console and select a gate or dynamic config. Under the *Diagnostics* tab, select the **View Code References** link to see your code references. {% figure %} ![Feature gate diagnostics tab showing code references link](/images/github_code_references/feature_gate_view.png) {% /figure %} Code References appear based on the feature gate or dynamic config page you are on. You can filter Code References by repository and file extension. {% figure %} ![Code references panel listing repositories and files](/images/github_code_references/code_references.png) {% /figure %} ### Github code references action A [GitHub Action](https://github.com/statsig-io/github-code-references) is also available. It can scan your repositories for gates and dynamic configs, then create a PR replacing [Stale Gates](/feature-flags/permanent-and-stale-gates). ================================================================================ # GitLab Code References URL: https://docs.statsig.com/integrations/gitlab_code_references Updated: 2026-07-11 ================================================================================ ## How the GitLab integration works The Statsig GitLab Integration lets you find [Feature Gate](/feature-flags/overview) and [Dynamic Config](/dynamic-config/overview) references within your codebase on the Statsig Console. The integration uses the GitLab API to access only Feature Gate or Dynamic Config references and doesn't store any sensitive information. ## Configure GitLab code references Create a new GitLab access token, either [project](https://docs.gitlab.com/user/project/settings/project_access_tokens/) or [personal](https://docs.gitlab.com/user/profile/personal_access_tokens/), with the `read_api` scope. Then, log in to the Statsig Console and navigate to the GitLab Code References integration on the Integrations page. Find this in *Project Settings > Integrations Tab > GitLab Code References*. The integration provides instructions on how to enable GitLab Code References. {% figure %} ![GitLab integration configuration interface](/images/gitlab_code_references/gitlab_integration.png) {% /figure %} After you enter your token and organization name, Statsig verifies it can access repositories and notifies you of any problems. Navigate to the Feature Gates or Dynamic Configs pages on the Statsig Console and select a gate or dynamic config. Under the *Diagnostics* tab, select the **View Code References** link to see your code references. {% figure %} ![Feature gate diagnostics page with GitLab code references link](/images/gitlab_code_references/feature_gate_view.png) {% /figure %} Code References appear based on the feature gate or dynamic config page you are on. You can filter Code References by repository and file extension. {% figure %} ![GitLab code references table listing files and repositories](/images/gitlab_code_references/code_references.png) {% /figure %} ================================================================================ # Slack Notifications URL: https://docs.statsig.com/integrations/slack Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Depending on your organization’s Slack settings, you may need help from a Slack Admin to complete setup. {% /callout %} --- The Statsig Slack integration connects your experimentation platform with your team's Slack workspace. After you enable this integration, you receive real-time updates and alerts about changes to your feature gates, experiments, and dynamic configs without checking the Statsig console or the operational health [status page](https://status.statsig.com). ## Set up the Slack integration **Step 1:** In your Statsig Console, go to **Statsig Settings** -> **[Integrations](https://console.statsig.com/integrations)**. **Step 2:** Select Slack and click **+ Add Connection**. **Step 3:** Allow the Statsig app to connect to your Slack workspace and the Slack channel where you want to receive Statsig notifications. --- ## Product notifications **Step 4:** Filter to the relevant Team, Tags, or Target Apps. **Step 5:** Choose the notifications about product-level activities and status changes you want to subscribe to. {% figure %} ![Slack Product Notification Events](/images/slack-team-notifs.png) {% /figure %} --- ## General notifications **Step 6:** Choose the notifications about Statsig system status or administrative updates you want to subscribe to. {% figure %} ![Slack General Notifications - Notifications about account health and administrative updates](/images/slack-general-notifs.png) {% /figure %} --- ## Personal notifications **Step 7:** Go to **Settings** -> **[My Account](https://console.statsig.com/account_settings)** and navigate to the Notifications section. You can also manage your email notification preferences here. After you enable personal notifications, Statsig sends you updates when you're tagged in discussions, invited to review a change, or when a feature gate, experiment, or dynamic config you follow changes. To follow a dynamic config, open it in the console and select **Follow**. For more detail, refer to [Follow a dynamic config](/dynamic-config/working-with#follow-a-dynamic-config). {% figure %} ![Personal notification preferences interface](/images/slack-personal-notifs.png) {% /figure %} ================================================================================ # Jira URL: https://docs.statsig.com/integrations/jira Updated: 2026-07-11 ================================================================================ ## How the Statsig for Jira app works The Statsig for Jira app lets you bring insights from your Statsig [Feature Gates](/feature-flags/overview) into your Jira project. Associate Statsig feature flags with your Jira issues to track rollout status and A/B test results directly in Jira. {% figure %} ![Jira issue view showing Statsig feature gate rollout widget](/images/integrations/jira/130303182-c450027d-1a5d-4691-83bc-d3df59dbabb4.gif) {% /figure %} ## Configure the Statsig for Jira app Get a [Server Secret Key from the Statsig Console](https://console.statsig.com/api_keys), then go to the [Atlassian Marketplace](https://marketplace.atlassian.com/apps/1225708/statsig-for-jira?hosting=cloud\&tab=overview) to install the Statsig app for Jira. To configure the app, go to *Apps > Statsig > Allow Access*, then enter your API key: {% figure %} ![Jira app configuration interface](/images/integrations/jira/185266500-4695bad9-b219-4b51-8e88-cc6059799d50.png) {% /figure %} ================================================================================ # A/B Test Email Campaigns URL: https://docs.statsig.com/guides/email-campaign-test Updated: 2026-07-11 ================================================================================ A/B testing an email campaign and measuring experiment results on downstream product metrics is a common use case for Statsig customers. Email marketing tools often have native A/B testing capabilities but are limited to measuring email open rates or link click rates. These patterns work across most email marketing tools, including Braze, [Customer.io](/guides/customer-io-email-abtest/), [Sendgrid](/guides/sendgrid-email-abtest), and Salesforce Marketing Cloud. Two common patterns used on Statsig are: #### 1. Do assignments in the marketing automation tool, do analysis in Statsig (requires Statsig WHN) Use case: The marketing automation tool only measures email open and click-through rates, but you need to measure impact on product usage. Solution: Use the email automation tool to bucket users into Control or Test groups. Write these assignments to a table in your warehouse. Statsig Warehouse Native uses this table as the assignment list for the experiment and analyzes experiment impact using the full catalog of business and product metrics configured in Statsig. Go to [this guide](/statsig-warehouse-native/guides/email-experiments) for more best practices with email experimentation using Warehouse Native. #### 2. Do assignments and analysis using Statsig Use case: You want email campaigns to be coordinated with in-app messaging or promotions. Solution: Call the Statsig SDK to assign each user into Control or Test groups and use that assignment in the email marketing system. For example, Salesforce Marketing Cloud lets you create a custom extension for this. You can perform a bulk evaluation for a list of email addresses selected for a campaign using a Python script and the Statsig Python SDK, or call the SDK one at a time. This works with both Statsig Warehouse Native and Statsig Cloud. When using a Python script to call the SDK, call either `flush()` or `shutdown()` before exiting the script so Statsig logs the exposures. Verify that exposures flushed on the *Diagnostics* page of your experiment. ================================================================================ # Guide to Contentful URL: https://docs.statsig.com/guides/contentful Updated: 2026-07-11 ================================================================================ The Statsig Contentful integration lets you create A/B/n tests and test different content blocks against each other directly from within Contentful. You can assess impact using business metrics on Statsig Cloud or Warehouse Native. Marketers can optimize content, obtain insights, and iterate continuously right from within Contentful. - Run experiments on CMS content without engineering involvement - Configure content to serve with each variation - No performance penalty or flicker The Statsig Contentful app adds a Statsig container that connects to an experiment in Statsig. Users can then add Content Blocks to that container to start a test. The Statsig Contentful app lets marketers measure progress towards business objectives by testing content for lift in any core business metrics configured in Statsig. ## Integrating with Contentful The Contentful Marketplace App is publicly available at the [Statsig listing](https://www.contentful.com/marketplace/statsig). To use this integration, complete a one-time setup for the Contentful marketplace app, your Content types, and your codebase. After setup, you can run A/B/n tests directly inside Contentful. ### Setting up the Statsig Marketplace App - Navigate to the Marketplace in Contentful, and find the Statsig app. Click 'Install'. - Statsig prompts you to enter a Console API Key. You can find an existing Console API Key in your Statsig project under Settings > Keys & Environments. The key must be **of type 'Console' and have read and write permissions**. Generate a new key of type 'Console' if a suitable one doesn't already exist for your project. {% figure %} ![image.png](/images/guides/contentful/80a564ad-22db-45aa-8caa-246512aad0ee.png) {% /figure %} - After entering your API Key, select 'Install to selected environments'. The app is now configured. Returning to this page later shows only the _obfuscated_ API Key. ### Setting up Statsig Variant Container After configuration, Contentful adds a new Content model called 'Statsig variant container' to your space. Verify the setup is correct: - Navigate to the 'Content model' tab in Contentful, and select the 'Statsig variant container'. {% figure %} ![image.png](/images/guides/contentful/6010f051-2f05-462f-ace1-9f3194f73941.png) {% /figure %} - You should see a list of 4 fields: Statsig Experiment Id, Entry Name, Default Variation (control), Treatment Variations. {% figure %} ![image.png](/images/guides/contentful/486955a9-f31c-4870-8369-df956606bfb3.png) {% /figure %} - If your 'Statsig Experiment Id' field shows `Excluded from api response`, update this field so API calls return it. Click the three dots on the right of the field and select 'Include in API response', then select 'Save'. {% figure %} ![image.png](/images/guides/contentful/ae81d2b5-7f3d-4aaa-bb7e-316ba28898fe.png) {% /figure %} The 'Statsig variant container' is now configured and ready to associate with other Content types. ### Setting up Experiments in Content Types You can configure your existing content types to run Statsig experiments, automatically serving different variants to your users. The steps below show how to add a 'Statsig experiment' field to your target content type. - Navigate to the 'Content model' tab in Contentful, and select your target content type (in this example, `page - Blog post`). You should see the list of fields for this content type: {% figure %} ![image.png](/images/guides/contentful/39c0ba10-1ba3-49a3-a106-bad366ba8e6a.png) {% /figure %} - Click 'Add field', and choose 'Reference'. Enter `Statsig experiment` for the Name, then click 'Add and Configure'. {% figure %} ![image.png](/images/guides/contentful/c077a3e3-797c-4600-9565-d8202f86db93.png) {% /figure %} - Under 'Validation', select 'Accept only specified entry type', and choose 'Statsig variant container' from the dropdown. {% figure %} ![image.png](/images/guides/contentful/b537b900-92ee-4e8a-ab47-75f4f4b9af46.png) {% /figure %} - Confirm your new field and save your content type. Your content type is now configured to use Statsig Experiments. Repeat this process for any other content types you want to run experiments with. ### Running an Experiment on your Content To run an experiment on your content, you can link a Statsig Experiment to it. Here's how: - Navigate to the 'Content' tab in Contentful, and select your existing entry from the list. At the bottom of the Editor tab, you should now see an editable field for 'Statsig experiment': {% figure %} ![image.png](/images/guides/contentful/63042ccf-382b-4e04-b23d-8c6bd8eb9cf1.png) {% /figure %} - Click on 'Add content', and select 'Statsig variant container' from the New content dropdown. You should see a new Statsig variant container layover: {% figure %} ![image.png](/images/guides/contentful/77688f35-b775-4c84-885d-67ee111d67e4.png) {% /figure %} - Under the Statsig tab, enter the name of your experiment in the 'Entry Name' field. Add your control and treatment variations. In this example, add `component - Rich image` variations. Experiment names must not include special characters. - When your experiment setup is finalized, hit 'Publish' on the new Statsig variant container entry. {% callout type="note" %} Ensure your experiment setup is finalized before publishing, as publishing creates your experiment in Statsig. {% /callout %} {% figure %} ![image.png](/images/guides/contentful/872643fb-9782-4728-96ca-362375323cfa.png) {% /figure %} - A prompt appears to start your newly created experiment in Statsig. Follow the 'Go to Statsig Experiment' link to finalize setup, add metrics, and start your experiment. - After the experiment starts in Statsig, a green banner appears at the top of your Statsig variant container and the variation fields become read-only. - Return to your original entry and select 'Publish changes'. Your experiment is now live! ### Integrating Statsig experiments in your codebase An [example repository](https://github.com/statsig-io/contentful-blog-webapp-nextjs-example/tree/main) shows how to integrate Statsig experiments created from Contentful into your codebase. The `README` walks through the setup process, including pulling experiment fields from Contentful, calling a Statsig SDK, and matching assigned users to their respective variant. ### Troubleshoot common problems #### I created an experiment and the 'Go to Experiment' button doesn't show up - what happened? Statsig didn't save the Experiment Id correctly. To fix this, navigate into 'Editor' and manually add the Statsig Experiment Id. After saving, the button populates as expected. ![Contentful Statsig Experiment Id](/images/contentful_statsig_experiment_id.png) ================================================================================ # Email AB Testing with Customer.io URL: https://docs.statsig.com/guides/customer-io-email-abtest Updated: 2026-07-11 ================================================================================ Email campaigns are a critical tool for any marketing team. Finding the best-performing email template is an ideal use case for an A/B test. Statsig lets you run A/B tests on different parts of your email content. Because Statsig integrates with product analytics, you can run email experiments and measure business-level impact on product metrics. {% callout type="info" %} This guide assumes you have an existing Statsig account. Go here to create a new free account if you don't already have one: https://statsig.com/signup {% /callout %} ### Step 1: Create an experiment Create a new Experiment in the Statsig console. Enter a name and leave the remaining fields at their default values. {% figure %} ![Experiment creation interface](/images/guides/sendgrid-email-abtest/210731384-8bbfc1e1-076c-4ae3-959a-ef1507801e71.png) {% /figure %} ### Step 2: Start the experiment An experiment requires at least one parameter before it can start. Add a placeholder parameter now. {% figure %} ![Experiment parameter configuration](/images/guides/sendgrid-email-abtest/210316935-99f13616-c412-47c1-b9ab-99b90462805e.png) {% /figure %} {% figure %} ![Experiment setup completion](/images/guides/sendgrid-email-abtest/210731687-0883c356-5cef-458d-a6aa-bfde9ad36f8b.png) {% /figure %} Save the experiment setup and select **Start**. {% figure %} ![Experiment start confirmation](/images/guides/sendgrid-email-abtest/210731969-decf481a-b6a2-41ee-b1c1-6b917bb18fab.png) {% /figure %} Copy the **Experiment Name**. You need it in the next step. {% figure %} ![Experiment name copy interface](/images/guides/sendgrid-email-abtest/210732298-34c0e1f4-6485-425b-af07-fa535ad86396.png) {% /figure %} ### Step 3: Set up Exposure Webhooks In your Customer.io campaign, create a **Random Cohort Branch** with a flow similar to the following: {% figure %} ![Customer.io random cohort branch setup](/images/guides/customer-io-email-abtest/211873572-0966ae02-4a41-40a1-b55d-19378f1d0b98.png) {% /figure %} In the Webhook actions, add a POST request similar to the following, with your API key and experiment name filled in: {% figure %} ![Webhook configuration for experiment exposure](/images/guides/customer-io-email-abtest/211873593-d48ed161-2a55-4099-8f06-7835ee82d011.png) {% /figure %} Pass in any other custom IDs and user attributes inside the post body. For each webhook, expose the correct group to attribute the branch to. The webhook shown above exposes the "Control" group. {% figure %} ![Experiment group exposure configuration](/images/guides/customer-io-email-abtest/211873585-1b2da267-cd72-4c29-acde-d1888500ac36.png) {% /figure %} In Statsig, you'll now have exposures for each of your experiment groups. ## Holdouts To use Statsig Holdouts with Customer.io, identify users who are part of a holdout using Customer.io's identify function: https://customer.io/docs/sdk/ios/identify/ When you call Customer.io's identify method, check a Statsig holdout gate and add an attribute to the user to mark them as being in a holdout. In your campaign, create a True/False branch to check whether a user is in the holdout. {% figure %} ![Customer.io holdout branch configuration](/images/guides/customer-io-email-abtest/211888584-a164d8bf-0d78-4a6b-86ab-ea336fd2bc3a.png) {% /figure %} ================================================================================ # Email AB Testing with SendGrid URL: https://docs.statsig.com/guides/sendgrid-email-abtest Updated: 2026-07-11 ================================================================================ Email campaigns are a critical tool for any marketing team. Finding the best-performing email template is a good use case for an A/B test. Statsig lets you run simple but powerful A/B tests on different parts of your email content. Because Statsig integrates with product analytics, you can run email experiments and measure deeper business-level impact. {% callout type="info" %} This guide assumes you have an existing Statsig account. Go here to create a new free account if you don't already have one: https://statsig.com/signup {% /callout %} ## Step 1: Create an experiment Start by creating a new experiment on the Statsig console. Enter a name and leave the remaining fields at their defaults. {% figure %} ![Experiment creation interface](/images/guides/sendgrid-email-abtest/210731384-8bbfc1e1-076c-4ae3-959a-ef1507801e71.png) {% /figure %} ## Step 2: Start the experiment Because you can't start an experiment without a parameter, add a placeholder parameter. {% figure %} ![Experiment parameter configuration](/images/guides/sendgrid-email-abtest/210316935-99f13616-c412-47c1-b9ab-99b90462805e.png) {% /figure %} {% figure %} ![Experiment setup completion](/images/guides/sendgrid-email-abtest/210731687-0883c356-5cef-458d-a6aa-bfde9ad36f8b.png) {% /figure %} Save the experiment setup and select **Start**. {% figure %} ![Experiment start confirmation](/images/guides/sendgrid-email-abtest/210731969-decf481a-b6a2-41ee-b1c1-6b917bb18fab.png) {% /figure %} Copy the **Experiment Name**. You'll need it in the next step. {% figure %} ![Experiment name copy interface](/images/guides/sendgrid-email-abtest/210732298-34c0e1f4-6485-425b-af07-fa535ad86396.png) {% /figure %} ## Step 3: Set up webhook In your SendGrid console, go to **Settings** > **Mail Settings** > **Event Webhook**. In the HTTP Post URL, enter: `https://sendgrid-webhook.statsig.workers.dev/?apikey=[YOUR STATSIG API KEY]` {% callout type="info" %} You can find your API key by going to Statsig Project Settings > API Keys and copying the Client API Key. {% figure %} ![Statsig API key location](/images/guides/sendgrid-email-abtest/210319717-49ac4a21-8bdd-4348-b534-374b760ab047.png) {% /figure %} It should look like this: `client-abcd123efg...` {% /callout %} Make sure all the **Deliverability Data** and **Engagement Data** checkboxes are selected. Enable the **Event Webhook Status** and select Save. The setup should look like this: {% figure %} ![SendGrid webhook configuration](/images/guides/sendgrid-email-abtest/210737123-86725ff7-2432-4bde-95e6-173f8f194f86.png) {% /figure %} ## Step 4: Create single sends In your SendGrid app, create two new **Single Send** campaigns and name them using the experiment name. The first is the Control, which is the baseline. Name it `[experiment_name]/control`. For example: `drip_campaign_ab_test/control`. {% figure %} ![Control email template setup](/images/guides/sendgrid-email-abtest/210733152-5766320a-ed30-45f7-b57f-bf0526f796a8.png) {% /figure %} The second is the Test, which is the template you are comparing with the baseline. Name it `[experiment_name]/test`. For example: `drip_campaign_ab_test/test`. {% figure %} ![Test email template setup](/images/guides/sendgrid-email-abtest/210733504-581ea181-3ebc-4bc1-a707-391866bcbf62.png) {% /figure %} You can customize these templates however you want, and even use different subjects. Split the recipient list evenly between the Control and Test sends to keep the experiment balanced. {% callout type="info" %} To avoid bias, split the recipient list at random. Ensure recipients from the same company are distributed evenly between the two lists. {% /callout %} Send the emails. Statsig will automatically track how each variant performs across email opens, clicks, and other engagement metrics. ## Monitoring the setup After starting the sends, verify that everything is working by going to the Diagnostics tab in your experiment and reviewing the Exposure Stream at the bottom of the page. The Exposure Stream shows a real-time feed of page loads and the variant each user was allocated. {% figure %} ![Experiment exposure stream in diagnostics tab](/images/guides/sendgrid-email-abtest/210737715-6adf5fd9-3b23-433a-a852-f2c8ea93cf2a.png) {% /figure %} ## Interpreting results Go to the **Pulse Results** tab in the Experiment page to add metrics and determine which variant is performing better. Refer to [Reading Pulse Results](/experiments/interpreting-results/read-results) for details. {% figure %} ![Pulse results tab showing experiment metrics](/images/guides/sendgrid-email-abtest/210737928-3f33ca3b-a46b-40ac-b095-0b33fab66109.png) {% /figure %} ## Using API instead of single send Statsig also supports A/B testing when using the API or automation to send marketing emails. To enable this, use [unique arguments](https://docs.sendgrid.com/for-developers/sending-email/unique-arguments) and pass `unique_args` as shown below: ```json { "unique_args": { "statsig_experiment_name": "[Experiment Name]", "statsig_variant_name": "[control or test]" } } ``` For the example above, set up the Control variant like this: ```json { "unique_args": { "statsig_experiment_name": "drip_campaign_ab_test", "statsig_variant_name": "control" } } ``` And the Test variant would look like this: ```json { "unique_args": { "statsig_experiment_name": "drip_campaign_ab_test", "statsig_variant_name": "control" } } ``` ## Running more than two variants You can extend this setup to run ABC or ABn tests by adding more variants in the Experiment Setup tab. Make sure the variant name is applied correctly in either the Single Send name or the unique arguments in the API. {% figure %} ![ABC test variant configuration interface](/images/guides/sendgrid-email-abtest/210738925-89f91116-fefc-4e3c-992d-c9fc228f1a8c.png) {% /figure %} ================================================================================ # Guide to General CMS Integrations URL: https://docs.statsig.com/guides/cms-integrations Updated: 2026-07-11 ================================================================================ ## Using Statsig with a CMS A common question is how to use Statsig with an existing CMS. Statsig also offers a no-code solution, [Sidecar](/guides/sidecar-experiments/introduction), but you can set up your code to integrate your CMS and Statsig so you write code once and then run experiments on arbitrary combinations of parameters in the future. Statsig recommends using [Layers](/experiments/layers-overview) to wire this up. Read the Layers documentation before continuing. {% callout type="info" %} Layers are a unit of mutual exclusion between experiments in Statsig. Every user participates in only one experiment in a layer at any given time. As such, Statsig recommends you set up a layer for each surface you're experimenting on with the help of your CMS {% /callout %} The remainder of this guide assumes you are experimenting on a single surface. Repeat these steps for each additional surface, such as a landing page, product page, or blog. As an example, this guide parameterizes the Statsig landing page to connect to a CMS. {% figure %} ![Statsig landing page diagram showing CMS-provided sections](/images/guides/cms-integrations/187345904-90ade71e-adc7-4205-85c7-751633a864da.png) {% /figure %} First, create a layer. Navigate to "Experiments" in the left-hand column, and then "Layers" in the title bar: {% figure %} ![Layers list and create button in Statsig experiments UI](/images/guides/cms-integrations/187341751-7c5861a7-90cd-494d-b84d-75c9ea54f923.png) {% /figure %} Create a layer for all content or parameters to experiment with on the landing page, named "statsig\_landing\_page". {% figure %} ![Create layer dialog naming statsig_landing_page](/images/guides/cms-integrations/187346608-3314ca52-4c99-4442-b056-7081369d2b8f.png) {% /figure %} Next, create parameters for the title, subtitle, and primary CTA. Set each parameter's value to the actual ID of the content in the CMS. {% figure %} ![Layer parameter creation form for CMS content IDs](/images/guides/cms-integrations/187346653-1be62abe-e6b9-4ed6-9bf6-b95e7b04e040.png) {% /figure %} It should look like this when you are done: {% figure %} ![Layer parameters table listing title, subtitle, and CTA defaults](/images/guides/cms-integrations/187346571-85013f61-6ca6-42f5-9c41-b4631931f93e.png) {% /figure %} Each layer parameter has a default value. If the user isn't in any experiments in that layer, the parameter returns that default value, which maps to content in the CMS. In code, your integration looks something like this: ```js const landingPageCmsIds = statsig.getLayer("statsig_landing_page"); const titleID = landingPageCmsIds.get("title", "<default_cms_title_id>"); // note that you have a default value in code as well // exact library and function call will map to your cms client library cmsClient.getEntry(titleID); ``` Repeat this for the subtitle, CTA, and all other parameters on your landing page to make them all dynamic. After you update a CMS ID in Statsig, your code loads the updated content for that section. To test a new variant, create new content in your CMS, then return to your layer and select "Create Experiment in Layer": {% figure %} ![Create experiment in layer button location](/images/guides/cms-integrations/187347191-ac9a27bb-3ea3-4338-8680-c94b6d1e753b.png) {% /figure %} Complete the experiment setup form: {% figure %} ![Layer experiment setup form with control and test weights](/images/guides/cms-integrations/187347290-fc3ebc84-1cf0-4f3a-8256-d6cc5a4b21b3.png) {% /figure %} Statsig creates the experiment. Now set up the test and control groups and specify which content each group uses for each parameter. In the Groups and parameters section, select "Add Parameter" and choose one of your existing parameters, such as title: {% figure %} ![Groups and parameters section showing Add Parameter menu](/images/guides/cms-integrations/187347358-f336195a-0fa5-42d0-9896-ea0e0cf85875.png) {% /figure %} {% figure %} ![Parameter value editor selecting CMS ID per group](/images/guides/cms-integrations/187347464-e4a2dbe6-bcb8-48f1-b807-339f59b6e0b2.png) {% /figure %} Update the parameter value to the ID of the new title: {% figure %} ![Updated parameter table showing new CMS content IDs per variation](/images/guides/cms-integrations/187347517-77ff4cfb-8f52-4db3-9dd1-92e453076b01.png) {% /figure %} Add more groups or parameters as needed. After you validate the experience in all groups, start your experiment. You don't need to change any code for parameters you already created. Statsig pulls the updated ID and your code loads the updated content automatically. The experimentation flow is the same as all other Statsig experiments. The parameter value maps to your CMS content. For more information about setting up and running experiments, go to [Experiments](/experiments/overview). ================================================================================ # A/B Testing on Shopify URL: https://docs.statsig.com/guides/shopify-ab-test Updated: 2026-07-11 ================================================================================ ## Use cases & considerations Shopify provides solutions for commerce businesses to build and manage all aspects of their online storefront, including product catalogue, inventory, site content, marketing, and user experience. For experimenting with the more static aspects of the store experience (static landing pages, visual aspects), use [Statsig Sidecar](/guides/sidecar-experiments/introduction) to both build your test treatments and to assign users to experiments when they land on your site, all without writing any code. To experiment on the more dynamic aspects of your online store (such as product catalogue or search capabilities), use [Shopify Headless Commerce](https://www.shopify.com/plus/solutions/headless-commerce) and integrate Statsig [SDKs](/sdks/getting-started) to get full control for experimenting within business logic. ## Using traditional Shopify + Sidecar for no-code testing The traditional Shopify service is a fully-managed platform for businesses that provides both a backend administration tool for managing your product catalogue & site content, and powers the storefront experience for your shoppers. While Statsig doesn't have an integration in the Shopify App Store, you can integrate Sidecar to run simple UX experiments on the storefront. The steps below walk through setting up Sidecar within the traditional Shopify stack. ### Install Sidecar Chrome extension [Follow this guide](/guides/sidecar-experiments/setup) on installing the Sidecar Chrome extension. This lightweight Chrome extension lets non-technical users build experiments and treatments. You can specify where the test runs based on URL, then configure treatments such as content changes, style changes, image swaps, or injected JavaScript for cases where the visual editor can't accommodate the requirement. ### Add Sidecar JS to your storefront's page source * Log in to your Shopify dashboard * Click on **Online Store**, then Themes * Locate and click the more menu \[...], find the **Edit Code** option {% figure %} ![statsig product overview](/images/shopify-customize-1.png) {% /figure %} * [Locate your Sidecar script tag](/guides/sidecar-experiments/publishing-experiments#step-2-add-script-code) and copy the script tag to your clipboard * Navigate to the `theme.liquid` file in your Shopify theme editor * Paste the Sidecar script tag toward the top of the page `<head>` as shown below. {% figure %} ![statsig product overview](/images/shopify-customize-2.png) {% /figure %} * Save your `theme.liquid` file. * Sidecar is now installed across your entire website. ### Configure event tracking Shopify's [Custom Pixel framework](https://help.shopify.com/en/manual/promoting-marketing/pixels/custom-pixels) is ideal for tracking customer events to Statsig. The custom pixel framework offers a [wide set of events](https://shopify.dev/docs/api/web-pixels-api/standard-events) you can subscribe to, and namely, the ability to perform tracking during the checkout experience. Code deployed outside the scope of a custom pixel won't fire during the checkout experience, as documented in the [Shopify pixel sandbox documentation](https://help.shopify.com/en/manual/promoting-marketing/pixels/overview#pixels-sandbox-environment). {% figure %} ![configure shopify pixel](/images/shopify-pixel.png) {% /figure %} Below is boilerplate custom pixel code that provides a function to send events back to Statsig. You should subscribe to the various events and event metadata necessary for your experimentation practices. This [sample GTM pixel](https://help.shopify.com/en/manual/promoting-marketing/pixels/custom-pixels/gtm-tutorial) shows some of the common events and metadata that you can capture and track to Statsig. ```js const getStableID = () => { // New gen JS-SDK stores in dynamic keyed localstorage entries for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key && key.includes('statsig.stable_id.')) { const value = localStorage.getItem(key); return value.replace(/"/gi, ''); } } // Old gen JS-SDK stores in specific localStorage key const fallback = localStorage.getItem('STATSIG_LOCAL_STORAGE_STABLE_ID'); if (fallback) { return fallback.replace(/"/gi, ''); } }; /** * Util function for tracking events back to statsig */ const statsigEvent = async (eventKey, eventValue = null, metadata = {}, userObject = {}) => { Object.assign(userObject, { customIDs: {stableID: stableID} // attach stableID automatically }); await fetch('https://events.statsigapi.net/v1/log_event', { method: 'POST', headers: { 'Content-Type': 'application/json', 'statsig-api-key': 'client-STATSIG_CLIENT_KEY' }, body: JSON.stringify({ "events": [{"user": userObject, "eventName": eventKey, "metadata": metadata}] }) }); } analytics.subscribe("checkout_completed", event => { statsigEvent('checkout', null, { orderId: event.data?.checkout?.order?.id, currency: event.data?.checkout?.currencyCode, subtotal: event.data?.checkout?.subtotalPrice?.amount, shipping: event.data?.checkout?.shippingLine?.price?.amount, value: event.data?.checkout?.totalPrice?.amount, tax: event.data?.checkout?.totalTax?.amount, }); }); analytics.subscribe("product_viewed", (event) => { statsigEvent('product_viewed', null, { product_title: event.data?.productVariant?.title, }); }); ``` For tracking behaviors in the main storefront experience, you can also use the following: * [Autocapture](/guides/sidecar-experiments/measuring-experiments#using-autocapture): loads by default with Sidecar and automatically collects various user behaviors. * [Custom event logging](/guides/sidecar-experiments/measuring-experiments#using-the-tracking-api): tracks behaviors using code. ## Using Shopify Headless + Statsig SDKs for deeper experimentation Using [Shopify Headless](https://shopify.dev/docs/storefronts/headless) gives you full control over customizing your storefront by decoupling the Shopify admin backend and the storefront application. This means that Shopify effectively serves as a data store, providing APIs to fetch & serve products, content, and manage the entire shopping experience using code. Whether you are using Shopify's [Hydrogen app](https://shopify.dev/docs/storefronts/headless/hydrogen/fundamentals) and its frameworks or a [custom headless stack](https://shopify.dev/docs/storefronts/headless/bring-your-own-stack), you can integrate Statsig's SDK to assign users to experiments. Integrating Statsig in this architecture follows a similar pattern to [integrating with headless CMS platforms](/guides/cms-integrations). ### Integrating data sources for experiment metrics Along with tracking click stream and point-of-sale behavior (described in [Configure event tracking](/guides/shopify-ab-test/#configure-event-tracking)), commerce businesses performing deeper experimentation often want to integrate offline data systems and measure experiments using existing business metrics. The Data Warehouse is commonly the source of truth for user purchase data and other offline data. This lets customers define more [bespoke metrics](/statsig-warehouse-native/configuration/metrics#metric-types) using filtering, aggregations, and other warehouse datasets to segment experiment results. ================================================================================ # Integrating Statsig with Framer URL: https://docs.statsig.com/guides/framer-analytics Updated: 2026-07-11 ================================================================================ ## How Statsig integrates with Framer Framer lets you create interactive prototypes and websites. By integrating Statsig, you can capture user behavior and log custom events directly within your Framer projects. This guide covers integrating Statsig into your Framer project using the JavaScript SDK. ## Installation To start tracking user interactions in your Framer project, follow these steps: 1. Copy your web snippet from Statsig, replacing `YOUR_CLIENT_KEY` with a Client API Key from your Statsig project. Find your key at [console.statsig.com/api\_keys](https://console.statsig.com/api_keys). ```html <script src="https://cdn.jsdelivr.net/npm/@statsig/js-client@3/build/statsig-js-client+session-replay+web-analytics.min.js?apikey=[YOUR_CLIENT_KEY]"></script> ``` 2. In your Framer project, click the gear icon in the top right corner to open project settings. You need the "mini" site plan or above to enable custom code. 3. In the General tab, scroll down to the Custom Code section and paste your Statsig snippet at the end of the `<head>` tag section. 4. Save your changes and publish your site. Statsig tracks basic user interactions. ## Capture custom events To track custom events in your Framer project, use `window.statsig.logEvent()` inside a custom component. 1. Go to the Assets tab in your Framer project. 2. Click the plus icon next to Code and create a new code component. 3. Name the file `CaptureButton` and select New Component. 4. Replace the default code with the following: ```js export default function CaptureButton() { const handleClick = () => { window.statsig.logEvent("click", "button"); }; return ( <button id="capture-button" onClick={handleClick}> Click me </button> ); } ``` 5. Save your changes (Cmd/Ctrl + S). 6. Drag your new CaptureButton component from the Code tab onto your Framer page. 7. Publish your site and click the button. After a couple of minutes, the event appears in your Statsig metrics. ================================================================================ # A/B Testing with Webflow and Visual Editor URL: https://docs.statsig.com/guides/webflow-sidecar-ab-test Updated: 2026-07-11 ================================================================================ ## Use cases & considerations Webflow is a platform for businesses to design, build, and manage websites and their content without writing code. To experiment on a Webflow site, use [Statsig Visual Editor](/guides/sidecar-experiments/introduction) to build test treatments and assign users to experiments when they land on your site, without writing code. #### Install the Visual Editor Chrome extension Install the [Statsig Visual Editor Chrome extension](https://chromewebstore.google.com/detail/statsig-sidecar-v3/mmgjfcbidnlghegclgpkgegpdhbopjhn) and follow the [Visual Editor setup guide](/guides/sidecar-experiments/sidecar-v3#2-installing-the-chrome-extension-for-editing). The extension lets non-technical users build experiments and treatments directly on a live page. You can target by URL and configure text changes, style changes, image swaps, and custom JavaScript for advanced use cases. #### Add the Visual Editor script to your Webflow site 1. Log in to your Webflow dashboard and navigate to your project. 2. Access the **Custom Code** section in your project settings. 3. Copy the Visual Editor script tag from the [Visual Editor setup guide](/guides/sidecar-experiments/sidecar-v3#1-installing-the-sidecar-script) and replace `client-xxx` with your Client SDK key from [Settings > Keys & Environments](https://console.statsig.com/api_keys): `<script src="https://cdn.jsdelivr.net/npm/statsig-sidecar-v2-beta/dist/index.min.js?apikey=client-xxx"></script>` {% figure %} ![Webflow custom code interface](/images/guides/webflow-sidecar-ab-test/210325253-6d118adf-8758-48fb-9c99-fdee6ff34f06.png) {% /figure %} 4. Paste the script tag into the `<head>` section of your Webflow site's custom code area. 5. Save and publish your changes to apply the Visual Editor script across your website. 6. In Statsig Console, create an experiment and choose **Visual Editor** as the experiment type before launching the editor. #### Optional script parameters Add these query parameters to the script URL for additional client behavior controls: * `&reduceflicker=0` disables briefly hiding `<body>` while the client initializes. #### Configure event tracking and metrics Statsig provides several approaches for tracking events when using Visual Editor. You can use built-in [Autocapture](/webanalytics/autocapture/) tracking or set up custom event logging. For detailed guidance, go to [Visual Editor Setup & Usage](/guides/sidecar-experiments/sidecar-v3#metrics) and the [event tracking documentation](/guides/sidecar-experiments/measuring-experiments). #### Debugging and troubleshooting If you encounter issues during integration or experimentation: * Verify that the Visual Editor script tag is correctly placed in the `<head>` section of your Webflow site. * Confirm you're using the latest package URL: `statsig-sidecar-v2-beta`. * Check the browser's console for any JavaScript errors that may indicate problems with the script. * Ensure you created your experiment as a **Visual Editor** experiment and that it is active in Statsig Console. * Confirm the Visual Editor Chrome extension is installed. * Go to the [Visual Editor setup documentation](/guides/sidecar-experiments/sidecar-v3) for any missed steps. ================================================================================ # Datadog URL: https://docs.statsig.com/integrations/datadog Updated: 2026-07-11 ================================================================================ ### Overview There are four key use cases for the Datadog integration: 1. [Config Changes](#config-changes): Streams changes made in Statsig into Datadog, so you can identify which feature was enabled when a CPU spike or other performance degradation occurred (most widely-used integration). 2. [Event Forwarding](#events): Statsig forwards event-count totals to Datadog for monitoring your Statsig usage volumes. 3. [Datadog RUM integration](#datadog-rum-integration): Enriches Datadog RUM data with flag/experiment assignment information, allowing you to correlate product feature changes with their impact on system/performance metrics. 4. [Datadog triggers](/integrations/triggers/datadog): When an alarm fires in Datadog, it stops a Statsig feature gate. ### Connecting to Datadog 1. To create a Datadog API key, navigate to **Organization Settings** > **API Keys**. If you have the permission to create API keys, click **New Key**. {% figure %} ![Datadog API key creation interface](/images/integrations/datadog/232632837-d1e81380-78a3-48a2-887d-72b13d541b0a.png) {% /figure %} 2. Paste the API key in the text box at the top of the integration dialog, then select **Confirm**. If the above steps are out of date, refer to the [Datadog documentation](https://docs.datadoghq.com/account_management/api-app-keys/#add-an-api-key-or-client-token) on how to set up API keys. ### Config changes This integration sends Datadog Events of your choice when your project's settings change. For example, Statsig sends an Event when someone edits a Feature Gate. These events appear in the Datadog Events Explorer. {% figure %} ![Datadog Events Explorer interface](/images/integrations/datadog/232636042-ee5cf1d0-e9e7-4158-903b-5a447ab14575.png) {% /figure %} ### Event totals forwarding This integration forwards the count of Statsig SDK Events to Datadog for monitoring your project's Statsig usage. This integration also has the option to forward non-production events to Datadog. Statsig events are mapped to Datadog metrics with listed tags as follows: * statsig::gate\_exposure -> statsig.check\_gate.count * environment * name * value * statsig::config\_exposure -> statsig.get\_config.count * environment * statsig::experiment\_exposure -> statsig.get\_experiment.count * environment * group * name * statsig::layer\_exposure -> statsig.get\_layer.count * environment * name * statsig::holdout\_exposure -> statsig.get\_holdout.count * environment * name * value #### Example of check\_gate metric {% figure %} ![Datadog check_gate metric visualization](/images/integrations/datadog/232629870-e1776bd6-c63d-438d-863e-2d7a3a347eab.png) {% /figure %} ### Datadog RUM integration This integration requires a client-side setup. Go to the [Datadog documentation](https://docs.datadoghq.com/integrations/statsig-rum/) for setup instructions. ================================================================================ # Datadog Triggers URL: https://docs.statsig.com/integrations/triggers/datadog Updated: 2026-07-11 ================================================================================ ### How Datadog triggers work Triggers let you make changes to your Statsig project from a third-party source. You can create a trigger with a specific action, such as "Disable a feature gate," and Statsig generates a URL that performs that action when called. You can use triggers with Datadog to toggle a gate on or off based on metric performance. For example, create a trigger that disables a gate and connect it to a Datadog monitor so Statsig automatically disables the feature if it detects a metric regression. ### Trigger types The provider supports gate triggers only. ### Creating a trigger 1. On Statsig console, navigate to the [integrations](https://console.statsig.com/integrations) tab. 2. Find and open **Datadog** -> **Triggers**. 3. Specify the target gate and action, then click **Create** {% figure %} ![Statsig trigger configuration interface](/images/integrations/triggers/datadog/216156433-a41461d7-349b-446a-a089-610e57917824.png) {% /figure %} ### Connecting to Datadog 1. Copy the trigger URL generated from the previous step. 2. In Datadog, create a new [Webhook](https://app.datadoghq.com/integrations/webhooks) using that URL. You don't need to change the payload. {% figure %} ![Datadog webhook configuration screen](/images/integrations/triggers/datadog/216156415-2b170a22-71ea-48af-9db9-dc3f76a357a4.png) {% /figure %} 3. In your Datadog monitor settings, add this webhook as a notification target. {% figure %} ![Datadog monitor notification settings](/images/integrations/triggers/datadog/216156425-8dd57f92-aa64-4a55-8bd4-4737a8e6818e.png) {% /figure %} To configure a Datadog monitor, refer to [Datadog Notifications](https://docs.datadoghq.com/monitors/notify/). ================================================================================ # Statsig Terraform Provider URL: https://docs.statsig.com/integrations/terraform/introduction Updated: 2026-07-11 ================================================================================ The Statsig Terraform Provider allows you to configure your gates and experiments with Terraform. The provider synchronizes with Statsig through the Console API. If the Terraform Provider doesn't support what you need, refer to the [Console API](/console-api/introduction). Statsig hosts it on the Terraform registry at https://registry.terraform.io/providers/statsig-io/statsig ## Supported features The provider supports the following Statsig configurations: * [Gates](/integrations/terraform/terraform_gate) * [Experiments](/integrations/terraform/terraform_experiment) Coming Soon: * Dynamic Configs * Segments If you need more from the Terraform Provider, ask in the [Statsig Slack channel](https://statsig.com/slack). ================================================================================ # Managing Gates With Terraform URL: https://docs.statsig.com/integrations/terraform/terraform_gate Updated: 2026-07-11 ================================================================================ You can create a .tf file (Terraform File) to configure your Statsig feature gates. All features of [console/v1/gates](/console-api/introduction) are supported. The layout is very similar to the JSON body of a /gates request. Require the Statsig provider and update the version to match your installed release: ```go terraform { required_providers { statsig = { version = "x.x.x" source = "statsig-io/statsig" } } } ``` ## Basic example The following creates a basic gate resource: ```go resource "statsig_gate" "my_gate" { name = "my_gate" description = "A short description of what this Gate is used for." is_enabled = true id_type = "userID" rules { name = "Public" pass_percentage = 100 conditions { type = "public" } } } ``` ## Conditions The provider supports all Console API conditions, but the syntax requires minor adjustments. * **type** | string | The [type](/console-api/introduction#all-conditions) of condition it is. * **operator** | string | The form of evaluation to run against the **target\_value**. * **target\_value** | \[string] | The value or values to evaluate. Must be an array; elements must be strings. (Wrap numbers in quotes: 31 -> "31") * **field** | string | Only for custom\_field condition type. The name of the field you wish to pull for evaluation from the "custom" object on a user. ```go conditions { type = "custom_field" target_value = ["31"] operator = "gt" field = "age" } ``` Refer to the full list of [conditions](/console-api/introduction#all-conditions). You can find a full gate example in the open source Github repo https://github.com/statsig-io/terraform-provider-statsig/blob/main/examples/resources/statsig\_gate/resource.tf ================================================================================ # Managing Experiments With Terraform URL: https://docs.statsig.com/integrations/terraform/terraform_experiment Updated: 2026-07-11 ================================================================================ You can create a .tf file (Terraform File) to configure your Statsig experiments. All features of [console/v1/experiments](/console-api/introduction) are supported. The layout is very similar to the JSON body of a /experiments request. Require the Statsig provider and update the version to match your installed release: ```go terraform { required_providers { statsig = { version = "x.x.x" source = "statsig-io/statsig" } } } ``` ## Basic example The following creates a basic experiment resource: ```go resource "statsig_experiment" "my_experiment" { name = "my_experiment" description = "A short description of what we are experimenting on." id_type = "userID" allocation = 10 status = "setup" groups { name = "Test Group" size = 50 parameter_values_json = jsonencode({ "a_string" : "test_string", "a_bool" : true }) } groups { name = "Control Group" size = 50 parameter_values_json = jsonencode({ "a_string" : "control_string", "a_bool" : false }) } } ``` ## Changing experiment status You can update the `status` field to four possible values: **setup**, **active**, **decision\_made**, and **abandoned**. For code examples of how the **Setup -> Run -> Ship** flow works, refer to the [Terraform Acceptance Tests](#) for experiments. #### Status: setup The experiment isn't ready. The Statsig SDK and HttpAPI don't serve any values. #### Status: active The experiment is running. Statsig returns values to users and collects analytics data. #### Status: decision\_made The experiment is complete and you've selected a group to ship. This state requires you to set the `launched_group_id` field to the GroupID found on [console.statsig.com](https://console.statsig.com). #### Status: abandoned The experiment doesn't serve any values and doesn't collect analytics data. {% callout type="info" %} * You can only create an experiment with the status "setup" or "active". * You can only transition to "decision\_made" from "active". {% /callout %} You can find a full experiment example in the open source Github repo https://github.com/statsig-io/terraform-provider-statsig/blob/main/examples/resources/statsig\_experiment/resource.tf. ================================================================================ # Pulumi URL: https://docs.statsig.com/integrations/pulumi Updated: 2026-07-11 ================================================================================ The [Statsig Pulumi Provider](https://www.pulumi.com/registry/packages/statsig/) lets you configure your gates and experiments using Pulumi Infrastructure as Code. The provider synchronizes with Statsig through the Console API. If the Pulumi Provider doesn't support what you need, refer to the [Console API](/console-api/introduction). ## Installation The Statsig provider is available as a package in the following Pulumi languages: * **JavaScript/TypeScript**: [`@statsig/pulumi-statsig`](https://www.npmjs.com/package/@statsig/pulumi-statsig) * **Python**: [`pulumi-statsig`](https://pypi.org/project/pulumi-statsig/) * **Go**: [`github.com/statsig-io/pulumi-statsig/sdk/go/statsig`](https://github.com/statsig-io/pulumi-statsig) * **.NET**: [`Statsig.Pulumi`](https://www.nuget.org/packages/Statsig.Pulumi) ## Configuration Configure your `Pulumi.yaml` file with your Console API key before using the provider: ```yaml # Pulumi.yaml provider configuration file name: configuration-example runtime: nodejs # or python, go, dotnet config: statsig:consoleApiKey: value: 'YOUR_CONSOLE_API_KEY' ``` ### Configuration reference * `consoleApiKey` (String) - The Statsig Console API key you retrieve from the Statsig Console. ## Example usage ### TypeScript ```typescript import * as pulumi from "@pulumi/pulumi"; import * as statsig from "@statsig/pulumi-statsig"; // Create a Feature Gate const gate = new statsig.Gate("my-gate", {}); ``` ### Python ```python import pulumi import pulumi_statsig as statsig # Create a Feature Gate gate = statsig.Gate("my-gate") ``` ### Go ```go package main import ( "github.com/pulumi/pulumi/sdk/v3/go/pulumi" "github.com/statsig-io/pulumi-statsig/sdk/go/statsig" ) func main() { pulumi.Run(func(ctx *pulumi.Context) error { // Create a Feature Gate _, err := statsig.NewGate(ctx, "my-gate", nil) if err != nil { return err } return nil }) } ``` ### C\# ```csharp using Pulumi; using Statsig = Statsig.Pulumi; return await Deployment.RunAsync(() => { // Create a Feature Gate var gate = new Statsig.Gate("my-gate"); }); ``` ## Supported features The provider supports the following Statsig configurations: * Gates * Experiments Coming Soon: * Dynamic Configs * Segments If you need more from the Pulumi Provider, ask in the [Statsig Slack channel](https://statsig.com/slack). ================================================================================ # Azure AI URL: https://docs.statsig.com/integrations/azureai/introduction Updated: 2026-07-11 ================================================================================ Statsig offers SDKs for integrating Azure AI models into server applications. These SDKs simplify the implementation of features like completions and embeddings in your server application. They provide easy-to-use APIs and automatically track metrics such as latency, token length, and model details, which you can use for optimization and experimentation. Use cases include: * Implement Azure AI Models in your code with a [single lightweight framework](/integrations/azureai/model-client) * [Stream completions](/integrations/azureai/completions) and [generate embeddings](/integrations/azureai/embeddings) * [Capture invocation and usage metrics](/integrations/azureai/capturing-metrics) with no extra work * [Run A/B tests on parameters](/integrations/azureai/running-experiments) like model, prompt, temperature and more {% figure %} ![Azure AI integration architecture diagram](/images/integrations/azureai/introduction/b23c79c3-8501-4390-a3f3-3496970eb272.png) {% /figure %} ## Supported SDKs * Node JS: https://github.com/statsig-io/azureai-nodejs/ * Python: https://github.com/statsig-io/azureai-python/ * .Net: https://github.com/statsig-io/azureai-dotnet/ ================================================================================ # Getting Started URL: https://docs.statsig.com/integrations/azureai/getting-started Updated: 2026-07-11 ================================================================================ ## Step 1: Install the SDK in your server app Install the Statsig Azure AI SDK using the package manager for your language or framework. {% tabs %} {% tab title="NodeJS" %} ```shell npm i @statsig/azure-ai ``` {% /tab %} {% tab title="Python" %} ```shell pip install azureai-statsig ``` {% /tab %} {% tab title=".NET" %} ```shell dotnet add package StatsigAzureAI ``` {% /tab %} {% /tabs %} ## Step 2: Create a model deployment in Azure AI Studio Log into your Azure AI Studio console and create a new deployment that you'd like to use. {% figure %} ![Azure AI Studio model deployment creation interface](/images/integrations/azureai/getting-started/3172d6f9-176e-4a3b-817f-3a5bd3af4291.png) {% /figure %} After confirmation, Statsig takes you to the details page of that model. {% figure %} ![Azure AI model deployment details page](/images/integrations/azureai/getting-started/b8f46ed9-ce02-486f-b6f7-1d09f7b62f33.png) {% /figure %} Copy the **Target URI** (your *endpoint* in code) and the **Key**. You need both in the SDK to call the APIs. ## Step 3: Get Statsig server SDK key {% callout type="info" %} This guide assumes you have an existing Statsig account. Go to https://statsig.com/signup to create a new free account if you don't already have one. {% /callout %} Go to your **Project Settings** and choose the **Keys & Environments** tab. Scroll down to the **API Keys** section and copy the Server Secret Key. If one doesn't exist, create one or ask your project admin to create one. {% figure %} ![Statsig project settings API keys section](/images/integrations/azureai/getting-started/f60f6f2b-60d0-4804-8db5-b82d4ac96bcf.png) {% /figure %} ## Step 4: Initialize Azure AI server {% tabs %} {% tab title="NodeJS" %} ```js import { AzureAI } from "@statsig/azure-ai"; await AzureAI.initialize("<STATSIG_SERVER_KEY>"); ``` {% /tab %} {% tab title="Python" %} ```python AzureAI.initialize("<STATSIG_SERVER_KEY>") ``` {% /tab %} {% tab title=".NET" %} ```csharp using Statsig; using Statsig.AzureAI; await Server.Initialize("<STATSIG_SERVER_KEY>"); ``` {% /tab %} {% /tabs %} ================================================================================ # AI Model Client URL: https://docs.statsig.com/integrations/azureai/model-client Updated: 2026-07-11 ================================================================================ To invoke Azure AI methods, instantiate a Model Client. You have two options. ## Option 1: (Recommended) using Statsig Dynamic Config Using Statsig's Dynamic Config lets you adjust AI invocation parameters and the endpoint without modifying code, giving you maximum flexibility. In your Statsig console, go to https://console.statsig.com/dynamic\_configs and click **Create** to create a new **Dynamic Config**. {% figure %} ![Dynamic Config creation interface](/images/integrations/azureai/model-client/29357d6f-c356-461c-afc7-e1673ba97f92.png) {% /figure %} After creation, fill in the properties of this deployment: {% figure %} ![Dynamic Config properties configuration screen](/images/integrations/azureai/model-client/e4d20573-9d1f-4d62-af11-408435d02f90.png) {% /figure %} The JSON of this looks like this: ``` { endpoint: "https://FILL_IN_YOUR_ENDPOINT", key: "FILL_IN_YOUR_KEY", completion_defaults: { frequency_penalty: 0, presence_penalty: 0, temperature: 1, top_p: 1, max_tokens: 0, stop: [], seed: 0, }, } ``` After saving the Dynamic Config, instantiate your Model Client using the **id** of that config: {% tabs %} {% tab title="NodeJS" %} ```js const client = AzureAI.getModelClient("<dynamic_config_id>"); ``` {% /tab %} {% tab title="Python" %} ```python client = AzureAI.get_model_client("<dynamic_config_id>") ``` {% /tab %} {% tab title=".NET" %} ```csharp var client = Server.GetModelClient("<dynamic_config_id>"); ``` {% /tab %} {% /tabs %} ## Option 2: Using hard-coded endpoint and key This option instantiates an AI model client directly. You must embed the endpoint and key in your code or load them using another mechanism, such as an environment variable. {% tabs %} {% tab title="NodeJS" %} ```js const modelClient = AzureAI.getModelClientFromEndpoint( "<DEPLOYMENT_ENDPOINT_URL>", "<DEPLOYMENT_KEY>" ); ``` {% /tab %} {% tab title="Python" %} ```python modelClient = AzureAI.get_model_client_from_endpoint("<DEPLOYMENT_ENDPOINT_URL>", "<DEPLOYMENT_KEY>") ``` {% /tab %} {% tab title=".NET" %} ```csharp var modelClient = Server.GetModelClientFromEndpoint( "<DEPLOYMENT_ENDPOINT_URL>", "<DEPLOYMENT_KEY>" ); ``` {% /tab %} {% /tabs %} ================================================================================ # Completions URL: https://docs.statsig.com/integrations/azureai/completions Updated: 2026-07-11 ================================================================================ Chat completions are AI-generated responses used to generate text. They can enable generic text completion or interactive dialogue based on a given prompt or message history. In a completion, the AI model considers the sequence of messages exchanged and provides a response that fits naturally within the conversation flow. ## Simple completions {% tabs %} {% tab title="NodeJS" %} ```js const messages = [ { role: "system", content: "You are a helpful assistant. You will talk like a pirate." }, { role: "user", content: "What is the best way to train a parrot?" }, ]; const result = await modelClient.complete(messages); for (const choice of result.choices) { console.log(choice.message.content); } ``` {% /tab %} {% tab title="Python" %} ```python response = modelClient.complete([ SystemMessage(content="You are a helpful assistant. You will talk like a pirate."), UserMessage(content="What is the best way to train a parrot?") ]) self.assertIsNotNone(response, "Expected response to not be None") for item in response.choices: content = item.message.content print(content) ``` {% /tab %} {% tab title=".NET" %} ```csharp var completion = await modelClient.Complete( "You are a helpful assistant that speaks like a pirate", "How do you train a parrot in 10 easy steps?" ); Console.WriteLine(completion); ``` {% /tab %} {% /tabs %} #### Output ``` Arrr, trainin’ a parrot be quite the adventure, savvy? Here be some tips fer ye: 1. **Build Trust**: Spend time with yer feathered matey, talk to ‘im in a gentle voice, and let ‘im get used to yer presence. 2. **Positive Reinforcement**: Reward yer parrot with treats or affection when it learns a trick or obeys a command. Parrots, like all creatures, respond well to praise! 3. **Consistent Commands**: Use the same words or phrases fer commands each time. Repeatin’ yerself helps the parrot make connections. 4. **Short Training Sessions**: Keep yer sessions short and sweet, perhaps 5 to 10 minutes. Parrots have short attention spans, ye see! 5. **Be Patient**: Not every parrot learns at the same pace. Keep yer cool, and don't lose yer temper; patience be key! 6. **Fun and Play**: Incorporate games into yer training to keep it interestin’. A happy parrot be a learnin’ parrot! Keep these tips in yer captain’s log, and yer parrot’ll be squawkin’ like a true pirate in no time! Arrr! ``` ## Streaming completions To reduce latency and increase responsiveness, stream AI responses to your client using the Streaming API. {% tabs %} {% tab title="NodeJS" %} ```js const messages = [ { role: "system", content: "You are a helpful assistant. You will talk like a pirate." }, { role: "user", content: "What is the best way to train a parrot?" }, ]; const stream = await modelClient.streamComplete(messages); for await (const event of stream) { if (event.data === "[DONE]") { return; } for (const choice of JSON.parse(event.data)?.choices) { process.stdout.write(choice.delta?.content ?? ""); } } ``` {% /tab %} {% tab title="Python" %} ```python response = modelClient.stream_complete([ SystemMessage(content="You are a helpful assistant. You will talk like a pirate."), UserMessage(content="What is the best way to train a parrot?") ]) for update in response: print(update.choices[0].delta.content or "", end="", flush=True) ``` {% /tab %} {% tab title=".NET" %} ```csharp var completion = await modelClient.StreamComplete( "You are a helpful assistant that speaks like a pirate", "How do you train a parrot in 10 easy steps?" ); await foreach (var update in completion) { if (!string.IsNullOrEmpty(update.ContentUpdate)) { Console.Write(update.ContentUpdate); } } ``` {% /tab %} {% /tabs %} ================================================================================ # Text Embeddings URL: https://docs.statsig.com/integrations/azureai/embeddings Updated: 2026-07-11 ================================================================================ Embeddings are numerical representations of data (such as text, images, or audio) that capture essential features as vectors. For text, embeddings map words or sentences to vector spaces where similar items are positioned closer together, enabling comparisons and efficient searches. Developers use embeddings in tasks such as semantic search, recommendation engines, and clustering. Embeddings let machine learning models analyze and process unstructured data by recognizing patterns in those vector representations. ## Generate text embeddings {% tabs %} {% tab title="NodeJS" %} ```js const result = await modelClient.getEmbeddings([ "Hello, world!", "Goodbye, world!", ]); for (const data of result.data) { console.log(`Embedding: ${data.embedding}`); } ``` {% /tab %} {% tab title="Python" %} ```python response = modelClient.get_embeddings(["Hello, world!", "Goodbye, world!"]) for item in response.data: length = len(item.embedding) print( f"data[{item.index}]: length={length}, [{item.embedding[0]}, {item.embedding[1]}, " f"..., {item.embedding[length-2]}, {item.embedding[length-1]}]" ) ``` {% /tab %} {% tab title=".NET" %} ```csharp var embedding = await client.GetEmbeddings(["Hello, world!", "Goodbye, world!"]); Console.WriteLine(embedding.First().ToArray()); Console.WriteLine(embedding.Last().ToArray()); ``` {% /tab %} {% /tabs %} #### Output ``` Embedding: -0.01918462,-0.025279032,-0.0017195191,0.018848283,-0.033795066,-0.019695852,-0.020947022,0.05158053,-0.03212684,-0.03037789,-0.0021458254,-0.028978731,-0.0024737532,-0.031481072,0.01033225,0.018606123,-0.046145335,0.041463535,0.00044186175,0.041221373,0.053679265,0.001873393,0.004567446,0.01002282,0.047867376,0.0022013208,-0.009834472,0.03847687,0.00089213194,-0.052118666,0.051150016,-0.03255735,-0.0140319485,-0.01263279, ..... ``` ================================================================================ # Capturing Metrics URL: https://docs.statsig.com/integrations/azureai/capturing-metrics Updated: 2026-07-11 ================================================================================ The Azure AI SDK automatically captures relevant invocation and usage metrics from each API call and logs them to Statsig. These events stream in real time in the console at https://console.statsig.com/metrics/events. {% figure %} ![Statsig console events streaming interface](/images/integrations/azureai/capturing-metrics/cb4136eb-6ae9-4ffa-b560-333f332eaa75.png) {% /figure %} ### Metrics captured Completion invocations capture the following metrics automatically: completion token length, prompt token length, latency, model name, total token length. For example, a completion API call results in a usage log that looks like this: {% figure %} ![Azure AI completion API usage log example](/images/integrations/azureai/capturing-metrics/d9c150cc-eded-4607-aec8-d1631af77cec.png) {% /figure %} You can use these metrics to compare multiple deployments and run experiments against different parameter sets, helping optimize cost, responsiveness, and user experience. ================================================================================ # Running A/B Tests URL: https://docs.statsig.com/integrations/azureai/running-experiments Updated: 2026-07-11 ================================================================================ The Azure AI SDK lets you run A/B tests to measure the effectiveness of different models and parameters. Using Statsig's stats engine, you gain real-time insights into model performance across metrics such as cost, accuracy, and latency. You can experiment with configurations including model type, prompt settings, and response parameters, then make data-driven decisions to improve your application. ## Example: Test GPT4o vs. GPT4o-mini ### Step 1: Create configs Create two dynamic configs, one named `gpt-4o` and another named `gpt-4o-mini`. In the **Value** section add the endpoint, key and other default parameters like this: {% figure %} ![Dynamic config setup interface](/images/integrations/azureai/running-experiments/3770f081-d68f-478e-8b3d-a36ef65d49c7.png) {% /figure %} These serve as the base deployment configs for the tests and let you modify parameters dynamically after launch. ### Step 2: Create some metrics to track This example uses a **latency** metric to show how to create metrics in Statsig. Navigate to the **Metrics Catalog** page at https://console.statsig.com/metrics/metrics\_catalog and click **Create**. {% figure %} ![Metrics catalog creation interface](/images/integrations/azureai/running-experiments/89841414-6140-41f4-89ee-b09b83f2846c.png) {% /figure %} Now, in the **Metric Definition** section, choose: | Property | Value | | --- | --- | | Metric Type: | **Aggregation** | | ID Type: | **User ID** | | Aggregation Using: | **Events** | | Aggregation Type: | **Average** | | Rollup Mode: | **Total Experiment** | | Event: | **usage** | | Average Using: | **Metadata** => **latency\_ms** | This creates a metric that averages **latency** across all **usage** events coming from chat completions. {% figure %} ![Latency metric configuration screen](/images/integrations/azureai/running-experiments/0317d30a-a479-498d-8dd0-666f2db616e3.png) {% /figure %} ### Step 3: Create an experiment Create a new experiment in the Statsig console at https://console.statsig.com/experiments. {% figure %} ![Experiment creation interface](/images/integrations/azureai/running-experiments/f1a93738-355f-4647-8444-9b6abfb72ffc.png) {% /figure %} In the **Setup** page, add the metrics you created in Step #2 in the **Primary Metrics** field. {% figure %} ![Primary metrics configuration screen](/images/integrations/azureai/running-experiments/eb47ec6c-bd9a-47d9-bca6-a6ce0ea4148b.png) {% /figure %} ### Step 4: Set up the variations Create the control and test variants for the experiment. For this example, split them 50/50. In the **Groups and Parameters** section, click **Add Parameter** and name the parameter *model\_name*, with *String* type. {% figure %} ![Parameter setup interface](/images/integrations/azureai/running-experiments/4837063a-9dde-4c0b-8bca-584d98adae47.png) {% /figure %} Add the two configs created in Step #1, one each to Control and Test parameters like this: {% figure %} ![Experiment variant configuration screen](/images/integrations/azureai/running-experiments/b54a41de-a442-4870-870c-81ff949ecceb.png) {% /figure %} ### Step 5: Save and start the experiment Select **Save** at the bottom of the page. A **Start** button appears at the top of the experiment page. Select it to begin the allocation process. ### Step 6: Write code The code below: 1. Fetches the experiment configuration from the server for a given user. Pass the **userID** from your client application or from your database. The example generates a random one for testing. 2. Gets the **config name** from the experiment variant (control or test). 3. Creates a model client using the fetched config. 4. Uses the model client to complete text. ```js async function testExperiments() { await AzureAI.initialize(statsigServerKey); const experiment = Statsig.getExperimentSync( { userID: Math.random().toString() }, // use a valid userID here "model_experiment_gpt4o_vs_gpt4o-mini", ); const configName = experiment.get("model_name", "gpt-4o"); console.log(`Using model: ${configName}`); const modelClient = AzureAI.getModelClient(configName); const result = await modelClient.complete([{ role: "user", content: "Recite the first 10 digits of pi." }]); result.choices.forEach((choice, i) => { console.log(choice.message.content); }); await AzureAI.shutdown(); } ``` ### Step 7: Run the experiment and verify results Run this experiment for several days to measure the latency profiles of **gpt-4o** compared with **gpt-4o-mini** in the Statsig console. Choose whichever model suits your needs. This is a simple experiment to test models against each other. You can also adjust other parameters such as *temperature*, *frequency\_penalty*, and *max\_tokens* by modifying the config, without updating code. ================================================================================ # Organization Policies URL: https://docs.statsig.com/access-management/org-admin/organization_policies Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Organization-level Experiment and Gate Policies are an Enterprise only feature. {% /callout %} ## How organization policies work You can configure Organization-level [Experimentation](https://console.statsig.com/settings/products/experimentation?tab=org) and [Feature Management](https://console.statsig.com/settings/feature_management?tab=org) policies through your **[Settings](https://console.statsig.com/settings)** page. These policies apply to all experiments and gates created within any project in your organization. For details, refer to: * [Experiment Policy](/access-management/org-admin/experiment_policy) * [Feature Gate Policy](/access-management/org-admin/gates_policy) ================================================================================ # Experiment Policy URL: https://docs.statsig.com/access-management/org-admin/experiment_policy Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Organization level Experiment Policies are an Enterprise only feature. {% /callout %} ## What Experiment Policy lets organization admins do * **Set defaults for new experiments:** Data scientists and experts can define experiment best practices for the organization, such as default experiment duration and use of sequential testing. * **Enforce these defaults:** Ensures consistency across all experiments and that teams follow best practices. * **Tailor Statsig for the business:** Limits experimentation practices to approved approaches, reducing the risk of unintended misuse. ## Configure experiment policies To manage Organization Experiment Policies, go to Product Configuration → Experimentation → [Organization tab](https://console.statsig.com/settings/products/experimentation). Only organization admins can modify these settings. {% figure %} ![Experiment policies](/images/organization_policies/experiments_policy_1.png) {% /figure %} ================================================================================ # Feature Gates Policy URL: https://docs.statsig.com/access-management/org-admin/gates_policy Updated: 2026-07-11 ================================================================================ {% callout type="info" %} Organization level Feature Gate Policies are an Enterprise only feature. {% /callout %} ## What Feature Gates Policy lets organization admins do Feature Gates Policy lets organization admins: **Configure the Custom Fields used in Feature Gate targeting:** With this setting, admins can pre-define allowed Custom Fields by ID type, add a description to provide more context on the Custom Field to end users, and pre-define the allowed values. Defining allowed values is optional. {% figure %} ![Organization gate policy page showing allowed custom fields per ID type](/images/access-management/org-admin/gates_policy/394b586d-22aa-45e9-96be-10ba2270c010.png) {% /figure %} **Configure allowed targeting criteria for Feature Gates:** With this setting, admins can select which targeting criteria surface as options during gate creation for end users. You also define these allowed targeting criteria at the ID type level, enabling you to, for example, create one set of targeting criteria for logged-in (UserID) vs. logged-out (stableID) feature rollouts. {% figure %} ![Allowed targeting criteria configuration for feature gates by ID type](/images/access-management/org-admin/gates_policy/e94ca308-49e3-422f-ad42-3647dc910773.png) {% /figure %} **Require templates for gate or dynamic config creation:** Lets admins enforce that all gate or dynamic config creations use a template. Manage restrictions on who can create or edit templates (and which templates are allowed per team) under **People** → **Teams** and **Roles**. {% figure %} ![Requirement toggle enforcing templates for new gates or configs](/images/access-management/org-admin/gates_policy/db0a74d4-a92a-4ae8-a82c-8b8cd409b251.png) {% /figure %} ## Configure feature gate policies To manage Organization Gate Policy, go to Product Configuration → Feature Management → [Organization tab](https://console.statsig.com/settings/feature_management?tab=org). Only organization admins can modify these settings. {% figure %} ![Product Configuration settings highlighting Feature Management organization policies](/images/organization_policies/gates_policy_3.png) {% /figure %} ================================================================================ # Templates URL: https://docs.statsig.com/experiments/templates/templates Updated: 2026-07-11 ================================================================================ ## How templates work Templates enable you to create a blueprint for gates, dynamic configs, and experiments to enable standardization and reusability across your project. Templates can help enforce a rollout sequence, or make it easy for new experimenters to start with your standard team settings for experimentation. Organization admins can enforce templates at the org level (through [Organization Settings](/access-management/org-admin/organization_policies) and [Role-based Access Controls](/access-management/projects)) or at the [Team level](/access-management/teams). The sections below describe the levels of controls and permissions available for templates. ## Creating templates There are two primary ways to create a new template: 1. From the [**Templates**](http://console.statsig.com/templates) page under **Product Configuration** 2. From a gate or experiment you want to turn into a new template ### Creating templates from project settings To create a new template from Project Settings, navigate to **Settings** -> **Project Configuration** -> **Templates** and click the **+Create New** CTA, then select whether you want to create a gate, experiment, or dynamic config template. {% figure %} ![Template Creation](/images/templates/create-template.png) {% /figure %} ### Converting an existing config into a template To convert an existing config into a template, tap the "..." menu in the config and select **Save as Template**. Enter a name and description, then save. {% figure %} ![Save as Template 1](/images/templates/templates_save_1.png) {% /figure %} {% figure %} ![Save as Template 2](/images/templates/templates_save_2.png) {% /figure %} ## Managing templates To manage templates, go to the **Templates** setting under **Product Configuration**. {% callout type="note" %} Statsig controls who can create or modify templates through Role-based Access Controls in **People** → **Roles**. By default, org and project admins can modify or delete any template. {% /callout %} {% figure %} ![Templates](/images/templates/templates_role_setting.png) {% /figure %} ## Creating configs from templates To create a config using a template, select a template from the template selector at the time of config creation. If templates are required at the organization or team level, you can't proceed with config creation until you select an approved template. Team admins configure the list of template options in the drop-down (refer to the section below). {% figure %} ![Template Selection](/images/templates/template-selection.png) {% /figure %} ## Template settings and permissions There are two key layers of settings governing templates: 1. Org level 2. Team level ### Org-level templates settings Within Experiment and Gate Policies (**Settings** -> **Project Configuration** -> **Feature Management**/ **Experimentation** - **Organization Tab**), you can enforce that a template is used for any new gate, dynamic config, or experiment creation. Only organization admins can configure this setting. {% callout type="note" %} You must create at least 1 experiment/gate template for users to choose if you toggle on this setting, otherwise they're blocked from creating new configs. {% /callout %} {% figure %} ![Org-level Feature Gate Templates Settings](/images/templates/gates_policy_3.png) {% /figure %} ### Team-level templates settings At the team level, you can configure which templates members of that team can choose from at the time of config creation, and whether to require use of a template. {% callout type="note" %} This setting only applies if templates aren't already required at the organization level, which overrides any team-level configuration. {% /callout %} To configure team-specific templates, navigate to **Settings** -> **People** -> **Teams** -> choose a team -> **Settings** and then choose which templates are allowed for gates, dynamic configs, and experiments. {% figure %} ![Team-level Templates Settings](/images/templates/templates_teams.png) {% /figure %} ### Known limitations * Statsig doesn't support renaming experiment templates. To rename a template, create a new experiment using the template, then create a new template from that experiment with the desired name. * Statsig doesn't support modifying templates through the Console API. ================================================================================ # Decision Framework URL: https://docs.statsig.com/experiments/templates/decision-framework Updated: 2026-07-11 ================================================================================ A Decision Framework for experiment templates allows teams to standardize their interpretation of results and decision-making process. After configuration, it provides clear recommendations for which group to ship based on experimental outcomes. The framework highlights recommended actions based on metric movements, but doesn't enforce them. ## Configure a decision framework To configure a Decision Framework, find the experiment template you want under **Settings** → [**Templates**](https://console.statsig.com/templates), then navigate to the **Decision Framework** tab. Select one primary metric and one or more guardrail metrics to choose a recommended action based on metric performance. {% figure %} ![Team-level Templates Settings](/images/templates/df_setup_3.png) {% /figure %} You can configure three types of recommendations: * **Rollout Winning Group**: An icon appears next to the winning group in the Make Decision button * **Discuss**: A message appears on the experiment page recommending discussion before making a ship decision * **Do Not Roll Out**: An icon appears next to the control group in the Make Decision button {% figure %} ![recommendation](/images/templates/df_icon.png) {% /figure %} ## Reviews for shipping negative results Configure reviewers for when a shipping decision doesn't align with the Decision Framework recommendations. Toggle on "Require reviews when decision opposes recommended decision" and add reviewers in the dialog box. {% figure %} ![recommendation](/images/templates/df_review.png) {% /figure %} After setup, Statsig requires a review in the following situations: * **Rollout Winning Group**: Shipping a treatment group that isn't recommended * **Discuss**: Shipping any treatment group * **Do Not Roll Out**: No review is required {% figure %} ![recoomendation](/images/templates/df_ship_review_2.png) {% /figure %} ================================================================================ # Discussions in Statsig URL: https://docs.statsig.com/access-management/discussions Updated: 2026-07-11 ================================================================================ ## Discussions Feature rollout and experimentation are collaborative. Collaboration often requires taking screenshots, reviewing metrics, and messaging team members with questions. Without a shared record, this information is siloed and unavailable to others. In-context Discussions allow teams to discuss aspects of a feature rollout or experiment while making that context available to all team members. Common uses include discussing a surprising metric lift, asking questions about validation performed before increasing a feature rollout, or keeping a running log of observations and escalations that can be referenced after the experiment is complete. {% figure %} ![Discussions interface in Statsig console](/images/guides/fomo/7215b193-41ae-4932-aeff-8f41fc00c55e.png) {% /figure %} ================================================================================ # Tags in Statsig URL: https://docs.statsig.com/access-management/tags Updated: 2026-07-11 ================================================================================ ## Tags for organization Tags let you apply lightweight organization to your Statsig config (gates, experiments, and metrics) to enable easy filtering by team or organization objectives. For example, you can tag growth-related experiments and metrics with a "Growth" tag and filter to only those items when needed. Tags are useful with metrics: add the Growth tag to your scorecard metrics to pull in all key Growth metrics at once instead of selecting them individually. The Core tag is for company-critical metrics. These are added to experiment scorecards by default so experimenters can track impact on those metrics. {% figure %} ![Tags organization interface](/images/guides/fomo/92a44de5-71bb-4b80-9fea-f8d83e2c4f95.png) {% /figure %} ================================================================================ # Setting up Reviews for Team Workflows URL: https://docs.statsig.com/guides/setting-up-reviews Updated: 2026-07-11 ================================================================================ You can enable reviews for all Statsig resources such as feature gates, dynamic configs, segments, and experiments that you'll likely deploy to a production environment. ## Turning on change reviews for a project As a Project Admin, you can configure your project to require reviews for any changes. To enable reviews for your project, navigate to the **Project Settings** page, switch to the *Reviews* tab and toggle this on. {% figure %} ![Project settings reviews configuration interface](/images/guides/setting-up-reviews/45a439b5-7cf7-4f32-82d0-596c089f2359.png) {% /figure %} - You can optionally allow different roles to bypass the review requirement and self-approve review requests by customizing the permissions available to user roles: {% figure %} ![User role permissions configuration screen](/images/guides/setting-up-reviews/4b7db056-a6be-4a76-99c9-08f8dc053ed8.png) {% /figure %} - When you make any configuration changes to a feature gate or experiment, the console prompts you to **Submit for Review**. You can add reviewers when you submit the change. {% figure %} <img src="/images/guides/setting-up-reviews/166678241-272adade-ca60-4942-bd04-a1413d54864c.png" alt="Submit for Review modal prompting for reviewer selection" width="1168" /> {% /figure %} - Reviewers see a notification on the Statsig console. When they select **View Proposed Changes**, they see a diff of the _current version_ in production and the _new version_. Reviewers can then **Approve** or **Reject** the submitted changes. {% figure %} ![proposed changes](/images/proposed_changes.png) {% /figure %} {% figure %} ![example review](/images/example_review_page.png) {% /figure %} ## Teams To create a predefined group of reviewers, you can create Teams. {% figure %} ![Teams creation interface](/images/guides/setting-up-reviews/1b1c72a9-ac98-4590-8690-c39d2e68489a.png) {% /figure %} You can now use these predefined **Teams** when you submit any changes for review. {% figure %} ![Team selection for review submission](/images/guides/setting-up-reviews/166684577-29598c7f-fcba-4c7e-848d-9a45b031bd79.png) {% /figure %} ## Enforcing team reviews You can restrict who can make changes to your Project by (a) turning on **Reviews Required** for your Project and (b) adding designated **Teams** or **Reviewers** when you create the Feature Gate or Experiment. For (a), refer to the **Turning on change reviews for a project** section to enable project-wide reviews. For (b), as an owner of a Feature Gate or Experiment, you can add designated **Teams** or **Reviewers** at any time. This ensures that only these designated groups or members can review and approve subsequent changes. When another member tries to edit these designated review groups or reviewers, the change requires approval from the currently designated reviewers. {% figure %} <img src="/images/guides/setting-up-reviews/166682283-c9e2de57-0b9a-473e-adf2-100a240ab6b0.png" alt="Team review configuration settings" width="655" /> {% /figure %} {% figure %} <img src="/images/guides/setting-up-reviews/166682177-d44411e6-c4ab-49fe-9250-c77b063621af.png" alt="Teams and reviewers selector for enforcing approvals" width="458" /> {% /figure %} ## Configuring review settings for different environments Many teams build, test, and launch new features and experiments across multiple development environments. Statsig supports this through [Environments](/guides/using-environments#configuring-environments). You can also configure which environments require reviews through **Project Settings**. Go to **Project Settings** → [**Keys & Environments**](https://console.statsig.com/BPJcDV1K1g87fTib5ZEMk/api_keys) → tap **Edit** on **Environments**. By default, if you have enabled "Reviews Required" for your project, reviews are required for Production but not for non-Production environments. {% figure %} ![Environments settings showing environments and order](/images/environments.png) {% /figure %} {% figure %} ![Manage environments dialog with environment list and review settings](/images/edit-environment-dialog.png) {% /figure %} #### Team-based required reviews per environment You can assign specific teams as reviewers for each environment. This ensures that only designated team members can approve changes for that environment. {% figure %} ![Assigning teams as reviewers for specific environments](/images/environment-reviews.png) {% /figure %} #### Code freeze use case During code freeze periods, you can prevent anyone from deploying feature flags or configs to production by assigning a dedicated code freeze team as the production reviewer. This ensures that only members of that team (such as your SRE team or designated code freeze owners) can approve production changes. After the code freeze period ends, you can remove the team assignment to restore normal review workflows. ================================================================================ # Pre-commit Webhooks URL: https://docs.statsig.com/guides/pre-commit-webhooks Updated: 2026-07-11 ================================================================================ Pre-commit Webhooks allow you to integrate external validation into your change approval workflow. This enables you to: * Route changes through internal approval systems * Run automated tests before changes go live * Enforce custom business logic or compliance checks * Integrate with CI/CD pipelines ## How pre-commit webhooks work When a Statsig user submits a change for review and a reviewer approves it, clicking **Commit Changes** triggers your configured webhook. Statsig sends the change details to your endpoint. Your endpoint validates the change (for example, runs tests or checks policies) and responds to Statsig with either an approval or a rejection. The response determines whether the user can commit the change or whether it remains blocked. ## Setup instructions ### Step 1: Configure your webhook endpoint Your webhook endpoint must: * Accept POST requests from Statsig * Process the change payload (refer to the format below) * Call the Change Validation API to approve or reject Refer to the [Change Validation API documentation](/console-api/introduction) for implementation details. ### Step 2: Configure Statsig console settings Navigate to **Settings** → **Product Configuration** → [**General**](https://console.statsig.com/settings/products): 1. **Webhook URL**: The endpoint where Statsig sends change notifications 2. **Webhook Key**: A secret key sent in the `x-statsig-webhook-key` header for authentication 3. **Initial Message**: A message shown to users while validation is in progress * Example: "Your change is being validated by our CI/CD system. This may take up to 2 minutes..." {% figure %} <img src="/images/guides/setting-up-reviews/49c408da-0910-4a90-9ee3-7d1e114c9013.png" alt="Webhook URL and initial message configuration" width="495" /> {% /figure %} {% figure %} <img src="/images/guides/setting-up-reviews/a01617b2-3bb0-4f20-a505-b9318dd220a7.png" alt="Webhook verification key configuration" width="497" /> {% /figure %} ## Usage instructions ### Step 3: Send a webhook payload Every payload has these fields at a minimum: ```json { "review_id": "string", // Required for API response "submitter": "user@example.com", // Who created the review "committer": "user@example.com", // Who is committing the change "config_type": "gate | dynamic_config | segment | experiment", "config_name": "string", // For gates, configs, segments "experiment_name": "string", // For experiments "type": "string" // Change type (see below) } ``` Currently, only changes to gates, dynamic configs, segments, and experiments trigger pre-commit webhooks, and only the following changes: * `rules`: Updating rules (for gates, dynamic configs, segments) * contains payloads `old_config` and `new_config` with the same format returned by the console API for the entity type (e.g. [/api-reference/gates/read-gate](/api-reference/gates/read-gate) for gates) * `update_target_apps`: Updating target applications * `update_allocation`: Changing the pass percentage of an ongoing experiment * `start_experiment` * `ship_experiment` * `abandon_experiment` * `update_experiment_settings`: Changing settings of an ongoing experiment * contains payloads `old_experiment` and `new_experiment` with the same format returned by the [Console API](/console-api/introduction) ### Step 4: Respond to webhooks Your system must call the Change Validation API to approve or reject the change: **Endpoint:** `POST https://statsigapi.net/console/v1/change_validation` When responding to validation, set the `message` field to provide a message to the user. Set the message after validation completes, or provide progressive updates while validation is running. ```json { "reviewID": "review_123", "message": "⏳ Running tests... (45/150 complete)" } ``` **What Users See** When a review is pending validation, users see: * **Title**: "Changes to this config have been reviewed and are pending validation" * **Description**: * The company-level `precommit_webhook_message` (if set in console settings) * The review-specific `precommit_message` (if your webhook has set it) ## Bypassing validation Project Admins can grant the "Bypass Pre-commit Webhook" permission to specific roles. Users with this permission see a "Commit Changes (Bypass Validation)" option that lets them skip webhook validation entirely. ## Best practices 1. **Respond quickly**: You have 2 minutes before the request times out. 2. **Include debug links**: Help users troubleshoot failures quickly. 3. **Use progressive updates**: Keep users informed during long validations. 4. **Set a helpful initial message**: Explain what is happening and the expected wait time. 5. **Handle errors gracefully**: If your validation system is unavailable, consider auto-approving or notifying users. ================================================================================ # Config History URL: https://docs.statsig.com/guides/config-history Updated: 2026-07-11 ================================================================================ ## Entity Change History To access change history for entities like Feature Gates, Experiments, Dynamic Configs, and Segments, click the "History" button on the top right of their respective page. {% figure %} ![Config History](/images/config_history.png) {% /figure %} The history page shows each change to a given config and the time that change was published. {% figure %} ![History timeline listing published config changes](/images/guides/config-history/169888219-6dd20bb4-2d22-4396-b9cb-ebe3a7307b90.png) {% /figure %} For Feature Gates and Dynamic Configs, there is also a "Preview" and "Restore" option. These make it easy to revert to a previous state, particularly if a rollout causes issues in production. The "Preview" action shows a diff view between the current state and the selected previous state, so you can confirm you are reverting to the correct state. {% figure %} ![Preview and restore dialog comparing config versions](/images/guides/config-history/169888598-df6f94ee-0194-4f09-906e-d85ed89cd554.png) {% /figure %} For Gates, Dynamic Configs, and Segments, you can also select two different versions, and compare the differences between the states of the entity between the two versions. {% figure %} ![Compare Configs](/images/config_history_compare_changes.png) {% /figure %} {% callout type="info" %} Statsig doesn't list changes in nested configs. For example, if this gate references another gate or segment through a "Passes Target Gate" or "User in Segment" condition, changes to the other gate or segment don't appear in the history view. {% /callout %} ## Audit Logs Audit logs show the change history for all available entities across the entire project. Statsig stores audit logs indefinitely. You can filter by entity type, name, tag, target app, environment, action, and the user who triggered the action. You can access audit logs programmatically or through the Statsig Console UI at *Settings* > [Audit Logs](https://console.statsig.com/audit_logs). ================================================================================ # User Data Deletion Requests API URL: https://docs.statsig.com/compliance/user_data_deletion_requests Updated: 2026-07-11 ================================================================================ {% callout type="info" %} User data deletion requests are for Enterprise contracts only. Contact the support team, your sales contact, or the [Slack community](https://statsig.com/slack) to enable Enterprise features. Statsig currently only supports deleting data for unit type `user_id`. {% /callout %} GDPR and similar laws may require that you delete a user's data when they request it. To support deletion requests, Statsig provides this API. ### How to use the API All requests must include the `STATSIG-API-KEY` field in the header. The value should be a Server API Key, which you can create in the Project Settings on console.statsig.com/api\_keys. ## Send a deletion request Data deletion requests take the following parameters: * `unit_type`: The unit type corresponding to the IDs you want deleted. Currently only `user_id` is supported. * `ids`: A comma-separated list of the IDs to delete data for. * `delimiter` (optional): If your IDs contain commas, specify a different delimiter here. * `request_id` (optional): A unique request ID from your system. If omitted, Statsig provides one in the response. The ID must be unique; otherwise the request fails with a 400 error. ```bash curl \ --header "statsig-api-key: <YOUR-API-KEY>" \ --header "Content-Type: application/json" \ --request POST \ --data '{"unit_type": "user_id", "ids": "1,2,3", "request_id": "test_request_1"}' \ "https://api.statsig.com/v1/delete_user_data" ``` Response: `{"request_id":"test_request_1"}` ## Check deletion status Using the request ID, check the status of a deletion. Input: `request_id`: the ID of the request. Output: * `COMPLETE`: data has been deleted * `PENDING`: data is still pending deletion * `UNKNOWN`: invalid request ID ```bash curl \ --header "statsig-api-key: <YOUR-API-KEY>" \ --header "Content-Type: application/json" \ --request POST \ --data '{"request_id": "test_request_1"}' \ "https://api.statsig.com/v1/get_delete_user_data_request_status" ``` Response: `PENDING` ## Deletion SLA Statsig doesn't handle data deletion requests synchronously. Statsig batches requests and performs mass deletions periodically. Data is guaranteed to be deleted within 30 days of receiving a request. ## Important notes * After a data deletion request has been submitted, it can't be canceled. * After data has been deleted, Statsig no longer stores the set of IDs that data was deleted for, because those IDs may constitute personally identifiable information. ================================================================================ # Using Private Attributes URL: https://docs.statsig.com/guides/private-attributes Updated: 2026-07-11 ================================================================================ ## Evaluating feature gates, dynamic configs, segments, and experiments without logging user data Statsig takes the privacy of user data seriously. If you have legal requirements that prevent sending PII to third parties, or prefer not to send PII to a third-party service, you can still use Statsig for feature gating, configs, or experiments. ## How private attributes work You can make any field used for evaluation private. {% callout type="warning" %} Making the `userID` field private breaks your experience in the Statsig console: the Users tab, metrics charts, event logs, and Pulse metrics won't populate accurately. To hide the user ID, use a stable, one-way hash to preserve the ability to analyze user behavior. {% /callout %} For example, let's say you want to use an email condition: 1. Create a feature gate with a condition that passes on certain emails or domains 2. Pass `email: email@domain.com` in `privateAttributes` rather than in the top level `email` field 3. Test it out in the `Test Gate` console after saving changes {% figure %} ![Private attributes](/images/conditions/private-attributes.png) {% /figure %} When evaluating rules and conditions, Statsig checks for fields at the top level, then in `custom` attributes, and finally in `privateAttributes`. Not passing `email` at the top level causes the evaluator to check `custom` and then `privateAttributes` before evaluating the condition. To keep `ip` and `userAgent` private while still using browser or IP checks in the console, place them in the `privateAttributes` dictionary instead of at the top level of the user object. The same applies to `country`, `locale`, custom fields, and so on. The `statsig-node` SDK illustrates how this works (this is also how gates are evaluated on Statsig servers): https://github.com/statsig-io/node-js-server-sdk/blob/d1cb9431fb68b40f840254fce70363de1dc51aa5/src/Evaluator.js#L374 {% callout type="warning" %} Don't provide a `privateAttribute` key anywhere else in the `user` object. Statsig drops the entire `privateAttributes` dictionary, but any duplicate fields at the top level or in the custom object are still logged (and evaluated against). {% /callout %} ## Client vs server SDKs Client and single-user environment SDKs send the user object with the `initialize` call to evaluate the user against every gate in your Statsig project. The SDK sends `privateAttributes` with this call, but Statsig servers don't store or log them. Statsig servers require the `privateAttributes` field to evaluate gate, config, and experiment conditions, then strip it from the user object before any logging occurs. Client SDKs remove `privateAttributes` before logging any events; the SDK uses the field only for gate evaluation. Example with the `@statsig/js-client` SDK: https://github.com/statsig-io/js-client-monorepo/blob/17fb70f1bea00d07e156cf6ff03b8024bbc1b197/packages/client-core/src/EventLogger.ts#L325 If this doesn't meet your needs, Statsig server SDKs provide a stronger guarantee: `privateAttributes` never leave your server. Server SDKs download the definition of each gate, config, and experiment and evaluate them locally. `privateAttributes` stripped from event logs with the `statsig-node` SDK: https://github.com/statsig-io/node-js-server-sdk/blob/d1cb9431fb68b40f840254fce70363de1dc51aa5/src/LogEvent.js#L21 Evaluation happening locally to the server on `privateAttributes` in the `statsig-node` SDK: https://github.com/statsig-io/node-js-server-sdk/blob/d1cb9431fb68b40f840254fce70363de1dc51aa5/src/Evaluator.js#L374 All Statsig SDKs are open source and [available on GitHub](https://github.com/statsig-io). Review the implementation of `privateAttributes` in the SDK you are using, or reach out on [Slack](https://www.statsig.com/slack) for guidance. {% callout type="info" %} To ensure that user PII is never transmitted over the network back to Statsig during Client SDK initialization, use [Client Bootstrapping](/client/concepts/initialize#bootstrapping-overview) and provide the `privateAttributes` as part of the user object on the server to the `getClientInitializeResponse(<user object>)` call. This generates all of the assignments locally on your server, and you can then pass these assignments as `initializeValues` to the client SDK, eliminating the need to send any user attributes from the client device to Statsig. {% /callout %} ## Event logging The SDK strips the `privateAttributes` field from the user object for any `logEvent` calls on both client and server SDKs. On server SDKs, you can omit the field from `logEvent` calls because no evaluation occurs at log time, so the field isn't needed. If you use the same user object throughout, the SDK handles dropping `privateAttributes` for you. ================================================================================ # AI Governance, Security & Privacy URL: https://docs.statsig.com/compliance/ai_governance_security_privacy Updated: 2026-07-11 ================================================================================ Trust, security, and privacy are central to Statsig's operations. Your data remains confidential, secure, and owned by you across the Statsig platform. ## Governance The AI features on the Statsig platform use your data to provide additional insights, analysis, and solutions for your review. Examples of Statsig AI features include Knowledge Graph, hypothesis advisor, and suggested metrics. By design, Statsig keeps your data separate from other customer data in its production environment. Statsig doesn't mix or process data from different customers together. Statsig doesn't use your data to build or develop any AI models unless you provide explicit written consent. You own the data you provide and control which internal sources are connected to the Statsig platform. You also control who has access to the Statsig platform within your organization. For information on single sign-on, refer to the [SSO overview](/access-management/sso/overview). ## Security Whether you are sending aggregated metrics, custom attributes, or hashed identifiers, or connecting parts of your codebase, Statsig prioritizes security. Statsig's software development lifecycle builds security into offerings at inception. Statsig follows zero-trust and defense-in-depth approaches across its overall security program, and has implemented layered security controls across endpoints, infrastructure, networks, and applications. Statsig uses industry-standard security practices and cryptography to protect your data, including AES-256 encryption at rest and TLS 1.2 or higher in transit. Statsig uses automated alerts and manual investigation processes to address suspicious activity. Systems undergo regular risk assessments and audits, including by independent third parties, to ensure adherence to high security standards. Statsig also maintains a SOC 2 Type II certification. For more information on Statsig's security practices, go to [Security at Statsig](https://www.statsig.com/trust/security). ## Privacy Statsig's data protection practices are designed to support your compliance with GDPR, CCPA, and other applicable privacy laws across the Statsig platform. For cross border data transfers, Statsig complies with the EU-US Data Privacy Framework, the UK Extension to the EU-US Data Privacy Framework, and the Swiss-US Data Privacy Framework. Statsig also provides a [Data Processing Addendum](https://www.statsig.com/legal/online-dpa) to support its customers' data handling requirements. These privacy protections also extend to all AI features. AI features on the Statsig platform use third-party large language models (LLMs). The underlying third-party model providers don't retain, access, or use data processed through these LLMs. Statsig never shares your data with other customers. Statsig's subprocessors are permitted to use data only as directed by Statsig and in accordance with contractual commitments. A list of Statsig's subprocessors is available at [statsig.com/legal/subprocessors](https://www.statsig.com/legal/subprocessors/). ================================================================================ # Infrastructure Ops Overview URL: https://docs.statsig.com/infrastructure/introduction Updated: 2026-07-11 ================================================================================ ## Why infrastructure matters for feature flagging and experimentation Feature flagging and experimentation platforms require robust infrastructure to deliver consistent, low-latency responses that don't impact an application's performance. Statsig must process every feature gate evaluation, experiment assignment, and analytics event reliably to ensure: * **Consistent user experiences**: Users always receive the same feature variant throughout their session. * **Real-time decision making**: Feature flags evaluate in milliseconds to avoid blocking your application. * **Accurate experiment results**: Statsig must capture and process every user interaction to generate reliable statistical insights. * **Business continuity**: Infrastructure downtime can't block feature releases or compromise running experiments. ## Enterprise-grade scale and reliability Statsig processes massive volumes of data while maintaining enterprise-grade reliability standards: ### Key metrics * **2+ Trillion** events processed per day * **Over 3 Billion** unique monthly experiment subjects * **99.99%** infrastructure uptime for API and Console ### Scalable architecture Statsig's infrastructure has been proven at scale by companies including OpenAI, Atlassian, Microsoft, Notion, Flipkart, and other enterprises. All customers get access to enterprise-grade infrastructure from day one. Statsig can scale with you regardless of volume, so there is no reason to switch tools or migrate to a different solution as you grow. Investment in scaled infrastructure also allows Statsig to offer affordable pricing across all products and customer tiers. Infrastructure covers more than serving configs and logging events. Statsig also provides comprehensive infrastructure management including: * Real-time health checks and monitoring * Automated guardrails on feature rollouts and releases * Multi-region deployment for global availability * Built-in redundancy and failover capabilities ## Getting started This section includes documentation about infrastructure setup and operations for your team when using Statsig. By default, Statsig works without additional setup. Some instructions here apply to specific use cases with special requirements. If you have questions, visit the [Statsig Slack channel](https://www.statsig.com/slack). ================================================================================ # Ingesting Cloudflare Logs and Metrics into Statsig URL: https://docs.statsig.com/server/concepts/cloudflare Updated: 2026-07-11 ================================================================================ ## How Cloudflare Logpush integration works This guide walks you through setting up the Cloudflare Logpush worker in your Cloudflare account and configuring it to send logs and metrics to Statsig using curl and the Logpush API. *** ## ✅ Prerequisites * [Cloudflare Logpush enabled for your account](https://developers.cloudflare.com/logs/logpush/) * [Statsig Server SDK Key](/server-core/) *** ## Configuring Logpush worker 1. [Create an API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/). 2. Ensure the token has logs edit permissions at the account level. 3. [Locate your Cloudflare account ID](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/). 4. Run the following command and get the job ID: ``` ACCOUNT_ID=<my account id> CLOUDFLARE_API_TOKEN=<cloudflare token> curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/logpush/jobs" \ --request POST \ --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \ --json '{ "name": "statsig-logpush-job", "destination_conf": "https://api.statsig.com/v1/log_event/cf_log_drain?header_statsig-api-key=<statsig-secret-key>&header_Content-Type=application%2Fjson", "dataset": "workers_trace_events", "output_options": { "field_names": [], "output_type": "ndjson", "batch_prefix": "{\"events\":[", "batch_suffix": "\n]}\n", "record_prefix": "\n {\"info\":{", "record_suffix": "}}", "record_delimiter": ",", "timestamp_format": "rfc3339" } }' ``` * In the Cloudflare dashboard, navigate to *Analytics & Logs > Logpush* and enable the logpush job. ## Verify logs on Statsig Console Explore your logs and metrics at [console.statsig.com](http://console.statsig.com) using the Logs Explorer and Metrics Explorer products under *Analytics* in the sidebar. *** ## 🔗 Resources * [Manage Logpush with cURL](https://developers.cloudflare.com/logs/logpush/examples/example-logpush-curl/) * [Logpush output types](https://developers.cloudflare.com/logs/logpush/logpush-job/log-output-options/#output-types) * [Create logpush API docs](https://developers.cloudflare.com/api/resources/logpush/subresources/jobs/methods/create/) ================================================================================ # Statsig IP Ranges URL: https://docs.statsig.com/infrastructure/statsig_ip_ranges Updated: 2026-07-11 ================================================================================ Statsig reserves the following IP addresses and ranges for use by its services. If you have a network policy configured in your systems, allowlist all of the IPs below, or select IPs based on the direction of network requests. ## Outbound (Statsig -> Your Servers) These IPs are used when Statsig sends requests to your servers or systems. For example, Statsig imports data from your data warehouse that has a network policy allowing only certain IPs. * 20.29.232.235 * 20.190.14.199 * 34.138.242.148/30 (4 addresses) * 34.126.186.120/30 (4 addresses) * 34.168.242.172/30 (4 addresses) * 34.38.207.120/30 (4 addresses) {% callout type="info" %} Webhook requests can be very high volume and Statsig may not initiate them from these IP Ranges. Consider using [Webhook Signatures](/integrations/event_webhook#webhook-signature) to validate webhook requests. {% /callout %} ## Inbound (Your Clients/Servers -> Statsig) These IPs back the domains (e.g. `api.statsig.com`, `featuregates.org`, `statsigapi.net`) of Statsig APIs. * 34.120.214.181 * 34.128.128.0/29 (8 addresses) ================================================================================ # Statsig Domains URL: https://docs.statsig.com/infrastructure/statsig_domains Updated: 2026-07-11 ================================================================================ Statsig uses the following domain names for its services. If you have a network policy configured in your systems, allowlist all of the domains below, or select domains based on the features you use. ## Statsig Console * `console.statsig.com` * `cdn.console.statsig.com` * `console.statsigcdn.com` ## Statsig API Services These domains are used by Statsig SDKs to communicate with the backend for feature gates, dynamic configs, and event logging. They are also used for other Statsig APIs, such as console APIs and integrations. * `api.statsig.com` * `featuregates.org` * `statsigapi.net` * `events.statsigapi.net` * `api.statsigcdn.com` * `featureassets.org` * `assetsconfigcdn.org` * `prodregistryv2.org` * `cloudflare-dns.com` * `beyondwickedmapping.org` Statsig's SDKs may switch between these domains dynamically as part of DNS resolution logic. {% callout type="info" %} **Why such odd names?** Statsig constantly and dynamically updates these domains to prevent overzealous blocking from browser ad blockers. Statsig updates them whenever ad blockers pick up the existing ones. {% /callout %} ### Minimizing the number of allowlisted domains Use the following approach to minimize the number of Statsig domains you need to allowlist. The [networkConfig](/client/javascript-sdk#networkconfig-object) initialization option allows you to specify a single domain for both the initialization server and the log event server. ```js new Statsig.StatsigClient('client-YOUR_KEY', {/* CONTEXT */}, { networkConfig: { initializeUrl: 'https://featureassets.org/v1/initialize', logEventUrl: 'https://prodregistryv2.org/v1/rgstr' } }); ``` ### Server-side APIs For a list of APIs used by backend/server SDKs, allowlist the following: * `api.statsig.com` * `statsigapi.net` * `api.statsigcdn.com` * `prodregistryv2.org` * `idliststorage.blob.core.windows.net` (details below) ### Statsig User Segment Storage API Server SDKs use this domain to download the segment list for your project. If you don't use large ID lists, you don't need to allowlist this domain. * `idliststorage.blob.core.windows.net` ================================================================================ # API Proxy URL: https://docs.statsig.com/infrastructure/api_proxy/introduction Updated: 2026-07-11 ================================================================================ This section provides documentation on the API proxy options available when using Statsig. ## Why use an API proxy Reasons to implement an API proxy for Statsig network requests: 1. **Mitigate tracking blocker impact**: Reduce the effect of tracking blockers on Statsig API usage. Default Statsig endpoints are commonly blocked by tracking blockers, so using a custom proxy with product-specific endpoint names is essential for reliable data collection. 2. **Meet security requirements**: Comply with internal network security and topology standards. 3. **Enhance API availability**: Improve API availability guarantees within your internal network boundary. ## API proxy deployment options You can deploy API proxies in various forms, each with its own advantages and considerations. The choice between a managed service and a self-hosted solution, or deploying outside versus inside your network, depends on your needs. The following options are available: For both server SDKs and client SDKs: * [Custom Proxy](/infrastructure/api_proxy/custom_proxy): A fully customizable proxy that you own and operate in your environment. * [Managed Proxy](/infrastructure/api_proxy/managed-proxy): A lightweight, Statsig-owned proxy that works by default without additional configuration. For server SDKs only: * [Forward Proxy](/server/concepts/forward_proxy): A Statsig-built proxy designed for deployment within your own environment. Choose the option that best fits your infrastructure requirements and operational preferences. If you have questions, join the [Statsig Slack channel](https://www.statsig.com/slack). ================================================================================ # Statsig Managed API Proxy URL: https://docs.statsig.com/infrastructure/api_proxy/managed-proxy Updated: 2026-07-11 ================================================================================ An API proxy gives you a unique URL to send and receive data to and from Statsig servers. This reduces the likelihood of interception by client-side or DNS-side blockers, allowing your applications to receive the correct configuration and send more complete data. {% callout type="info" %} The Managed Proxy is available only for Pro or Enterprise tiers. The Managed Proxy is a quick way to prevent ad-blocking. For lower-volume customers, or as a starting point before you configure the [Custom Proxy](/infrastructure/api_proxy/custom_proxy), the Managed Proxy provides a functional solution. The Custom Proxy is more robust and customizable. {% /callout %} ## Why use a proxy Many web browsers have tracking blockers installed. These blockers can block feature flags, experiments, and runtime dynamic config data, which excludes affected users from statistical analysis. Using an application-specific proxy signals to tracking blockers that the proxy is a required component of your application. ## Set up a managed proxy If your project is on the Pro or Enterprise tier, an option to create a unique proxy for your SDK appears in the *Settings > Project > Keys & Environments* tab: {% figure %} ![Proxy creation option in Keys & Environments settings](/images/infrastructure/api_proxy/managed-proxy/5eec435d-8221-4f8c-a8e8-70b4f67e771c.png) {% /figure %} Selecting **Create a proxy** generates a new unique worker on AWS and returns a URL you can start using immediately. {% figure %} ![Generated proxy URL configuration screen](/images/infrastructure/api_proxy/managed-proxy/0de0509c-1411-4ee4-9a0c-e533ef3fce01.png) {% /figure %} {% callout type="info" %} Statsig hosts the managed proxy in the `ap-south-1` region. If you want it hosted in a different region, reach out to Statsig support. {% /callout %} ## Using your proxy After you set up a proxy, apply its URL to the SDK. For JavaScript client SDKs, use `StatsigOptions.networkConfig.api`. Go to [Statsig Options](/client/javascript-sdk#statsig-options) for JavaScript-specific options. Other SDKs may expose the proxy base URL differently, but the managed proxy URL serves the same purpose. The following example shows initializing with a proxy in JavaScript: ```typescript Statsig.initialize(mySdkKey, myUser, { networkConfig: { api: "https://my-statsig-proxy.com/v1", }, }); ``` {% callout type="note" %} In JavaScript, `networkConfig.api` is a base URL, not the same thing as `initializeUrl`. Use `initializeUrl` only when you want to override initialization independently. There is no generic `api` fallback option; endpoint failover is configured with `initializeFallbackUrls` and `logEventFallbackUrls`. {% /callout %} ================================================================================ # Custom Proxy for Statsig API URL: https://docs.statsig.com/infrastructure/api_proxy/custom_proxy Updated: 2026-07-11 ================================================================================ ## How the custom proxy works Instead of sending API requests directly to Statsig, you can set up your own environment that proxies requests from your custom domain name to Statsig. This reduces the likelihood that tracking blockers intercept your APIs, and allows you to capture more data. These instructions cover a few common service providers. Many other proxy configurations are possible. {% callout type="warning" %} **Important: Default Endpoints Are Blocked** The default Statsig endpoints (like `/v1/log_event`) are commonly blocked by tracking blockers. To ensure your proxy works effectively: 1. **Use a custom endpoint name** specific to your product (e.g., `/v1/my-product-data` instead of `/v1/log_event`) 2. **Rewrite the URL in your proxy** to map your custom endpoint to the actual Statsig endpoint (`log_event`) 3. **Don't use passthrough** for the endpoint path - you must rewrite it Additionally, your proxy shouldn't try to deserialize the payload body. This improves robustness by reducing risk of integration issues from Server SDK -> Proxy -> Client SDK, as well as efficiency of the proxy. For example, client SDKs may change encoding to compress payloads, which breaks if your proxy doesn't accept the new format (e.g. gzip). {% /callout %} ## Approaches ### AWS CloudFront #### Prerequisites * Write access to your DNS settings. * Write access on your AWS CloudFront and Lambda console. * Access to a SSL certificate for your custom domain. #### Setup On your [AWS CloudFront console](https://console.aws.amazon.com/cloudfront/), * Click on Create distribution. * In the Origin section, * Set the Origin Domain to `api.statsig.com`. * Set the Protocol to `HTTPS only`. {% figure %} ![Origin section](/images/infrastructure/api_proxy/custom_proxy/178337858-834c6762-15b4-410d-91bb-68e04932523e.png) {% /figure %} * In the Default cache behavior section, * Set Viewer protocol policy to `Redirect HTTP to HTTPS`. * Set Allowed HTTP methods to `GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE`. * In the Cache Key and origin requests subsection, allow all headers and parameters to be forwarded to the Origin, and allow CORS requests for the Origin. {% figure %} ![Default cache behavior section](/images/infrastructure/api_proxy/custom_proxy/178590547-acdedcb6-e15a-4086-a29f-0657242d9894.png) {% /figure %} * In Function associations section, * Add a Lambda@Edge function to Origin request to rewrite the `Host` header to `api.statsig.com` and rewrite custom endpoint paths to Statsig endpoints. Refer to [the AWS tutorial on creating a Lambda@Edge function](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-how-it-works-tutorial.html). {% figure %} ![Function associations section](/images/infrastructure/api_proxy/custom_proxy/178591897-a93a046a-c76f-4fc6-ab0c-86452de99be7.png) {% /figure %} You can use the following javascript code snippet in your Lambda@Edge function. This example rewrites a custom endpoint `/v1/my-product-data` to the Statsig endpoint `/v1/log_event`: ```javascript export const handler = async (event, context, callback) => { const request = event.Records[0].cf.request; request.headers.host[0].value = "api.statsig.com"; // Rewrite custom endpoint to Statsig endpoint // Replace 'my-product-data' with your custom endpoint name if (request.uri.includes('/v1/my-product-data')) { request.uri = request.uri.replace('/v1/my-product-data', '/v1/log_event'); } return callback(null, request); }; ``` * In Settings, * Add an Alternate domain name (CNAME) to be your preferred domain name to use for the custom proxy, e.g. `statsig.example.com`. * Add a Custom SSL certificate. Follow the [AWS guide for Alternate domain name](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#alternate-domain-names-requirements) to add a SSL certificate. * Click on Create distribution. {% figure %} ![Settings](/images/infrastructure/api_proxy/custom_proxy/178337890-828e9f37-dd28-43a3-adc1-061052916045.png) {% /figure %} * You get a Distribution domain name (e.g. `d111111abcdef8.cloudfront.net`) once AWS provisions it. In your DNS settings (depending on your DNS provider), * Add a CNAME record in your custom DNS record: * Host name: `statsig.example.com` * Type: `CNAME` * Data: `d111111abcdef8.cloudfront.net` (The Distribution domain name from AWS) * Your proxy is now set up. Go to [Using Your Proxy](#using-your-proxy) for instructions on how to configure your Statsig SDK. ### Cloudflare Worker #### Prerequisites You need a Cloudflare account. Visit [https://www.cloudflare.com](https://www.cloudflare.com/) to set one up. #### Setup After you log into Cloudflare, follow these steps: 1. Navigate to "Workers & Pages > Overview" in the left rail to create a new worker. {% figure %} ![1-cloudflare-create](/images/infrastructure/api_proxy/custom_proxy/39bcd1ad-ddcc-4be9-9d71-905ed6a90b8b.png) {% /figure %} {% callout type="note" %} You may see a different experience if you already have workers on your account. {% /callout %} 3. Name your new worker and click "Deploy". {% figure %} ![2-cloudflare-deploy](/images/infrastructure/api_proxy/custom_proxy/9d728e12-675e-4648-b6ee-ce84c72b305c.png) {% /figure %} 4. Once deployed, click "Edit Code". {% figure %} ![3-cloudflare-edit-code](/images/infrastructure/api_proxy/custom_proxy/8c971a58-5bb7-4faa-a7ba-4574ab29f0ce.png) {% /figure %} 5. Copy and paste the following snippet into the `worker.js` file, then hit "Deploy". This example rewrites a custom endpoint `/v1/my-product-data` to the Statsig endpoint `/v1/log_event`: ```javascript export default { async fetch(request, _env, _ctx) { const url = new URL(request.url); // Rewrite custom endpoint to Statsig endpoint // Replace 'my-product-data' with your custom endpoint name let pathname = url.pathname; if (pathname.includes('/v1/my-product-data')) { pathname = pathname.replace('/v1/my-product-data', '/v1/log_event'); } const original = new Request(request); original.headers.delete("cookie"); return fetch( `https://statsigapi.net$\{pathname\}${url.search}`, original ); }, }; ``` {% figure %} ![4-cloudflare-paste-snippet](/images/infrastructure/api_proxy/custom_proxy/558498dd-159f-409e-acef-a31e0dff86c2.png) {% /figure %} 6. Your worker is now deployed. Go to [Using Your Proxy](#using-your-proxy) for instructions on how to configure your Statsig SDK. ## Using your proxy After you set up a proxy, apply its URL to the SDK. For JavaScript client SDKs, use `StatsigOptions.networkConfig.api`. Go to [Statsig Options](/client/javascript-sdk#statsig-options) for JavaScript-specific options. Other SDKs may expose the proxy base URL differently, but the purpose is the same: point the SDK at your proxy instead of Statsig's default domains. The following example shows initializing with a proxy in JavaScript: ```typescript Statsig.initialize(mySdkKey, myUser, { networkConfig: { api: "https://my-statsig-proxy.com/v1", }, }); ``` {% callout type="note" %} In JavaScript, `networkConfig.api` is a base URL. The SDK appends endpoint paths like `/initialize` and `/rgstr` automatically. Use `initializeUrl` only when you want to override initialization independently of the other endpoints. There is no generic `api` fallback option; failover is configured per endpoint with `initializeFallbackUrls` and `logEventFallbackUrls`. {% /callout %} {% callout type="note" %} Depending on the SDK type, version, and proxy approach you are using, you may not need to append `'/v1'` to the end of your api string, for example `"https://my-statsig-proxy.com/"`. {% /callout %} ### JavaScript failover example If you want JavaScript clients to use your proxy as the primary endpoint and a second endpoint as failover, configure fallback URLs per endpoint: ```typescript Statsig.initialize(mySdkKey, myUser, { networkConfig: { api: "https://my-statsig-proxy.com/v1", initializeFallbackUrls: [ "https://my-proxy-cache.com/v1/initialize", ], logEventFallbackUrls: [ "https://my-proxy-cache.com/v1/rgstr", ], }, }); ``` ### Configuring custom endpoints If you configured your proxy to use custom endpoint names (recommended to avoid tracking blockers), configure the SDK to use those custom endpoints. The SDK appends the endpoint path to your base URL. For example, if your proxy rewrites `/v1/my-product-data` to `/v1/log_event`, configure: ```typescript Statsig.initialize(mySdkKey, myUser, { networkConfig: { api: "https://my-statsig-proxy.com/v1", logEventUrl: "https://my-statsig-proxy.com/v1/my-product-data", }, }); ``` Go to your SDK's documentation for the configuration options available for customizing endpoint URLs. ================================================================================ # Forward Proxy URL: https://docs.statsig.com/server/concepts/forward_proxy Updated: 2026-07-11 ================================================================================ ## Statsig Forward Proxy The Statsig Forward Proxy is a service you can host in your own infrastructure. When you configure SDKs to use the forward proxy, the proxy provides a closer and more reliable hop for retrieving Statsig configurations from within your infrastructure instead of reaching out to Statsig's networks. The expected benefits of using the proxy include: 1. Reduced dependency on Statsig Infrastructure Availability 2. Improved performance through localization of download\_config\_spec to your cluster 3. Improved cost savings and reduced network overhead through minimizing requests to Statsig Infrastructure 4. Improved consistency of configurations, as the forward proxy becomes an articulation point for serving configs 5. Support for cool features such as a streaming API through GRPC If you have questions about deployment or need assistance, contact Statsig on Slack or create a new GitHub issue. ### Installation #### Helm Installation The recommended way to install Statsig Forward Proxy in a Kubernetes environment is the official Helm chart. Follow these steps to deploy: ```bash # Add the Statsig Helm repository helm repo add statsig https://statsig-helm.storage.googleapis.com helm repo update # Install the chart helm install statsig-forward-proxy statsig/statsig-forward-proxy ``` The Helm chart provides extensive configuration options for customizing your deployment. For detailed configuration options, go to the [Statsig Forward Proxy Helm chart documentation](https://github.com/statsig-io/statsig-forward-proxy/blob/main/chart/README.md). #### Manual Deployment For environments where Helm isn't available or for more customized deployments, you can deploy Statsig Forward Proxy manually. The proxy is available as a pre-built Docker image, and you can also build your own binary from source. For detailed instructions on manual deployment options and available configuration parameters, go to the [Manual Deployment section in the GitHub repository](https://github.com/statsig-io/statsig-forward-proxy/tree/main?tab=readme-ov-file#manual-deployment). ### How Forward Proxy works The forward proxy works by setting up a local HTTP or gRPC server. When you make requests to the forward proxy, only the initial request is blocking. After that, a background loop keeps configurations up to date without impacting the serving path. The forward proxy also enables backup caches and monitoring through technologies such as Redis and Statsd. ## Integration with SDK {% callout type="info" %} `Legacy Java/Kotlin`, `Legacy Python`, `Python Server Core`, and `Node Server Core` SDKs support Forward Proxy integration with gRPC streaming. For other server SDKs, integrate with the Forward Proxy through HTTP by overriding the initialize endpoints in StatsigOptions. Contact us in [Slack](https://statsig.com/slack) if you need guidance on this approach. {% /callout %} You can configure SDK networking to use different protocols for integration with the Statsig Forward Proxy. The SDK makes requests to three network endpoints: `download_config_specs`, `get_id_lists`, and `log_events`. You can configure `download_config_specs` to use the proxy. Support for the latter two is under active development. #### Network protocol for different endpoints For `download_config_specs` endpoint, where we get specs on evaluating gates/layers/experiments/configs 1. `http`: the default protocol. If the SDK is initialized with http, it polls config updates in a background thread. 2. `grpc_websocket`: Establishes a gRPC streaming connection from the SDK to the Statsig Forward Proxy and listens for updates pushed from proxy servers. You must use the Statsig Forward Proxy or a similar proxy server to use gRPC streaming. Not all SDKs support gRPC yet. If you need support for a specific SDK, reach out on the Statsig support channel on Slack. 3. `grpc`: Unary RPC, behaves similarly to the HTTP protocol. After initialization, the SDK polls changes from the forward proxy server in a background thread. #### Listen to Config Change Example ```typescript const statsigOptions = { proxyConfigs: { download_config_specs: { proxyAddress: proxyAddress // Your proxy address protocol: "grpc_websocket" } } } Statsig.initialize(server_key, statsigOptions) // Statsig will use listen for config updates from statsig forward proxy using grpc_websocket protocol. And use http to get idlists and post log events from statsig servers. ``` For information on your specific SDK language, go to the language-specific docs in the left-hand column. #### Failover behavior **Initialization** SDK behavior remains the same when the forward proxy is in use. Several configurations can improve data availability during initialization. The default behavior is to get from the forward proxy. If a DataAdapter is present, the SDK gets from the DataAdapter first and falls back to the Forward Proxy if no value is returned from the data adapter. Several ways exist to configure this behavior. 1. Set `fallbackToStatsigAPI = true` to fall back to the Statsig API when initialization or config sync from primary sources fails. 2. To customize the initialization order, set `initializeSources` with multiple sources. For example, `initializeSources=[DataSource.Network, DataSource.DataAdapter, DataSource.StatsigNetwork]` fetches from three sources sequentially until one succeeds. **Config Sync (Post initialization)** If the SDK is using a polling model (`grpc` or `http`): 1. Set `fallbackToStatsigAPI=true`. 2. Set `configSyncSources` with multiple sources. For example, `initializeSources=[DataSource.Network, DataSource.StatsigNetwork]` fetches from sources sequentially until one succeeds. Config sync sources don't apply when you are using streaming. If the SDK is using the `grpc_websocket` protocol (listening mode for config changes), by default it will: 1. Retry connecting to the forward proxy with exponential backoff: 10s, 50s, 250s, 1250s, and so on, up to 10 retries. 2. If the 4th retry fails, the SDK starts polling from the Statsig endpoint. Use `StatsigOptions.pollingInterval` to control the polling frequency. 3. While polling from the Statsig endpoint, the SDK continues retrying the forward proxy connection until it succeeds. All of the above behaviors are configurable through `StatsigOptions`. #### Streaming failover in depth When exceptions occur (for example, when a connection is dropped or the forward proxy is down), gRPC returns an error to the SDK, which then starts the failover behavior. The Python SDK checks whether the channel is idle every 2 hours to prevent edge-case behavior in the Python gRPC library. #### TLS: advanced network authentication The forward proxy supports TLS and mTLS when using the gRPC server, so all gRPC network traffic (streaming and unary calls) is encrypted. To enable it: 1. [Setup forward proxy server with valid certifications](https://github.com/statsig-io/statsig-forward-proxy?tab=readme-ov-file#deploying) 2. [Setup SDK with valid certifications (python example)](/server/pythonSDK). {% callout type="note" %} If certificates are misconfigured, the SDK treats it as an exception and starts the failover behavior you configured (for example, falling back to the Statsig API, retrying the connection, and starting fallback behavior). {% /callout %} #### Coordinate SDK DataAdapter / DataStore with Forward Proxy cache service To increase reliability during initialization, configure a DataStore. If the Forward Proxy is unavailable at initialization time, the SDK initializes with values from the DataStore and then syncs from the forward proxy in the background. The SDK and the Forward Proxy should share the same cache service for consistency and to reduce cache I/O. Example on DataStore setup in python: ```py class DataAdapter(IDataStore): def __init__(self): self.redis_cache = ExampleCache() def get(self, key: str): # IDlist isn't currently supported by proxy, so do normal lookup if "statsig.id_lists" in key: return self.cache.get(key) # This logic must stay in sync with statsig-forward-proxy else: hashed_key = "statsig::" + hashlib.sha256(key.encode()).hexdigest() return self.cache.hget(hashed_key, 'config') def set(self, key: str): # Don't implement set method if you are share the same cache between forward proxy and sdk, forward proxy will write to cache pass def shutdown(self): self.cache.shutdown() ``` #### Deployment Notes Because every use case differs, there are no strict requirements. General recommendations include: 1. Understand your general QPS to Statsig. Contact us if you need help. 2. Estimate the throughput a single pod can support, then calculate how much to scale out to handle your traffic. 3. Pre-scale the proxy for your peak QPS before gradually rolling it out to services. 4. Configure auto-scaling. For most deployments, scaling up at 70% CPU and memory utilization is sufficient, but this may need adjustment depending on usage. If horizontal scaling doesn't resolve scaling issues, front the proxy with nginx to help with flow control. For assistance, send a message to the [Statsig Slack channel](https://statsig.com/slack). ================================================================================ # Reliability FAQs URL: https://docs.statsig.com/infrastructure/reliability-faq Updated: 2026-07-11 ================================================================================ Integrating your product with Statsig means depending on Statsig, and Statsig takes reliability seriously. Here are common questions about evaluating the risks. Reach out on Slack if you have questions not listed here. ## What does Statsig do to stay highly available? * Statsig actively tracks internal Service Level Objectives (SLOs) for availability to maintain high uptime. * Measures Statsig takes to ensure service reliability: * Statsig handles bursts through autoscalers and over-provisioned resources. * Mechanisms exist to reduce unintended or malicious spikes and prevent DDoS attacks. * Statsig deploys services in multiple regions. If a region goes down, Statsig routes traffic to other healthy regions. * Statsig uses the GitOps approach (code review, validation, CI/CD) for all infrastructure changes to prevent human errors. * A 24/7 engineering on-call rotation handles customer-facing alerts and issues. ## Does Statsig use any caching to help with latency? * Statsig uses a combination of caching solutions, depending on the problem. For console and API requests, Statsig caches most data at the region or host level. ## What else does Statsig do to make sure the service is resilient? * Statsig designs its SDKs to be resilient if API requests fail. * Client SDKs: * The SDKs use the latest values from the Statsig server when the user can reach the Statsig server. * If the server is unreachable, the SDKs use a cached value from a previous session, if available. * If no cached value exists, the SDKs fall back to default values set in your code, so users receive the default experience. * The SDKs automatically retry failed event requests if Statsig event servers are unreachable. Client SDKs also persist failed log requests to local storage and retry in subsequent sessions. * Server SDKs: * Server SDKs store rules for gates and experiments in memory, so evaluation continues even if Statsig is down. * You can bootstrap your server SDKs with rule values from a previous session if Statsig is down when your server starts. Use a [Server Data Store](/server/concepts/data_store/) to plug a storage provider into the Statsig SDK to store your rule values. * The SDKs automatically retry failed event requests if Statsig event servers are unreachable. ## What kind of automated testing does Statsig do? * Unit and integration tests run on every pull request * Continuous CI/CD running unit and integration test suites * Synthetic tests for Console and API use cases, mimicking customer requests * Stress tests to detect any performance issues * Continuous SDK tests run on every pull request and on schedule ## What does Statsig do to protect runtime code? Statsig uses GitHub and DockerHub for code and binary storage, and tracks the entire CI/CD process from source code to production deployment with traceable versioning and binary verification. ================================================================================ # Paranoid about uptime? 10 things to do! URL: https://docs.statsig.com/guides/uptime Updated: 2026-07-11 ================================================================================ Statsig serves billions of individual user interactions and is designed for reliability and availability. If your application can't reach Statsig for any reason, it continues to work as expected using locally cached values. The following best practices help maximize your uptime across issues including failed client connectivity, failed server connectivity to Statsig, and buggy or deprecated code. You can also read more about how Statsig [designs for failure](https://statsig.com/blog/designing-for-failure). 1. Feature Gates and Experiments have **default values** used during evaluation. You can disable Feature Gates and set a default value; if the SDK has no information, the value defaults to `false`. For experiments, specify default values in code. Validate that these defaults work and don't break the experience. 2. **Test** your code with all possible assignments in Experiments and Feature Gates. Use overrides and inline real-time diagnostics. Identify crashing variants before rolling out your experiment. 3. Use Statsig's support for **pre-production environments** (e.g. Dev, Staging) as part of your validation process. Pre-production environments can remove change approval requirements, allowing faster iteration. 4. **Start small**, validate and then ramp. With feature gates, Statsig recommends rolling out to 2% (check for crashes/obvious bugs) before ramping up to 10%, 50%, and then 100% while watching metrics. You can also have [Statsig fire Rollout Alerts](/metrics/rollout-alerts) when thresholds are violated. 5. **Clean up** your code and remove features you have finished launching. Leaving untested or unmonitored code paths increases risk because those paths can still be triggered. 6. Use **change management** on Statsig in production. A reviewer should approve changes. For critical areas, you can enforce an Allowed Reviewer group that has enough context to decide. Statsig Feature Gates let you easily audit and roll back changes. 7. **Caching on client SDKs**: Initializing Statsig client SDKs requires them to connect to Statsig and download config. Client SDKs can cache and reuse config (for the same user) if they are offline. You can also choose to bootstrap your client from your own server (and remove the round trip to Statsig) by using [client SDK bootstrapping](/client/concepts/initialize#bootstrapping-overview). 8. **Caching on server SDKs**: Initializing Statsig server SDKs requires them to connect to Statsig and download config. If connectivity to Statsig fails, initialization fails (falling back to default values). Two key things can help mitigate this (and related) risks. 1. Deploy the Statsig [Forward Proxy](/server/concepts/forward_proxy/) so your servers connect to this endpoint to download config instead of connecting to Statsig directly. This also reduces network traffic and improves consistency of configuration across your server fleet. 2. Reduce connectivity-related SDK initialization failures by providing config locally using a [dataAdapter](/server/concepts/data_store#dataadapter-or-datastore). 9. **Test for failure conditions** explicitly (for example, no Statsig client or server connectivity). Run a disaster simulation such as breaking DNS routing to Statsig within your data center, and test client app behavior when Statsig is unreachable. Go to the [testing](/guides/testing) guide for available capabilities. 10. **Implement a custom proxy** to prevent adblockers from blocking events or initialization in client SDKs. Adblockers can interfere with Statsig's default endpoints, preventing your application from logging events or initializing properly. By setting up a [custom proxy](/infrastructure/api_proxy/custom_proxy) on your own domain with custom endpoint names, you ensure that tracking blockers don't intercept your API calls and that you capture all necessary data. ================================================================================ # Monitoring the SDK URL: https://docs.statsig.com/infrastructure/sdk-monitoring Updated: 2026-07-11 ================================================================================ {% callout type="note" %} This latest release of structured logging and metrics is available for the Python SDK only. Want it in another? Reach out in the [Support Slack](https://statsig.com/slack). {% /callout %} ## SDK metrics Some Statsig SDKs provide built-in metrics to help you monitor performance and impact on your application. The specific implementation varies by programming language. Refer to the documentation for the language-specific SDK interface. ### Metric interface methods The following interface methods are provided by the Statsig SDK to track various metrics: * **Initialization (`init`)**: Called on SDK initialization. Allows you to initialize your observability client (such as StatsD or OpenTelemetry), preparing the SDK to send metrics and logs to the chosen observability tool. * **Shutdown (`shutdown`)**: Called on SDK shutdown. Allows you to perform any actions to ensure a graceful shutdown of the observability client, such as ensuring that pending metrics or logs are sent before the SDK stops. * **Counter**: Tracks occurrences of specific events. * **Gauge**: Records point-in-time values, such as the number of active connections or other metrics that don’t accumulate over time. * **Distribution**: Tracks distributions of numerical data over time, such as latency or response times. * **Should Enable High Cardinality Tags**: Called for high cardinality tags. Allows you to define whether certain high cardinality tags (which can generate large data volumes) should be enabled for detailed tracking. By default, Statsig disables all high cardinality tags. ### List of metrics The SDK provides the following primary metrics: | **Metric Name** | **Type** | **Tags** | **Description** | |-------------------------------|----------------|---------------------------------------------------|---------------------------------------------------------------------------------------------------| | `initialization` | distribution | `success`, `init_source`, `init_source_api`, `store_populated` | Tracks SDK initialization duration. | | `config_propagation_diff` | distribution | `source`, `source_api`, `lcut*`, `prev_lcut*` | Measures the time difference between the last config updated time vs the time that sdk received the config. | | `config_no_update` | counter | `source`, `source_api` | Tracks occurrences of no configuration updates. | | `events_successfully_sent_count` | counter | N/A | Tracks number of events sent successfully to the Statsig server. | | `sdk_exceptions_count` | counter | N/A | Tracks occurrences of unexpected exceptions caught. | | `grpc_received_message` | counter | N/A | GRPC Streaming received a new message | | `grpc_reconnected` | counter | N/A | GRPC streaming reconnected | | `grpc_streaming_failed_with_retry_ct` | distribution | N/A | Streaming failed and the current retry count (to estimate how long the client has been disconnected) | {% callout type="note" %} * Statsig prefixes all metrics with `statsig.sdk.`, for example, the full initialization metric name in your integration is `statsig.sdk.initialization`. * While `sdk_exceptions_count` metric captures all exceptions, certain errors (e.g., temporary network connectivity issues or timeouts) occasionally occur and generally don't indicate a problem. Use this metric to identify unexpected or persistent issues that may require investigation. * Tags marked with `*` (such as `lcut` and `prev_lcut`) are high cardinality tags. {% /callout %} ### Metric tags High cardinality tags can generate large data dimensions when enabled. Statsig disables these tags by default, but you can enable them through the `Should Enable High Cardinality Tags` method on the observability client interface. High cardinality tags include: * `lcut`: The last configuration update timestamp. * `prev_lcut`: The previous configuration update timestamp. Metric Tags: * `source`: The source of the configuration update, such as network/bootstrap/datastore. * `source_api`: The API endpoint used to fetch the configuration update. * `success`: Indicates whether the initialization was successful. * `store_populated`: Indicates whether the configuration store was populated.