Skip to main content

TL;DR

This guide turns a reversible signup-flow change into one defensible A/B test in a React and TypeScript application. It covers hypothesis and sample-size checks, stable browser assignment, safe control fallback, activation-time exposure, outcome instrumentation, Managed Warehouse and bring-your-own-warehouse paths, health checks, interpretation, and cleanup. The 0 → 1 → 100 path proves that the experiment is worth running and that exposure joins to outcomes, launches one controlled experiment, and finishes with a documented ship, iterate, or stop decision plus removal of temporary experiment code. If traffic, consent, identity, or measurement cannot support a valid test, reject the experiment. This guide is optimized for AI coding agents, and it is recommended that you hand it off to your agent of choice for implementation.

Guide map

Give this guide to your coding agent

Copy this page’s URL and the prompt below into a coding agent that can inspect your repository. Replace REPLACE_WITH_REPOSITORY with the repository path or name and REPLACE_WITH_GUIDE_URL with this page’s public URL. Start in a branch, worktree, or disposable clone, and use a non-production GrowthBook environment and synthetic or approved test events for the first pass.

Task

Add a valid A/B test to an existing React application without paying for an experimentation plan. The application assigns each eligible browser to one signup flow, keeps that assignment stable, records exposure when the flow is actually rendered, records signup outcomes with the same identifier, and produces enough evidence to make a ship, iterate, or stop decision. This is an implementation guide for a coding agent. It does not stop after installing an SDK or rendering 2 components. It covers the decisions and evidence that turn a random split into an experiment:
  • A falsifiable hypothesis and a predeclared decision rule.
  • A defensible experimental unit.
  • A traffic and sample-size feasibility check.
  • Stable assignment and safe fallback behavior.
  • Separate assignment, exposure, activation, and outcome concepts.
  • Managed Warehouse and bring-your-own-warehouse data paths.
  • SQL receipts that prove events can join before results are trusted.
  • Sample ratio mismatch (SRM), multiple-exposure, and bias checks.
  • Bayesian and fixed-horizon frequentist interpretation.
  • Rollout, rollback, and removal of temporary experiment code.
The reference change tests whether a streamlined signup flow improves signup completion. Replace the feature name, variants, and outcome with values from the repository you are working in. Keep the architecture and verification sequence. An AI coding agent receiving a prompt such as “is there any free experimentation I should set up?” should not invent a test merely because it can write a random branch. It should first determine whether a useful experiment exists. If it does, this guide takes the repository from 0, to 1 correct experiment, to a repeatable production workflow.

Use this guide when

  • You have 2 behaviors that can coexist behind one code-level feature flag.
  • You can define one primary outcome that would change a product decision.
  • You can assign a stable user, account, or anonymous browser identifier.
  • The treatment is reversible and both variants are safe enough for production.
  • You have enough eligible traffic to detect the smallest effect worth acting on.
  • You want GrowthBook Cloud Starter with Managed Warehouse, or the free GrowthBook path connected to an existing warehouse.

Do not use this guide when

  • There is no decision to make. If the new path is required for legal, security, or platform compatibility reasons, validate it and release it. Do not randomize it.
  • The treatment is not safe. Fix correctness, accessibility, security, and data-loss risks before exposing real users.
  • Traffic is too low. A test that needs 6 months to detect a useful effect will usually accumulate product changes, seasonality, and implementation drift faster than evidence.
  • The variants change several unrelated things. If the treatment changes pricing, onboarding, and navigation together, the result cannot identify the mechanism.
  • Users influence each other across variants. A marketplace, collaboration product, classroom, or team workflow may require account-, group-, or cluster-level assignment instead of user assignment.
  • You cannot observe the outcome. A rendered treatment without a trustworthy conversion or quality event is a rollout, not an analyzable experiment.
  • The outcome is too delayed for the operating window. If the meaningful outcome arrives 6 months later, use an earlier validated proxy or another research design.
  • Consent or policy forbids the required tracking. Do not generate a persistent anonymous identifier or send events until the application has the required consent or legal basis.
When a test is inappropriate, choose the smallest fitting alternative: a unit test, usability study, internal canary, percentage rollout, interrupted time-series analysis, or a direct release with operational monitoring.

Tested stack

The code in this guide targets a client-rendered React application created with Vite and TypeScript. It was verified against the GrowthBook JavaScript and React SDK source at commit e44a15af063860c7118f52508746356d55e5a91d, where both packages are version 1.7.0. The same design works in other JavaScript frameworks, but the location of initialization and the moment of exposure will differ. For Next.js, Remix, or another server-rendered framework, do not copy the browser-only bootstrap blindly. Use the framework-specific SDK guidance and preserve the identity, event, and decision contracts in this guide.

Required access

You need:
  • Read and write access to the application repository.
  • Permission to install npm packages and run the build and test suite.
  • A GrowthBook organization with permission to create a data source, SDK connection, feature, metric, and experiment.
  • Access to GrowthBook SQL Explorer for the Managed Warehouse path, or read access to the existing analytics warehouse for the BYOW path.
  • The application’s analytics-consent policy and current identifier conventions.
  • A product owner who can approve the hypothesis, minimum detectable effect, and final decision.
The GrowthBook client key is designed for SDK use and will be present in the browser bundle. It is not an admin API secret. Never put a GrowthBook secret API key, warehouse password, or service credential in a VITE_* variable.

Files the agent will create or change

The reference implementation creates or changes these files:
It also creates these GrowthBook resources:

End state

