/Docs

Migrate from Statsig to A vs B

Statsig separates feature gates, dynamic configs, and experiments into three distinct evaluation surfaces. A vs B unifies all three under a single Flag<T> abstraction: boolean flags replace gates, JSON flags replace dynamic configs, and A/B test rules are configured on the same flag entity. Your gate keys, config keys, and event names migrate directly — what changes is the call site shape, the identity model, and the removal of the singleton pattern in favor of explicit client instances.

Concept mapping

StatsigA vs B
Feature gate (checkGate)Boolean flag (getBoolFlag)
Dynamic config (getConfig)JSON flag (getJsonFlag<T>)
Experiment (getExperiment)Flag with an A/B test rule (same key, evaluated via getFlag)
Layer (getLayer)Flag namespace — use distinct keys per flag; no layer abstraction
StatsigUserEvalContext (SingleContext)
user.userIDcontext.key
user.customTop-level attributes on EvalContext (no nesting needed)
Statsig.initialize(key, options) (singleton)new AvsbServer({ sdkKey }) (explicit instance)
Statsig.checkGate(user, gateName)server.forUser(ctx).getBoolFlag(key, false).value
Statsig.getConfig(user, configName).valueserver.forUser(ctx).getJsonFlag(key, {}).value
Statsig.getExperiment(user, expName).get(param, default)server.forUser(ctx).getJsonFlag(key, {}).value.param
Statsig.logEvent(user, eventName, value, metadata)server.track(eventKey, { context, value?, properties? })
Statsig.shutdown()server.close()
Holdout (Statsig Pro)FlagDatafileHoldout — included in all plans

Client construction

Statsig uses a module-level singleton. A vs B uses an explicit server instance so you can run multiple SDK keys (e.g. per-project or per-environment) in the same process without conflict.

Statsig — Node
ts
1import Statsig from 'statsig-node';
2
3await Statsig.initialize('secret-server-key', {
4 environment: { tier: 'production' },
5});
6// All calls via Statsig.checkGate(user, ...) singleton
A vs B — Node
ts
1import { AvsbServer } from '@avsbhq/node';
2
3const server = new AvsbServer({
4 sdkKey: process.env.AVSB_SDK_KEY!,
5 defaultDecideOptions: [], // optional global decide options
6});
7const result = await server.onReady();
8if (!result.success && !result.degraded) {
9 // Hard failure — consider your fallback strategy
10}
11// All calls via server.forUser(ctx).getFlag(...)
Statsig — Browser (@statsig/js-client)
ts
1import { StatsigClient } from '@statsig/js-client';
2
3const client = new StatsigClient('client-key', { userID: 'u_123' });
4await client.initializeAsync();
A vs B — Browser
ts
1import { AvsbClient } from '@avsbhq/browser';
2
3const client = new AvsbClient({
4 sdkKey: 'sdk_production_abc123',
5 context: { kind: 'user', key: 'u_123' },
6});
7await client.onReady();

Identity model

Statsig passes the user object on every evaluation call. A vs B binds the context at client construction and mutates it via explicit identity methods. On the server, the context is passed per-call via forUser.

Statsig
ts
1// User passed on every call
2const user = { userID: 'u_123', custom: { plan: 'pro', country: 'US' } };
3Statsig.checkGate(user, 'my_gate');
4
5// Update user — call with new user object on next evaluation
6const updatedUser = { userID: 'u_123', custom: { plan: 'enterprise' } };
7Statsig.checkGate(updatedUser, 'my_gate');
A vs B
ts
1// Browser — context bound at construction, updated via identify/updateAttributes
2client.identify({ kind: 'user', key: 'u_123', plan: 'pro', country: 'US' });
3
4// Partial attribute patch (no full re-identify needed)
5client.updateAttributes({ plan: 'enterprise' });
6
7// Anonymous → identified stitching (send once per session at login)
8await client.alias(
9 { kind: 'user', key: 'anon_device_xyz' },
10 { kind: 'user', key: 'u_123' }
11);
12
13// Server — context passed per call; no mutation needed
14const ctx = { kind: 'user', key: 'u_123', plan: 'pro', country: 'US' };
15server.forUser(ctx).getBoolFlag('my_gate', false);
Info
Statsig's user.customobject maps to flat attributes on A vs B's EvalContext. Replace { userID: 'u_1', custom: { plan: 'pro' } } with { kind: 'user', key: 'u_1', plan: 'pro' }.

Flag evaluation

The three Statsig calls — checkGate, getConfig, and getExperiment — each map to a different A vs B typed evaluator. The return type is always Flag<T>: access .value for the raw result and .isEnabled() as the boolean gate equivalent.

Statsig — gates and configs
ts
1// Feature gate
2const showNewDash = Statsig.checkGate(user, 'new_dashboard');
3
4// Dynamic config
5const uiConfig = Statsig.getConfig(user, 'ui_settings');
6const theme = uiConfig.get('theme', 'default');
7
8// Experiment
9const exp = Statsig.getExperiment(user, 'checkout_experiment');
10const ctaText = exp.get('cta_text', 'Buy now');
A vs B — unified evaluator
ts
1const uc = server.forUser(ctx);
2
3// Boolean gate equivalent
4const showNewDash = uc.getBoolFlag('new_dashboard', false).value;
5// Or use isEnabled() which also checks that a rule matched:
6const gateOpen = uc.getBoolFlag('new_dashboard', false).isEnabled();
7
8// Dynamic config equivalent — the whole JSON value
9const uiConfig = uc.getJsonFlag<UiSettings>('ui_settings', { theme: 'default' }).value;
10const theme = uiConfig.theme;
11
12// Experiment — same as config; variation key tells you the arm
13const checkoutFlag = uc.getJsonFlag<CheckoutConfig>('checkout_experiment', { ctaText: 'Buy now' });
14const ctaText = checkoutFlag.value.ctaText;
15const arm = checkoutFlag.variationKey; // 'control' | 'treatment_a' | null