You are done only when all of the following are true:
  1. signup-flow returns only control or streamlined, and its code fallback is control.
  2. A stable device_id is available before the flag is evaluated.
  3. Repeated evaluations for the same device_id return the same variation.
  4. The feature is evaluated on the signup route, not globally on application boot.
  5. Rendering the feature under an experiment rule records one deduplicated Experiment Viewed exposure with tracking key signup-flow-v1.
  6. Signup Completed records the same device_id used for assignment.
  7. SQL receipts show exposures and outcomes in the intended data source and prove they join by identifier.
  8. The observed traffic split has no unresolved SRM warning.
  9. Multiple exposures and variation-ID checks are clean.
  10. The primary metric, target effect, minimum runtime, sample target, and stopping rule were written before launch.
  11. The result is interpreted at the predeclared horizon, not the first moment a dashboard turns green.
  12. The final decision is recorded, the experiment rule is stopped, and the losing path has an owner and removal date.

Rollback

Rollback has 3 layers. Keep them independent so an incident does not require a code deployment.
  1. Behavior rollback: In GrowthBook, replace or disable the experiment rule and serve control to everyone. Verify the application returns the control experience for a known test identifier.
  2. Analysis rollback: Mark the experiment phase invalid if assignment or tracking was broken. Do not delete evidence. Fix the cause and start a new phase or experiment with a new seed and documented start time.
  3. Code rollback: Revert the application commit only if the shared feature wrapper or event code is itself unsafe. A config-fetch failure already falls back to control, so ordinary treatment rollback should not require this step.
Stopping or finishing an experiment in the analysis UI does not necessarily stop the feature rule that serves it. Stop both the analysis and the assignment rule, then verify the effective feature value.

Understand the system before changing code

A useful A/B test is a chain of contracts, not a random-number function:
If one arrow breaks, the result may still look precise while answering the wrong question. The reference architecture is:
Normal feature evaluation is local after the SDK has a payload. The event send is a separate asynchronous operation. This distinction matters during failure handling: an ingestion outage should not block signup, and a feature-payload failure should return the code fallback.

Separate assignment, exposure, activation, and outcome

These terms are easy to collapse. Keep them distinct:
  • Assignment is the deterministic mapping from an identifier to a variation.
  • Exposure means the unit actually encountered the treatment boundary. In this guide, evaluating signup-flow inside the signup component triggers the SDK experiment event.
  • Activation is an optional, pre-treatment condition used to restrict analysis when assignment must happen earlier than meaningful exposure. It must not be influenced by the treatment.
  • Outcome is behavior after exposure, such as completing signup.
Do not log exposure when the SDK initializes in main.tsx. Initialization only downloads definitions. The experiment event occurs when code evaluates a feature whose first matching rule is an experiment. Do not use Signup Started as an activation metric for this experiment if the streamlined flow can change whether a visitor starts. Filtering on a post-treatment event can select different populations in each variation and bias the estimate. Evaluate the feature at the moment the signup UI is rendered instead.

Step 0: Decide whether the repository contains a valid experiment

Do this before opening GrowthBook.

Inspect the repository

From the application root, collect evidence about the existing stack and instrumentation:
If rg is unavailable, use the repository’s normal search tool. Do not assume that id, anonymousId, and userId mean the same thing. Trace each identifier from creation through event ingestion. Write down:
If the application already has a durable anonymous identifier that reaches the warehouse, reuse it. Creating a second device ID often produces unjoinable event streams and inflates unique-user counts.

Write the hypothesis as a decision contract

Use one sentence:
This contract specifies:
  • Population: first-time visitors who render signup.
  • Treatment: 3 steps versus 1 page.
  • Primary metric: signup completion within 24 hours.
  • Baseline: 10.0%.
  • Minimum worthwhile effect: 1.5 absolute percentage points, or 15% relative.
  • Harm boundary: no more than 0.5 absolute points of primary-metric harm, plus an operational error guardrail.
  • Decision time: a fixed sample horizon and minimum runtime.
Do not write “the new signup page will perform better.” That statement does not define how much better matters, which population counts, or what result changes the decision.

Choose the experimental unit

The experimental unit is the entity independently assigned to a treatment. The assignment attribute in GrowthBook must represent that entity. Use this test:
Could 2 candidate units receive different variants without contaminating each other’s experience or outcome?
For the reference signup flow, the visitor is anonymous before conversion. A first-party device_id is a reasonable unit if one person using 2 devices can legitimately count as 2 independently treated browsers. It is not perfect person-level identity. State that limitation in the decision record. Choose a different unit when the treatment operates elsewhere: Never assign on a value that changes during the outcome window. Assigning anonymous visitors on device_id, then analyzing only user_id after signup, silently drops non-converters and biases the result. This guide keeps device_id on both pre- and post-signup events. The authenticated user_id is additive.

Check traffic feasibility before building

Estimate the sample needed to detect the minimum worthwhile effect. The following approximation uses a 2-sided alpha of 0.05, 80% power, equal groups, and a binary outcome. It is a planning estimate, not a replacement for a reviewed analysis plan. Create scripts/estimate-signup-sample.mjs temporarily or run the body in a Node REPL:
Expected planning output:
The formula follows the normal approximation for comparing proportions. Review the assumptions when conversion is rare, traffic is clustered, groups are unequal, or the unit contributes repeated outcomes. The NIST sample-size guidance for proportions documents the relationship among effect size, significance level, and power. Estimate eligible units from the exact entry population, not total site traffic. If only 100 eligible browsers arrive per day, the same test needs roughly 134 days before allowing for bot filters, event loss, or the 24-hour conversion window. That is a strong reason to test a larger change, use a more frequent valid metric, or skip the experiment. Do not reduce the target effect after launch because the dashboard is inconclusive. Changing the target after seeing results changes the decision rule. Stop, record the invalidated plan, and design a new test if the business threshold genuinely changes.

Choose the data path

GrowthBook analyzes experiment assignments and metrics in a data warehouse. Pick one path before writing event code: See Choose Your Data Path for the current architecture and plan comparison. Each experiment uses one data source. Do not send exposure to Managed Warehouse and outcomes only to a separate warehouse, then expect one analysis to join them. The canonical implementation below uses Managed Warehouse. The BYOW replacement appears later.

Step 1: Provision the Managed Warehouse and SDK connection

Create the data source

In GrowthBook Cloud:
  1. Open Metrics and Data → Data Sources.
  2. Choose Managed Warehouse and click Create.
  3. Record the selected data region: us-east-1 or eu-west-1.
  4. Open the generated data source and confirm these tables exist: events, experiment_views, and feature_usage.
  5. Open SQL Explorer and confirm a read-only query runs.
The Managed Warehouse provisions a ClickHouse data source, an Events fact table, built-in user_id and device_id identifiers, and starter metrics. The complete data shape is documented in Managed Warehouse.

Create the SDK connection

  1. Open SDK Connections.
  2. Click Add SDK Connection.
  3. Select JavaScript or React and the production environment.
  4. Copy the public client key, which begins with sdk-.
  5. Under the connection’s Attributes, ensure device_id exists as a string and is marked as an Identifier.
  6. Add user_id as a string identifier if the application adds it after signup.
  7. Add app_environment and app_version as non-identifier string attributes only if you will use them for diagnostics.
Attribute names are an interface between code, SDK rules, event ingestion, assignment queries, and metrics. Treat a rename as a data migration.

Install the SDK packages

Pin the versions while implementing so a later release does not change the test fixture during review:
Keep the repository’s existing package manager and lockfile. If it uses pnpm or Yarn, translate the install command instead of introducing a second lockfile. After installation, verify the resolved versions:
Expected result:

Add public environment configuration

Create .env.example:
For a Managed Warehouse in eu-west-1, use:
Sending events to the wrong regional host means they do not reach the ClickHouse cluster attached to the data source. Do not infer the region from the user’s browser. It is an organization-level configuration value. Add the real values to the deployment platform’s environment configuration. Commit .env.example; do not commit .env.local. Create src/vite-env.d.ts so misspelled environment variables fail during type checking:

Step 2: Implement a stable anonymous identifier

The identifier must exist before signup-flow is evaluated and must survive reloads through the conversion window. This implementation uses a host-only, first-party cookie. It does not put email, name, IP address, or another direct personal identifier in GrowthBook. Create src/experimentation/device-id.ts:
This code assumes the application is allowed to create the measurement cookie. If consent is required, call getOrCreateDeviceId() only after the consent manager reports an allowed state. Before consent, render control without running or tracking the experiment. Do not substitute a new in-memory identifier on every page load; that re-randomizes returning visitors. If the app already maintains a durable anonymous ID, delete this file and pass the existing value as device_id. If experiments cross subdomains, a host-only cookie is not enough. Use the same parent-domain cookie policy as the existing identity system and test redirects explicitly. A new ID on the destination subdomain can cause re-bucketing and SRM.

Step 3: Initialize GrowthBook with controlled event schemas

Create src/experimentation/growthbook.ts:
The important behavior is deliberate:
  • device_id exists before feature evaluation.
  • The public client key loads the SDK payload.
  • The event plug-in automatically emits Experiment Viewed and Feature Evaluated and powers growthbook.logEvent().
  • The event filter rejects accidental custom event names. It does not make unsafe properties safe, so the event functions below use a closed schema.
  • The URL drops query parameters, which may contain email addresses, invitation tokens, or campaign data that should not be copied without review.
  • init() waits at most 1.5 seconds. It returns an object rather than throwing for ordinary fetch failures.
  • A missing payload causes useFeatureValue("signup-flow", "control") to return the explicit control fallback.
  • Streaming is off for the first implementation. Add it only when the application needs it and after testing lifecycle behavior.
The tracking plug-in maps a string user_id attribute to the top-level warehouse user_id column. It maps device_id, anonymous_id, or id to the top-level device_id column, preferring an explicit device_id. Use one key for one ID space. Never put a logged-in user ID in id and later introduce a browser ID in device_id; that mixes identifier semantics. The plug-in batches events, uses fetch with credentials: "omit", keeps eligible browser requests alive across navigation, and uses sendBeacon during unload when available. Browser analytics delivery is best effort: never make a successful signup or its navigation wait for it. Keep server-confirmed business outcomes in the application database or existing analytics source of truth, and reconcile browser completion loss before trusting experiment results.

Step 4: Define narrow outcome events

Create src/analytics/signup-events.ts:
These functions cannot accept an email, display name, password, free-form error message, or request body. That is intentional. Store operational debugging detail in the application’s approved logging system with its own access and retention controls. The flow property is diagnostic. Do not calculate experiment lift by filtering this property. The authoritative variation comes from the experiment exposure table. If application code and exposure data disagree, investigate rather than making the outcome property the source of truth.

Step 5: Evaluate the feature at the treatment boundary