Tracking events

Statsig's logEventtakes a user, event name, optional string value, and optional metadata map. A vs B's track uses a single TrackPayload object where the numeric valuefield replaces Statsig's positional value argument (which Statsig also accepts as a number).

Statsig
ts
1Statsig.logEvent(user, 'purchase', 49.99, { orderId: 'ord_99', sku: 'pro' });
A vs B
ts
1// Server
2server.track('purchase', {
3 context: ctx,
4 value: 49.99,
5 properties: { orderId: 'ord_99', sku: 'pro' },
6});
7
8// Browser (context bound)
9client.track('purchase', {
10 value: 49.99,
11 properties: { orderId: 'ord_99', sku: 'pro' },
12});

Multi-context

Statsig supports multi-user context via StatsigUser fields like userID, email, ip, and custom units. A vs B uses a formally typed MultiContextwith named context kinds, which maps cleanly to Statsig's concept of per-unit-type targeting but with an explicit schema. You define context kinds in the dashboard before targeting against them.

Statsig — company-level targeting
ts
1const user = {
2 userID: 'u_123',
3 custom: { companyID: 'org_42', companyTier: 'enterprise' },
4};
5// Target on companyID via custom fields in Statsig rules
A vs B — explicit multi-context
ts
1import type { MultiContext } from '@avsbhq/core';
2
3const ctx: MultiContext = {
4 kind: 'multi',
5 user: { kind: 'user', key: 'u_123' },
6 organization: { kind: 'organization', key: 'org_42', tier: 'enterprise' },
7};
8// In the dashboard, set hashAttribute to 'organization.key' on org-level rules
9server.forUser(ctx).getBoolFlag('enterprise_feature', false);

Streaming updates

Statsig's server SDK polls on a timer; the browser SDK uses a combination of polling and server-sent events depending on configuration. A vs B follows the same hybrid model: configurable poll interval with optional SSE push for the browser.

A vs B — flag change listener
ts
1const unsub = client.on('flagChange', ({ flagKey, previousValue, newValue }) => {
2 // Re-render anything that depends on flagKey
3});
4// Call unsub() to stop listening

Bootstrap / SSR

Statsig provides getClientInitializeResponse for server-side bootstrap. A vs B uses a FlagDatafile fetched on the server and passed as the bootstrap prop to AvsbProvider.

Statsig — server bootstrap
ts
1const bootstrapValues = Statsig.getClientInitializeResponse(user);
2// Serialize and embed in HTML for client pickup
A vs B — server bootstrap
ts
1// Server component
2import { fetchDatafile } from '@avsbhq/node/server';
3const datafile = await fetchDatafile(process.env.AVSB_SDK_KEY!);
4
5// Client provider
6<AvsbProvider sdkKey="..." context={ctx} bootstrap={datafile ?? undefined}>
7 {children}
8</AvsbProvider>

Holdouts

Statsig holdouts are a Pro/Enterprise feature. In A vs B, holdouts are available on all plans and are configured in the platform under Holdouts. Held-out users receive source: 'holdout' on any flag participating in the holdout — no SDK code changes are needed.

A vs B — detecting holdout traffic
ts
1const flag = server.forUser(ctx).getBoolFlag('checkout_redesign', false);
2if (flag.source === 'holdout') {
3 // User is in the holdout group — exclude from experiment metrics
4}

Cleanup

Statsig
ts
1await Statsig.shutdown();
A vs B
ts
1await server.close(); // flushes events and stops polling

Testing

Statsig — test overrides
ts
1import { DynamicConfig } from 'statsig-node';
2// Use Statsig's local mode or override APIs in test environments
A vs B — mock client
ts
1import { createMockClient } from '@avsbhq/test';
2
3const mock = createMockClient({
4 flags: {
5 new_dashboard: true,
6 ui_settings: { theme: 'blue' },
7 checkout_experiment: { ctaText: 'Get started' },
8 },
9});
10// Inject mock wherever AvsbServer or AvsbClient is expected

Cutover checklist

1

Remove Statsig packages

Uninstall statsig-node, @statsig/js-client, and statsig-react from your project.
2

Install A vs B packages

Install @avsbhq/node, @avsbhq/browser, and @avsbhq/react as needed.
3

Replace singleton initialization

Replace Statsig.initialize(key, opts) with new AvsbServer({ sdkKey }). Store the instance in a module-level variable or a DI container.
4

Migrate StatsigUser to EvalContext

Move user.userID to context.key, set context.kind = 'user', and flatten user.custom attributes to top-level context properties.
5

Replace gate checks

Replace Statsig.checkGate(user, key) with server.forUser(ctx).getBoolFlag(key, false).value.
6

Replace config and experiment calls

Replace getConfig and getExperiment with getJsonFlag<T>(key, defaultValue). Define a TypeScript interface for each config shape for full type safety.
7

Replace logEvent

Replace Statsig.logEvent(user, name, value, metadata) with server.track(name, { context, value, properties }).
8

Remove layer calls

If you use Statsig layers, identify which flags each layer wraps and address each flag key directly in A vs B.
9

Update tests

Replace Statsig test utilities with createMockClient from @avsbhq/test.
10

Verify and deploy

Run npm run build and npx tsc --noEmit. Confirm flag evaluations and events appear in the A vs B dashboard before promoting to production.