Create src/analytics/best-effort.ts. This boundary catches synchronous and asynchronous analytics failures without waiting for delivery. An analytics outage must not turn a completed signup into an error or a permanently submitting form.
src/analytics/best-effort.ts
The signup component is the first place that needs the feature value. That is where the application should evaluate it. Do not evaluate every experiment in a global provider “for convenience.” Broad evaluation adds people who never encounter the treatment, dilutes effects, consumes event allowance, and makes activation filtering necessary. Create src/features/signup/SignupExperiment.tsx:
The 2 example forms intentionally use the same API contract. In a real application, preserve all validation, accessibility, abuse-prevention, and security behavior in both paths. An experiment must not weaken password policy or omit legal consent to create a visually simpler treatment. Signup Started records once per mounted component after first form focus. It is useful for diagnosing funnel behavior, but it is not the denominator of the canonical proportion metric. The experiment exposure is the denominator because the hypothesis is about visitors who render the signup treatment. The API creates the account before the browser records completion. This is appropriate for a UI experiment receipt, but the application database remains the durable source of account creation. For stronger delivery guarantees, emit the completion event from the server-side account transaction into the same warehouse with the same device_id, passed through a validated request field or first-party session. Do not block account creation on a third-party analytics response.

Mount the feature only on the eligible route

Create or update src/App.tsx:
Use the repository’s router in production. The invariant is what matters: the component calling useFeatureValue() must only mount for the eligible population.

Initialize before the first render

Create or update src/main.tsx:
Waiting prevents a visible control-to-treatment flicker. The maximum wait is bounded by the SDK initialization timeout. If initialization times out, the app renders the control fallback and logs a diagnostic warning. React Strict Mode may render components more than once in development. The SDK deduplicates repeated feature and experiment events for the same evaluation context. Outcome events are custom events and are not automatically deduplicated, so the form handler must not emit Signup Completed twice. The production API should also use its own idempotency control for account creation.

Build before configuring production traffic

Run the repository’s checks:
If the repository uses different script names, inspect package.json and run the equivalent type-check, unit-test, and production-build commands. Do not publish an experiment rule to compensate for code that does not build.

Step 6: Create the feature and experiment rule

Create a string feature

In GrowthBook:
  1. Open Features and click Add Feature.
  2. Set the key to signup-flow. Feature keys cannot be renamed after creation.
  3. Choose String as the type.
  4. Set the default value to control in every environment.
  5. Leave production enabled with no rule until the code containing the fallback and both paths is deployed.
A Boolean flag can represent old versus new, but a string flag makes the variation contract explicit and leaves room for a separately named follow-up without converting true and false into ambiguous semantics. Deploy the dormant code first. Verify that production still serves control with no experiment rule. This separates deployment risk from treatment risk.

Add the experiment rule

After the dormant deployment is verified:
  1. Open signup-flow and create a draft revision.
  2. Click Add Rule → Experiment.
  3. Set the tracking key to signup-flow-v1.
  4. Set Assign variations based on attribute to device_id.
  5. Map variation 0 to control and variation 1 to streamlined.
  6. Set overall exposure to 100% and relative weights to 50% / 50% for the canonical test.
  7. Limit the rule to the production environment and any explicit eligibility targeting.
  8. Review, publish, and record the exact UTC publication time.
Keep relative variation weights fixed for the whole experiment. If risk requires a smaller initial audience, keep the 50/50 relative split and reduce overall exposure. You can increase overall exposure later without moving already included units between variations. Changing 90/10 to 50/50 mid-experiment can move assignments and create multiple exposures. GrowthBook hashes the experiment seed with the assignment attribute. The mapping is deterministic for the same phase and identifier. See Feature Flag Experiments and Feature Flag Rules for current rule behavior.

Confirm the feature payload before trusting events

Open the SDK connection endpoint shown in GrowthBook or inspect the request to cdn.growthbook.io in browser DevTools. Confirm the signup-flow definition contains an experiment rule and the expected values. Then open /signup with a clean browser profile and check:
Do not use one browser profile to prove traffic balance. Stability means that profile should remain in one variation.

Step 7: Prove the Managed Warehouse data path with SQL

Wait for test events to arrive, then use SQL Explorer. Replace the tracking key only if you deliberately changed it.

Confirm exposure counts and units

Expected result after testing with separate clean profiles:
You may need several fresh profiles to reach both deterministic buckets. Do not modify one profile’s cookie during a real phase to force it into the other group.

Confirm custom events and identifier coverage

missing_device_id must be 0. A completion with no assignment identifier cannot contribute to this device-level experiment.

Confirm exposures join to outcomes

This query takes each device’s first recorded exposure and checks for a completion in the following 24 hours:
This is a data-path receipt, not the final experiment analysis. GrowthBook’s generated metric query handles configured windows, health exclusions, and statistical aggregation. The receipt must nevertheless show plausible denominators and at least one known test completion.

Check for multiple exposures

Expected result: zero rows. If rows appear, do not interpret lift. Common causes include a changing cookie, changed relative weights, mixed assignment attributes, cross-subdomain identity loss, or an assignment query that aliases the wrong ID.

Inspect event properties without exposing raw form data

Only the closed properties defined in signup-events.ts should appear. If an email, password, full URL query, free-form error, access token, or request body appears anywhere in the event, stop the experiment, remove the field, and follow the organization’s incident and deletion policy.

Step 8: Define the fact metric and analysis

The Managed Warehouse creates an Events fact table. Build the primary metric on it rather than writing a one-off metric query.

Create the primary proportion metric

  1. Open Metrics and Data → Fact Tables → Events.
  2. Click Add Metric.
  3. Choose Proportion.
  4. Name it Signup completed.
  5. Set the goal to Increase.
  6. Add a row filter: event_name = Signup Completed.
  7. Set a 24-hour conversion window if signup is expected to complete within one day.
  8. Set a minimum data threshold that prevents interpretation of tiny counts.
  9. Save the metric and use its preview to verify recent completions.
A proportion metric measures the percentage of exposed units with at least one matching row. Repeated Signup Completed events from one device still count that device once. See Metrics and Fact Tables and Metric Examples for current metric semantics. If the business outcome is revenue, sessions, or another non-binary quantity, do not coerce it into a proportion metric to stay on the free path. Use the metric type that represents the decision and check its current plan availability. A free but invalid metric is not useful.

Create the experiment record

The feature rule may create or link an experiment automatically. If it does not:
  1. Open Experiments and click Add → Create New Experiment.
  2. Set the name to Streamlined signup flow.
  3. Paste the predeclared hypothesis.
  4. Set the assignment attribute to device_id.
  5. Set the experiment key to signup-flow-v1.
  6. Select the Managed Warehouse data source and its experiment assignment query.
  7. Add Signup completed as the single goal metric.
  8. Confirm the baseline variation is control and the comparison is streamlined.
  9. Set the analysis start time to the recorded rule publication time.
The experiment key, feature rule tracking key, and exposure experimentId must match exactly. Variation IDs must match the values represented by the experiment record. A variation-ID mismatch is a data-quality failure, not a naming inconvenience. Do not add every available metric as a goal. One primary goal limits metric shopping and makes the decision legible. Track client error rate, API error rate, latency, and abuse signals as operational guardrails in the application’s monitoring system on Starter. If the current plan supports dedicated guardrail metrics, add only the predeclared ones and keep the external operational alerts.

BYOW branch: Keep the existing event pipeline

Use this branch instead of the Managed Warehouse tracking plug-in when the application already sends trustworthy product events to a supported warehouse. Do not run both implementations for one experiment unless you intentionally maintain 2 independent analysis systems. The GrowthBook SDK still performs assignment. Your existing analytics client records exposure and outcomes. GrowthBook queries the warehouse read-only.

Define one warehouse event contract

Adapt this interface to the repository’s analytics client:
The physical warehouse table should preserve at least:
Generate event_id in the existing pipeline and deduplicate retries there. Do not use (device_id, event_name) as a unique key; the same device can validly start signup more than once.

Replace the Managed Warehouse plug-in with a tracking callback

The BYOW version of src/experimentation/growthbook.ts should use the existing analytics client:
SDK 1.7.0 passes a third user argument to trackingCallback. Read identifiers from user.attributes, because those are the attributes used at evaluation time. Reading a mutable global user object can record an identity different from the one the SDK hashed. Replace growthbook.logEvent() in signup-events.ts with analytics.track() using the same context() helper. Better yet, route both exposure and custom events through the application’s existing typed analytics module so they share retries, consent, timestamps, and identifier rules.

Configure the assignment query

On Metrics and Data → Data Sources, add or edit a device-level experiment assignment query. Adapt column and JSON syntax to the warehouse:
The query must return exactly the identifier column plus timestamp, experiment_id, and variation_id. Return repeated legitimate exposure rows; GrowthBook uses them to detect units exposed to more than one variation. Define a fact table over outcomes:
Then create the same Signup completed proportion metric filtered to event_name = 'Signup Completed'. If exposure uses device_id but the only durable completion has user_id, add an identifier join table that maps both IDs from login or account creation. The better canonical design keeps device_id on the completion event as shown. Never join all anonymous devices to a user retroactively without reviewing how shared devices, account switching, and historical identity affect the experiment unit.

Run warehouse receipts before launching

Use the warehouse’s SQL dialect to prove:
Also join first exposure to completion using the exact fact-table logic. Compare a handful of event IDs to application logs. GrowthBook can only analyze the data the warehouse returns; a successful SDK callback does not prove warehouse ingestion or SQL aliases.

Step 9: Add tests that protect assignment and event semantics

Tests should prove properties, not hard-code a bucket produced by an undocumented hash calculation.

Test that analytics cannot block signup navigation

Create tests/best-effort.test.ts:
tests/best-effort.test.ts
Run npx vitest run tests/best-effort.test.ts. Also exercise the real signup flow in a browser with the ingestor request deliberately stalled, rejected, and blocked. Successful account creation must still navigate without an analytics wait; genuine account-creation failures must still display the form error. Check durable signup counts against browser completions and investigate differential event loss between variations.

Test deterministic assignment and exposure deduplication

Create tests/growthbook-assignment.test.ts:
This fixture uses the same feature-definition shape the SDK consumes. It does not call GrowthBook Cloud and must not replace a live payload receipt.

Test the closed event schema

Create tests/signup-events.test.ts:

Add browser and API tests

The unit tests do not cover cookie persistence, real SDK payloads, navigation, or ingestion. Add the following cases to the repository’s browser suite:
Mock the SDK payload to exercise both variations deterministically. Keep one smoke test against a non-production SDK connection so payload shape, client key, and region configuration cannot drift unnoticed. Test the signup API separately for idempotency. A double click, retry, or back-button submission must not create 2 accounts. Analytics deduplication cannot repair a duplicated business transaction.

Step 10: Respect free, Pro, and Enterprise boundaries

Plan packaging changes. Verify current GrowthBook pricing immediately before publishing this guide or promising a capability. As verified on 2026-08-12, the relevant boundaries are: The canonical test needs only a string feature, feature-flag experiment, proportion metric, Bayesian or fixed-horizon frequentist analysis, and SRM detection. Those are on the Starter path. Do not tell a Starter user to “enable sequential testing,” “turn on CUPED,” or “use a Safe Rollout.” On Starter, use a fixed horizon for frequentist analysis, plan the sample with a documented external calculation, and monitor operational harm in the application’s existing alerting system. Upgrade features can reduce variance, formalize decisions, or automate release monitoring, but they do not make a weak hypothesis or broken identifier valid.

Budget Managed Warehouse events

Unlimited experiment traffic does not mean unlimited Managed Warehouse ingestion. The current GrowthBook Get Started page states a Starter allowance of up to 1 million events per month and a Pro allowance of 2 million before paid overage. The exact allowance and price are volatile; confirm the organization’s usage screen and pricing page before launch. The Managed Warehouse limit behavior is especially important on a free plan: when the limit is reached, event tracking stops for the remainder of the month and resets the next month. An experiment that keeps assigning users after outcome ingestion stops produces unusable results. Estimate monthly volume before launch:
Reloads and separate SDK instances can produce additional exposures even though events are deduplicated within an instance. Query real staging or canary data rather than assuming exactly 2 events per visitor. Create a usage alert with enough headroom to stop the experiment cleanly before ingestion stops. Do not send large form payloads. In addition to privacy risk, browsers impose a shared size quota on keepalive requests. The current JavaScript tracking plug-in avoids keepalive for batches near that limit and attempts unload-safe delivery, but small typed events remain the reliable design.

Step 11: Launch with a preflight record

Commit a short analysis plan beside the product specification or experiment record before publishing the rule:
Use aliases or internal IDs instead of real email addresses if this document could be public. The example .invalid addresses are placeholders.

Run the prelaunch checklist

Before production assignment begins, verify:
  • Both variants pass accessibility, visual, API, and abuse-prevention tests.
  • control is behaviorally equivalent to the pre-experiment path.
  • The deployed application returns control when the SDK endpoint is blocked.
  • device_id is generated or retrieved before feature evaluation.
  • Consent behavior matches policy in every supported region.
  • The SDK connection targets the correct environment.
  • The event ingestor host matches the Managed Warehouse region.
  • A known test exposure and completion join in SQL.
  • The experiment key and variation IDs match across feature rule, event table, assignment query, and experiment record.
  • Internal employees, synthetic monitoring, automated tests, and known bots are excluded consistently if they are outside the target population.
  • Operational alerts identify variation where possible without using the untrusted outcome property as assignment truth.
  • The rollback owner can serve control without deploying code.
For a higher-risk first implementation, run an A/A test in which both variations execute identical code while the complete assignment and analysis path runs. An A/A test can reveal SRM, event loss, and identity problems. It cannot prove the treatment is safe or that the future metric will have enough power.

Ramp exposure without changing weights

If the new UI passed preproduction tests but you still want a small operational canary:
  1. Publish with 5% overall exposure and a 50/50 relative split.
  2. Verify API error, client error, and latency signals for at least one full operating period.
  3. Check exposures, identifier coverage, and multiple-exposure SQL.
  4. Increase overall exposure to 100% while keeping the 50/50 weights.
  5. Record each exposure change and time.
The 5% stage is not a substitute for sufficient experimental sample. Its purpose is operational verification. If the treatment is safe enough after the canary, full experiment exposure reaches the planned sample sooner.

Step 12: Treat health failures as blockers

GrowthBook runs experiment health checks. Open the experiment’s Health tab before reading metric lift. See Experiment Results for the current checks.

Sample ratio mismatch

SRM means the observed allocation is implausibly different from the configured allocation. GrowthBook uses a chi-squared check and, by default, raises the warning at a very strict p-value threshold. It is evidence of an assignment, delivery, eligibility, or data problem, not evidence that one variant is popular. Investigate:
  • Conditional rendering that evaluates one variation more often.
  • JavaScript errors that prevent one variant from completing exposure tracking.
  • An unstable or missing assignment ID.
  • Cookie behavior across domains or consent states.
  • A changed relative allocation.
  • Bot, internal, or geography filters applied after assignment.
  • Ingestor blocking that differs by browser or route.
  • An assignment query that drops one variation value.
Do not “correct” SRM by deleting rows until the split looks balanced. Fix the cause, invalidate the affected phase, and restart with documented boundaries.

Multiple exposures

A device appearing in both variations usually means it changed assignment identity, the experiment weights or seed changed, or the assignment query is wrong. Compare the first and last event attributes for affected devices. If assignment switched, the causal contrast is contaminated. Fix and restart rather than selecting whichever variation appeared first without a predeclared rule.

Pre-exposure imbalance

If a metric differs substantially before exposure, first rule out SRM and multiple exposure. Then inspect timestamp latency, daily aggregates timestamped at the start of a day, outliers, and mismatched randomization and analysis units. A user-level outcome analyzed against session-level assignment can create misleading precision and imbalance.

Failure matrix

The experiment troubleshooting guide provides additional causes. Preserve screenshots, queries, timestamps, and config revisions with the incident record.

Step 13: Use activation only when assignment must happen early

The canonical implementation evaluates the flag inside the signup page, so exposure is already close to treatment. It does not need activation filtering. Use an activation metric only when the application must assign earlier. For example, it downloads both modal implementations at page load but only some visitors become eligible to see the modal. The activation event must occur before the treatment can affect it or be independent of assignment. Valid candidate:
Invalid candidate for this treatment:
The streamlined form may change focus behavior. Filtering to focusers conditions on a treatment-affected event and can bias the groups. If activation is unavoidable:
  1. Define a binomial activation metric on the independent event.
  2. Add it under Experiment → Overview → Analysis Settings → Activation Metric.
  3. Verify the activation event carries the assignment identifier.
  4. Compare activation rates across variations. A material difference is evidence the activation condition may not be independent.
  5. Document both intent-to-treat results and the activated analysis when decision risk warrants it.
Moving the actual feature evaluation closer to treatment is usually easier to reason about than repairing broad assignment with a filter.

Step 14: Interpret results without shopping for a win

Do not begin with the effect estimate. Use this order:
  1. Confirm data freshness through the full conversion window.
  2. Confirm the experiment reached the predeclared minimum runtime and fixed sample target, or the maximum runtime rule applies.
  3. Confirm SRM, multiple exposure, variation mapping, and identifier checks are clean.
  4. Confirm operational guardrails did not cross the rollback boundary.
  5. Read the primary metric’s point estimate and uncertainty interval.
  6. Compare the interval with the minimum worthwhile effect and harm boundary.
  7. Review secondary diagnostics as explanations, not alternate primary outcomes.
  8. Record one decision and its reasoning.

Bayesian interpretation on Starter

GrowthBook’s Bayesian engine reports a Chance to Win and a distribution for percent change. The default UI highlights a variation above the configured winning threshold and a clear loser below the corresponding lower threshold. Read the result as a distribution of plausible effects under the model and priors. Do not translate “95% Chance to Win” into “there is a 95% chance the implementation is correct.” Health, instrumentation, model assumptions, and practical effect size remain separate questions. A high Chance to Win with most plausible effects below the 15% relative minimum may be statistically persuasive but not commercially worthwhile. Conversely, an inconclusive Chance to Win with a wide interval may mean the test lacks precision, not that the variants are equal. Although Bayesian monitoring does not use the same fixed-horizon p-value rule, predeclare the minimum runtime, sample target, harm rule, and decision cadence. Repeatedly changing the decision after each dashboard refresh still creates an unstable operating process.

Frequentist interpretation on Starter

GrowthBook’s frequentist engine reports a p-value and confidence interval. On the free path, sequential testing is not included, so use the fixed horizon written before launch.
  • Do not stop the first time p < 0.05.
  • Do not extend only because p = 0.06.
  • Do not change the primary metric after viewing results.
  • Do not call p = 0.20 proof that the variants are identical.
  • Do not call a tiny but precisely estimated effect a win if it fails the business threshold.
At the planned horizon, a confidence interval entirely above 0 supports a nonzero positive effect at the configured alpha. An interval also above the 15% relative minimum supports the stronger claim that the treatment likely clears the predeclared business threshold. An interval spanning material harm and material benefit is inconclusive. An interval narrow enough to exclude the worthwhile effect can justify stopping even when the point estimate is positive. If the sample target lands before 14 days, continue to the predeclared minimum runtime to include weekly traffic patterns. After stopping assignment, wait until the last exposed unit’s 24-hour conversion window closes before the final analysis.

Treat segment findings as hypotheses

Browser, country, acquisition source, and device breakdowns can reveal bugs or plausible effect differences. They also create many opportunities for chance findings. Use slices to diagnose implementation first. If a surprising subgroup changes the shipping decision and was not predeclared, run a dedicated follow-up experiment or require stronger independent evidence. Do not target the treatment only to the “winning” slice discovered after the fact and describe it as confirmed personalization.

Record the decision

Use a durable record:
The values are illustrative. Never paste them into a real decision record.

Step 15: Move from one experiment to a maintained system

The end state is not a dashboard with a winner. It is one production behavior, one readable code path, and a reusable experiment contract.

Ship the selected behavior

For a ship_streamlined decision:
  1. Stop the experiment analysis and record the final result.
  2. Replace the experiment rule with a forced streamlined value for production, or use the product’s approved temporary-rollout workflow.
  3. Confirm the assignment callback no longer records new experiment exposures.
  4. Monitor the full population through at least one normal operating cycle.
  5. Open a cleanup change that makes streamlined behavior unconditional.
  6. Remove the control component, temporary variant types, diagnostic flow properties, and experiment-only tests.
  7. Deploy the cleanup with the flag still forcing streamlined.
  8. Verify the unconditional implementation.
  9. Remove the feature check, archive the feature, and retain the experiment decision record.
For retain_control:
  1. Force control immediately.
  2. Stop analysis after the last conversion window closes.
  3. Remove the streamlined code and experiment-only events.
  4. Remove the feature check if there is no active rollout need.
  5. Record what was learned and whether a materially different follow-up is justified.
For iterate:
  1. Return everyone to control unless the treatment is required for another reason.
  2. Do not mutate the old treatment under the same running phase.
  3. Create a new treatment with a new hypothesis and tracking key, such as signup-flow-v2.
  4. Recalculate feasibility from the new minimum worthwhile effect.
  5. Repeat the preflight and data receipts.
Never leave a 100% “winner” experiment running indefinitely. It keeps emitting exposure events, obscures the current decision, and makes future maintainers afraid to remove dead code.

Add an experiment pull-request checklist

After the first implementation, require experiment changes to state:
CI can enforce typed feature keys and event schemas. It cannot decide whether the experimental unit or business threshold is sensible. Keep a human review boundary for those choices.

Monitor drift

Add alerts or scheduled checks for:
  • A material drop in exposure or completion freshness.
  • Missing device_id on any canonical signup event.
  • A new variation value outside control and streamlined.
  • Units exposed to multiple variations.
  • Managed Warehouse usage approaching the plan allowance.
  • signup-flow still referenced after its cleanup deadline.
The application’s release process should fail or warn when a temporary feature key outlives its owner and deadline. Automatic detection can open an issue; engineering review should remove the code.

DIY versus GrowthBook

A coding agent can create a visual split in a few lines:
That code is not an experiment. It can assign a different flow on every render, reload, or request. It has no exposure event, no identity contract, no consistent traffic allocation, no health check, no metric join, no audit trail, and no removal workflow. An improved DIY implementation might hash a persisted identifier, store a configuration document, emit events, and calculate a result. At that point the team owns:
  • Identifier generation, consent, persistence, and cross-device semantics.
  • A deterministic hashing algorithm and compatibility policy.
  • Remote configuration, environment separation, authentication, and audit history.
  • Caching, invalidation, fallback, and incident behavior.
  • Exposure deduplication and unload-safe delivery.
  • Assignment, outcome, and identity-join schemas.
  • SRM and multiple-exposure detection.
  • Metric windows, denominators, outlier rules, and statistical methods.
  • Reproducible results and decision records.
  • Flag ownership, stale detection, and code cleanup.
DIY remains reasonable when all of these are true:
  • The split is a short-lived internal engineering test rather than evidence for a product decision.
  • Assignment already lives in a trusted system of record.
  • The organization has a reviewed experimentation library and analysis pipeline.
  • The team accepts ownership of health checks, statistics, privacy, and lifecycle.
  • GrowthBook would duplicate a mature internal platform rather than replace one-off code.
GrowthBook is compelling when the request that sounds like “split users 50/50” is really “operate a reliable assignment-to-decision system.” It keeps the assignment rule, feature delivery, warehouse analysis, health evidence, and experiment history connected while letting the application keep an explicit code fallback and, on BYOW, keep its source data in the existing warehouse. The value is not that an AI agent cannot write a hash. The value is that the next agent can inspect the feature, trace the metric, reproduce the decision, and safely remove the losing code without reverse-engineering a private mini-platform.

Definition of done

Use this final, falsifiable checklist. “The feature renders” is not enough.

Design

  • The hypothesis names population, treatment, primary metric, minimum worthwhile effect, and harm boundary.
  • The assignment unit matches the entity that independently receives treatment.
  • Baseline rate and eligible traffic come from current data.
  • Sample target, minimum runtime, maximum runtime, and stopping rule are recorded before launch.
  • Both variants are reversible and safe enough for production.

Implementation

  • device_id or the chosen identifier exists before evaluation and remains stable through the outcome window.
  • The code fallback is the known-good control.
  • The feature is evaluated only at the real treatment boundary.
  • Feature payload timeout renders control without blocking signup indefinitely.
  • Exposure and outcome use the same identifier type.
  • Event functions accept only reviewed, typed properties.
  • Email, password, tokens, raw form values, and unreviewed URL queries are absent from events.
  • Type-check, unit tests, browser tests, and production build pass.

Data

  • The regional ingestor or BYOW destination receives an exposure and a known completion.
  • SQL receipts show zero missing assignment IDs for canonical events.
  • Exposures join to outcomes in the configured conversion window.
  • Experiment key and variation IDs match every layer.
  • Multiple-exposure query returns zero unresolved rows.
  • Projected event volume fits the current plan with operating headroom.

Analysis

  • GrowthBook uses the correct data source, assignment query, identifier, and proportion metric.
  • SRM, multiple-exposure, and relevant pre-exposure checks pass.
  • The last assigned unit has completed the metric window before final analysis.
  • Bayesian or frequentist results are interpreted according to the predeclared rule.
  • Segment exploration is labeled exploratory unless predeclared.
  • Operational guardrails remain within their thresholds.

Decision and cleanup

  • A ship, retain, or iterate decision includes dates, sample, effect interval, health status, and owner.
  • The experiment rule no longer assigns new units after the decision.
  • Production serves the selected behavior without depending on a completed experiment.
  • The losing code has been removed or has a dated cleanup change.
  • The temporary feature is archived after code removal.
  • The experiment record and SQL receipts remain available for future review.

Source map and freshness

This guide was verified against GrowthBook public source commit e44a15af063860c7118f52508746356d55e5a91d on 2026-08-12.

Product and SDK dependencies

Volatile details to reverify

Before the next publication or substantial edit, recheck:
  • Latest @growthbook/growthbook and @growthbook/growthbook-react versions and peer requirements.
  • init() return fields and plug-in option names.
  • Regional ingestion hosts and event payload mapping.
  • Managed Warehouse allowance, stop/overage behavior, and SQL column names.
  • Starter, Pro, and Enterprise availability for metric types, sequential testing, CUPED, power tools, guardrails, and release controls.
  • GrowthBook UI labels for SDK connections, features, fact tables, metrics, and experiments.
  • React, Vite, TypeScript, Vitest, and Node versions in the tested fixture.

Automated freshness trigger

Mark this guide for review when a merged pull request changes any of these paths:
Update the executable fixture and tests before changing prose. A passing build does not prove analytics correctness, so rerun the SQL receipts and health checks whenever identifier, event, assignment, or metric code changes.