Skip to main content

JavaScript Client SDK (Web)


note

Using our legacy statsig-js packages? Support ends Jan 31, 2025 - see migrating to js-client and react-bindings for a list of breaking changes and recommended solutions.

Note that this SDK is designed primarily for Browser-only use, and for Server-side rendering use-cases. For server-side use-cases, consider using our Node SDK.

Getting Started

To get up and running with Statsig in JS, you can install the Statsig client SDK via npm or yarn.

Optional features:



npm install @statsig/js-client @statsig/session-replay @statsig/web-analytics

After installing the SDK, you can initialize it using the following code:

import { StatsigClient, StatsigOptions, StatsigUser } from '@statsig/js-client';
import { StatsigSessionReplayPlugin } from '@statsig/session-replay';
import { StatsigAutoCapturePlugin } from '@statsig/web-analytics';

const user: StatsigUser = { userID: 'some_user_id' };

const options: StatsigOptions = {
plugins: [
new StatsigSessionReplayPlugin(),
new StatsigAutoCapturePlugin(),
],
environment: { tier: "production" } // Optionally set the environment
};

const myStatsigClient = new StatsigClient(YOUR_SDK_KEY, user, options);
await myStatsigClient.initializeAsync();

See the User (StatsigUser) doc for more information on the "user" parameter.

Working with the SDK

Checking a Feature Flag/Gate

Now that your SDK is initialized, let's check a Feature Gate. 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.

if (myStatsigClient.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 can be very useful for simple on/off switches, with optional but advanced user targeting. However, if you want to be able send a different set of values (strings, numbers, and etc.) to your clients based on specific user attributes, e.g. country, Dynamic Configs can help you with that. The API is very similar to Feature Gates, but you get an entire json object you can configure on the server and you can fetch typed parameters from it. For example:

const dynamicConfig = myStatsigClient.getDynamicConfig("awesome_product_details");
const itemName = dynamicConfig.get("product_name", "Some Fallback");
const price = dynamicConfig.value["price"] ?? 10.0;

if (dynamicConfig.value["is_discount_enabled"] === true) {
// apply some discount logic
}

See Typed Getters to learn more about accessing values.

Getting a Layer/Experiment

Then we have Layers/Experiments, which you can use to run A/B/n experiments. We offer two APIs, but we recommend the use of layers to enable quicker iterations with parameter reuse.

// Values via getLayer

const layer = myStatsigClient.getLayer("user_promo_experiments");

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");
const priceExperiment = myStatsigClient.getExperiment("new_user_promo_price");

const promoTitle = titleExperiment.value["title"] ?? "Welcome to Statsig!";
const discount = priceExperiment.value["discount"] ?? 0.1;

See Typed Getters to learn more about accessing values.

Logging an Event

Now that you have a Feature Gate or an Experiment set up, you may want to track some custom events and see how your new features or different experiment groups affect these events. This is super easy with Statsig - simply call the Log Event API for the event, and you can additionally provide some value and/or an object of metadata to be logged together with the event:

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

Learn more about identifying users, group analytics, and best practices for logging events in the logging events guide.

Typed Getters

The Layer, Experiment and DynamicConfig objects all support a "typed" get method. This method can help avoid issues where a value is not the type you expect.

For example, imagine we have a Dynamic Config with a single number value called "my_value":

// { "my_value": 1 }

const myDynamicConfig = myStatsigClient.getDynamicConfig("a_config");

const myFallbackValue = myDynamicConfig.get("my_value", "fallback"); // returns: "fallback"
const myTypedValue = myDynamicConfig.get("my_value", 0); // returns: 1
const myRawValue = myDynamicConfig.get("my_value"); // returns: 1

Because "my_value" points to a number but in the myFallbackValue case, we are calling .get with a string fallback value, the fallback is being returned.

In the myTypedValue case, we are passing a number fallback value, and since "my_value" is also a number, the actual value of 1 is returned.

If typing is not important to you, the fallback argument can be omitted, and the SDK will simply return the value. This is highlighted in the myRawValue case.

Evaluation Details

When you receive a value, it may be useful to know how the SDK came to that result. For this purpose, on every Statsig type there exists a way to check the EvaluationDetails object.

This object contains the following fields:

  • Reason: This is a string containing the source as well as whether or not the specific config was found.
    • Network:Recognized means the SDK has the latest values and found an entry for the config.
    • Cache:Unrecognized means we are working with cached values, are could not find the given config.
    • For the full list of possible combinations, see the page on Debugging.
  • lcut: Last Config Update Time - This is the unix timestamp for when any configuration in your project changed.
    • lcut works as a version number for you project. If it changes, values are deemed out of date and possibly need to be refetched.
  • receivedAt: This is the unix timestamp of when the SDK received these values. This can be useful in knowing how old your cache is.
const gate = myStatsigClient.getFeatureGate('a_gate');
console.log(gate.details);
// ⮑ { "reason": "Cache:Recognized", "lcut": 1713837126636, "receivedAt": 1713838137598 }

const config = myStatsigClient.getDynamicConfig('a_config');
console.log(config.details);
// ⮑ { "reason": "Cache:Unrecognized", "lcut": 1713837126636, "receivedAt": 1713838137598 }

// Note: Only "reason" is different as "lcut" and "receivedAt" relate to all values

Code Examples

Prefer seeing it in practice? Included in the open source repository are some Code Examples. View these for common use cases for the SDK.

Parameter Stores

Parameter Stores hold a set of parameters for your mobile app. These parameters can be remapped between Statsig entities (Feature Gates, Experiments, and Layers), so you can decouple your code from the configuration in Statsig.

You can read more about the concept here.

Getting a Parameter Store

To fetch a set of parameters, use the following api:

const homepageStore = myStatsigClient.getParameterStore("homepage");

Getting a parameter

You can then access parameters like this:

const title = homepageStore.get(
"title", // parameter name
"Welcome", // default value
);

const showUpsell = homepageStore.get(
"upsell_upgrade_now",
false,
);

Statsig User

You should provide a StatsigUser object whenever possible when initializing the SDK, passing as much information as possible in order to take advantage of advanced gate and config conditions (like country or OS/browser level checks).

Most of the time, the userID field is needed in order to provide a consistent experience for a given user (see logged-out experiments to understand how to correctly run experiments for logged-out users).

If the user is logged out at the SDK init time, you can leave the `userID` out for now, and we will use a stable device ID that we create and store in the local storage for targeting purposes.

Besides userID, we also have 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 be able to create targeting based on them.

Once the user logs in or has an update/changed, make sure to call `updateUser` with the updated `userID` and/or any other updated user attributes:

Private Attributes

Have sensitive user PII data that should not be logged? No problem, we have a solution for it! On the StatsigUser object we also have a field called privateAttributes, which is a simple object/dictionary that you can use to set private user attributes. Any attribute set in privateAttributes will only be used for evaluation/targeting, and removed from any logs before they are sent to Statsig server.

For example, if you have feature gates that should only pass for users with emails ending in "@statsig.com", but do not want to log your users' email addresses to Statsig, you can simply add the key-value pair { email: "my_user@statsig.com" } to privateAttributes on the user and that's it!

Updating Users

At some point, you will probably need to change the StatsigUser being used by the Statsig client. To do this you can call updateUserSync or updateUserAsync.

In advanced use cases, you may want to Prefetch or Bootstrap (Provide) values for the updating of a user. See Using EvaluationsDataAdapter to learn how this can be achieved.

const user = { userID: 'a-user' };

// update immediately from cache values
myStatsigClient.updateUserSync(user);

// or, update and wait for the latest values
await myStatsigClient.updateUserAsync(user);

Prefetching Users

In some cases, it can be useful to prefetch values for a given user, such that switching to that user later can be done synchronously.

To do this, you would call prefetchData and then later updateUserSync.

const user = { userID: 'my-other-user' };

// Fetch the latest values for a given StatsigUser
await myStatsigClient.dataAdapter.prefetchData(user);

// or, if you just want to do it optimistically
myStatsigClient.dataAdapter
.prefetchData(user)
.catch((err) => console.warn("Failed to prefetch", err));

// Then, at some later time, we can synchronously switch users
myStatsigClient.updateUserSync(user);

const gate = myStatsigClient.getFeatureGate('a_gate');
console.log(`a_gate`, gate.value, gate.details.reason); // outputs: a_gate, true, Prefetch:Recognized

Statsig Options

disableLogging boolean

Prevents collecting and sending any events over the network. This is useful for secure deployments where you want to completely disable analytics tracking.

default: false

disableStableID boolean

Prevents the SDK from generating and using a stable device ID. Use this when you want to disable any device-level identification.

default: false

disableEvaluationMemoization boolean

Disables caching of evaluation results. When enabled, each call to checkGate, getConfig, etc. will re-evaluate the condition rather than using a cached result.

default: false

initialSessionID string

Overrides the initial session ID used by the SDK. By default, the SDK generates a random session ID.

Note: Sessions still expire and will be replaced with an auto-generated SessionID.

enableCookies boolean

Enables the use of cookies for storing the stable ID. This is useful for cross-domain tracking.

default: false

disableStorage boolean

Prevents writing anything to storage.

Note: caching will not work if storage is disabled

networkConfig object

Allows for fine grained control over which api or urls are hit for specific Statsig network requests

See NetworkConfig object

api string

The API to use for all SDK network requests. You should not need to override this unless you have a custom API that implements the Statsig endpoints.

Note: You need to include /v1 on the end of your custom API. eg https://my-custom-api.com/v1

logEventUrl string

The URL used to flush queued events via a POST request. Takes precedence over NetworkConfig.api.

default: https://prodregistryv2.org/v1/rgstr

logEventFallbackUrls string[]

A list of URLs to try if the primary logEventUrl fails. This provides fallback options for event logging in case the primary endpoint is unavailable.

default: []

networkTimeoutMs number

The maximum amount of time (in milliseconds) that any network request can take before timing out.

default: 10,000 ms (10 seconds)

preventAllNetworkTraffic boolean

Prevents any network requests being made. Useful for secure deployments where the browser is not allowed to send requests to external domains.

Note: When using this option, you may see "Failed to flush events" warnings in the console. To prevent these warnings, also set disableLogging: true in your StatsigOptions.

networkOverrideFunc function

Overrides the default networking layer used by the Statsig client. By default, the client use fetch, but overriding this you could use axios or raw XMLHttpRequest

default: fetch

initializeUrl string

The URL used to fetch the latest evaluations for a given user. Takes precedence over NetworkConfig.api.

default: https://featureassets.org/v1/initialize

environment StatsigEnvironment

An object you can use to set environment variables that apply to all of your users in the same session.

logLevel LogLevel

How much information is allowed to be printed to the console.

default: LogLevel.Warn

loggingBufferMaxSize number

The maximum number of events to batch before flushing logs to Statsig.

default: 50

loggingIntervalMs number

How often (in milliseconds) to flush logs to Statsig.

default: 10,000 ms (10 seconds)

overrideAdapter OverrideAdapter

An implementor of OverrideAdapter, used to alter evaluations before its returned to the caller of a check api (checkGate/getExperiment etc).

includeCurrentPageUrlWithEvents boolean

(Web only) Should the 'current page' url be included with logged events.

default: true

disableStatsigEncoding boolean

Whether or not Statsig should use raw JSON for network requests where possible.

default: false

disableCompression boolean

Whether or not Statsig should compress JSON bodies for network requests where possible.

default: false

dataAdapter EvaluationsDataAdapter

An implementor of EvaluationsDataAdapter, used to customize the initialization/update flow.

default: StatsigEvaluationsDataAdapter

customUserCacheKeyFunc CustomCacheKeyGenerator

Overrides the default cache key generation. Given the SDKKey and current StatsigUser, return a key to be used for storing values related to that user.

default: Cache key is a hash of the sdkKey + user.userID + user.customIDs.

Manual Exposures

warning

Manually logging exposures can be tricky and may lead to an imbalance in exposure events. For example, only triggering exposures for users in the Test group of an experiment will imbalance the experiment, making it useless.

Added in version , you can now query your gates/experiments without triggering an exposure as well as manually logging your exposures.

To check a gate without an exposure being logged, call the following.

const result = myStatsigClient.checkGate('a_gate_name', { disableExposureLog: true });

Later, if you would like to expose this gate, you can call the following.

myStatsigClient.checkGate('a_gate_name');

Shutting Statsig Down

In order to save users' data and battery usage, as well as prevent logged events from being dropped, we keep event logs in client cache and flush periodically. Because of this, some events may not have been sent when your app shuts down.

To make sure all logged events are properly flushed or saved locally, you should tell Statsig to shutdown when your app is closing:

await myStatsigClient.shutdown();

StableID

StableID is a concept in Statsig that allows us to have a consistent identifier for a single device. This allows Statsig to run experiments on logged out users (users without a UserID) as well as target gates on the device level rather than the user level.

How it works

When you initialize the SDK for the first time, it checks if a StableID is already present in local storage. If it's missing, the SDK generates a new StableID and saves it in local storage. On subsequent initializations, the SDK retrieves and reuses the previously stored StableID.

The StableID is stored in local storage under the key statsig.stable_id.<SDK_KEY_HASH>. Each SDK key has its own StableID, meaning that if you're using multiple SDK keys, each one will have a separate StableID.

It's important to note that local storage is not shared across different domains or subdomains. This means that if you are working across multiple domains or subdomains, the StableID will be isolated to each domain's local storage.

If you need a StableID to persist across domains, you'll need to implement your own mechanism to store and retrieve the StableID, such as using a cookie as demonstrated here.

The Statsig SDK does not use any cookies itself.

Accessing the StableID

You can access StableID that the Statsig client is using by calling getContext() and then checking the stableID field.

const context = myStatsigClient.getContext();
console.log('Statsig StableID:', context.stableID);

Overriding StableID

If your app already has something similar to a StableID and you would prefer to use that instead, you can override the default behavior by simply passing your value in as part of the StatsigUser object.

import { StatsigClient, StatsigUser } from '@statsig/js-client';

const userWithStableID: StatsigUser = {
customIDs: {
stableID: 'my-custom-stable-id', // <- Your Stable ID (Must have key "stableID")
},
};

// Pass in your user object with a stableID
const myStatsigClient = new StatsigClient(YOUR_CLIENT_KEY, userWithStableID);

// or, if you already have an initialized client, you can update the user instead
await myStatsigClient.updateUserAsync(userWithStableID);
note

Once overridden, the new StableID will be persisted to local storage so it will be used for future sessions.

Persisting StableID across subdomain

If you are assigning a user to a test on one subdomain, and tracking behavior for metrics purposes on a different subdomain, you'll need to have this solution in place to ensure Statsig can properly attribute cross-origin behavior to the Test Group assignment that took place on the initial experiment domain.

To install, simply paste the first script tag prior to SDK initialization and include code that manually pulls the stableID onto the user object.

<!-- 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 statsigClient = new Statsig.StatsigClient('<client-sdk-key>', userObj, {/* OPTIONS */});
</script>
(please use the above code at your discretion and test thoroughly)

Keeping StableID Consistent across Client & Server

If you have a backend service running a Statsig SDK as well as using the Statsig client in your frontend, you may need to have the same StableID accessible in both environments.

It is highly dependent on your setup, but usually you can send the StableID up with any requests from your frontend. If the backend ever receives a request without a StableID, it can generate one and have the client store it (usually as a cookie) for future requests.

Because Server SDKs are designed to handle multiple Users, they do not generate their own StableIDs.

Here is an example showing how you might use a cookie to share the StableID across client and server and use it bootstrap the client SDK.

// --------------
// Server Side
// --------------
const isStatsigServerReady = Statsig.initialize(
process.env['STATSIG_SERVER_KEY']!,
);

function getCookieFromRequest(req: Request, name: string): string {
// reads the value from a cookie
}

function setCookieOnResponse(res: Response, name: string, value: string): void {
// stores the cookie on the response
}

app.post('/init-statsig-client', async (req, res) => {
await isStatsigServerReady;

const user = req.body.user;
let stableID = getCookieFromRequest(req, 'my_stable_id');

if (!stableID) {
stableID = generateStableID();
setCookieOnResponse(res, 'my_stable_id', stableID);
}

user.customIDs = {
...user.customIDs,
stableID,
};

const values = Statsig.getClientInitializeResponse(
user,
YOUR_CLIENT_KEY, // <- Client Key
{
hash: 'djb2',
},
);

res.json({ values: JSON.stringify(values), user });
});

// --------------
// Client Side
// --------------
function loadUserData(): string {
// Creates a JSON string for your request containing your StatsigUser object.
}

const { values, user: serverVerifiedUser } = await fetch(
'/init-statsig-client',
{
method: 'POST',
body: loadUserData(),
}
).then((res) => res.json() as Promise<ResponseType>);

const myStatsigClient = new StatsigClient(YOUR_CLIENT_KEY, serverVerifiedUser);
myStatsigClient.dataAdapter.setData(values);
myStatsigClient.initializeSync();

Overrides (OverrideAdapter)

In the new javascript SDKs, we provide an OverrideAdapter interface that you can customize, plus a ready-to-use version called LocalOverrideAdapter that should work for most use cases. Add it to your instance at initialization time, and it should behave intuitively, allowing you to override any config.

import { LocalOverrideAdapter } from '@statsig/js-local-overrides';
const overrideAdapter = new LocalOverrideAdapter(); // <-- instantiate here
overrideAdapter.overrideGate('gate_a', false);
overrideAdapter.overrideGate('gate_b', true);

const client = new StatsigClient(
DEMO_CLIENT_KEY,
{ userID: 'a-user' },
{
logLevel: LogLevel.Debug,
overrideAdapter, //<-- Add to your instance here
},
);

Making these overrides "sticky"

In other client SDKs, overrides persist on an individual device between sessions. The LocalOverrideAdapter supports this - but given that the SDK also supports Multi-Instance, you should pass your SDK key into the LocalOverrideAdapter to serve as a unique key for each instance's overrides in storage.

const overrideAdapter = new LocalOverrideAdapter("client-XXXXXXXXX"); // <-- adding the client key enables persistent overriding.

Testing

When writing tests, you can use jest to mock Statsig packages.

Consider the following example function. We take in a string as input and based on some Statsig configurations, we edit the string and return the result.

import { StatsigClient } from '@statsig/js-client';

export async function transform(input: string): Promise<string> {
const client = new StatsigClient(
YOUR_CLIENT_KEY,
{ 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;
}

In our jest test, we can mock the StatsigClient to return the values we want.

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');
});

Session Replay

By including the @statsig/session-replay package in your project, you can automatically capture and log user sessions as videos. This feature is useful for debugging and understanding user behavior. Read more about Session Replay.

import * as React from 'react';

import { StatsigProvider } from '@statsig/react-bindings';
import { StatsigSessionReplayPlugin } from '@statsig/session-replay';

return (
<StatsigProvider
sdkKey={YOUR_CLIENT_KEY}
user={{ userID: 'a-user' }}
loadingComponent={
<div style={{ height: 100, width: 300, padding: 16 }}>Loading...</div>
}
options={{ plugins: [ new StatsigSessionReplayPlugin() ] }}>
<div>Hello</div>
</StatsigProvider>
);

Web Analytics / Auto Capture

By including the @statsig/web-analytics package in your project, you can automatically capture common web events like clicks and page views. Read more about Web Analytics.

import * as React from 'react';

import { StatsigProvider } from '@statsig/react-bindings';
import { StatsigAutoCapturePlugin } from '@statsig/web-analytics';

return (
<StatsigProvider
sdkKey={YOUR_CLIENT_KEY}
user={{ userID: 'a-user' }}
loadingComponent={
<div style={{ height: 100, width: 300, padding: 16 }}>Loading...</div>
}
options={{ plugins: [ new StatsigAutoCapturePlugin() ] }}>
<div>Hello</div>
</StatsigProvider>
);

Advanced

Async timeouts

For the Statsig client calls that hit the network (initializeAsync and updateUserAsync), it is possible to specify a maximum amount of time (in milliseconds) that the method is allowed to take before the promise is resolved.

// timeouts during initialization
await myStatsigClient.initializeAsync({ timeoutMs: 1000 });

// timeouts during user updates
await myStatsigClient.updateUserAsync(
{ userID: 'a-user' },
{ timeoutMs: 1000 },
);

If this timeout is hit, the promise will resolve and cached values will be used (If any are available). The network request will continue and upon a response, will write values to cache so the will be available for subsequent calls.

If you'd like to avoid a network request entirely, see initialization strategies for more options.

Data Adapter

The EvaluationsDataAdapter type outlines how the StatsigClient should fetch and cache 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. See Using EvaluationsDataAdapter to learn more and see example usage.

Client Event Emitter

It is possible to subscribe to StatsigClientEvents (Not to be confused with StatsigEvent). These events occur at various stages while using the Statsig client. You can subscribe to specific events by specifying the StatsigClientEvent name, or, all events by using the wildcard token '*'.

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.

Multiple Client Instances

In some cases, it might be useful to have multiple instances of the Statsig client running.

🔥IMPORTANT: You must use two different SDK keys for this to work. Various functionality of the Statsig client is keyed on the SDK key being used. Using the same key will lead to collisions.

import { StatsigClient } from '@statsig/js-client';

const mainStatsigClient = new StatsigClient(
YOUR_CLIENT_KEY,
{ userID: 'a-user' },
);

const secondaryStatsigClient = new StatsigClient(
SOME_OTHER_KEY,
{ userID: 'some-other-user' },
);

await Promise.all([
mainStatsigClient.initializeAsync(),
secondaryStatsigClient.initializeAsync()
]);

if (mainStatsigClient.checkGate('a_gate')) {
// do something because a_gate passes
}

if (secondaryStatsigClient.checkGate('some_other_gate')) {
// do something beacuse some_other_gate passes
}

Partial User Matching

In some advanced flows, you may have data that you want on your user objects, but do not want to make a network request to re-evaluate the user.

To achieve this, you can call updateUserSync with a altered user object and use StatsigOptions.customUserCacheKeyFunc to only match on the original user properties.


const originalUser = {
customIDs: {
analyticsID: "some-analytics-id-here"
},
}

const myCustomCacheKey = (sdkKey: string, user: StatsigUser) => {
const analyticsID = user.customIDs?.analyticsID;
return `sdkKey:${sdkKey}:analyticsID:${analyticsID}`; // <- Only match on analyticsID
}

const myStatsigClient = new StatsigClient("client-key-here", originalUser, {
customUserCacheKeyFunc: myCustomCacheKey // <- Pass in the custom cache key function
});
await myStatsigClient.initializeAsync();

someAsyncFunctionThatTakesAWhile().then((newData) => {
const alteredUser = {
...originalUser,
userID: newData.userID,
email: newData.email,
};

myStatsigClient.updateUserSync(alteredUser);
});
warning

Because we are only using analyticsID in the cache key, we will get cache hits for any user object with this ID. Potentially leading to incorrect evaluations.

For example, if we had a gate is_internal_user that was evaluating to true for all users with a specific UserID, but we were using a partial cache key, we would get a cache hit for all users with the same analyticsID.

await myStatsigClient.updateUserAsync({
userID: "internal_user_123",
customIDs: {
analyticsID: "MY_ANALYTICS_ID"
},
});

myStatsigClient.checkGate("is_internal_user"); // <- returns true
// ...

// Somewhere else in the code


myStatsigClient.updateUserSync({
userID: "prod_user_123",
customIDs: {
analyticsID: "MY_ANALYTICS_ID" // <- Same myAnalyticsID as internal_user_123
},
});

myStatsigClient.checkGate("is_internal_user"); // <- Still returns true because we got a cache hit from the internal user.

If you want to ensure you are always working with the latest values for the current user, you should await the call to updateUserAsync.

Debugging

If you run into issues when implementing Statsig, or are receiving values you do not understand, you can try some of the following to get yourself unstuck.

Enable Debug Logging

By setting the log level to the most verbose (Debug), the SDK will print more information around the internal operations it is performing.

import { LogLevel, StatsigClient, StatsigOptions } from '@statsig/js-client';

const client = new StatsigClient(YOUR_CLIENT_KEY, { userID: 'a-user'}, {
logLevel: LogLevel.Debug, // <- Print Everything
});

Access __STATSIG__ Global

If you see issues appearing, but only for certain environments, it can be useful to open the web browsers debugger and view the Statsig client instance.

  • Open the web browsers console. In Chrome, this is under View > Developer > JavaScript Console.
  • In the console, enter __STATSIG__. This will print the global Statsig state object, containing all internal state of the SDK.
  • eg __STATSIG__.instance()._logger._queue is the queued events array

STATSIG-global

View Network Logs

To see all traffic that is being sent via the Statsig SDK, you can filter your browsers network logs to those containing your client SDK key.

In Chrome, going to the Network tab of the developer tools and filtering to 'client-' is enough to show all the requests:

network-logs

Check Evaluation Reason

When you get a value that you don't fully understand, you can check the details field to see how that value was received.

For example, if you have a feature gate, you can check the .details.reason field:

const gate = myStatsigClient.getFeatureGate('a_gate');
console.log(gate.details.reason); // <- Prints the reason for this evaluation

Reasons can be any of the following:

  • Network | NetworkNotModified - The latest value from a network requests
  • Cache - Values loaded from local storage
  • NoValues - No cache and no successful network request
  • Bootstrap - Values found and loaded after a dataAdapter.setData call
  • Prefetch - Values found and loaded after a dataAdapter.prefetchData call

FeatureGate, DynamicConfig, Experiment and Layer types all have this details object. (Learn more at Debugging > Reasons)

FAQ

How do I run experiments for logged out users?

See the guide on device level experiments

Does the SDK use the browser local storage or cookies? If so, for what purposes?

The SDK does not use any cookies.

It does use the local storage for feature targeting and experimentation purposes only. Values for feature gates, dynamic configs and experiments are cached in the local storage, which are used as a backup in the event that your website/app cannot reach the Statsig server to fetch the latest values. If any events were logged but could not be sent to Statsig server due to issues like network failure, we also save them in the local storage to be sent again when network restores.

Can I reference the SDK instance globally?

Yes, StatsigClient instances can be accessed via the instance() function.

For example, if you initialize using a script tag in the head of your webpage, you can use the following to access the StatsigClient and log a custom event.

window.Statsig.instance().logEvent("test_event");

or, if you are importing the package:

import { StatsigClient } from '@statsig/js-client';

StatsigClient.instance().logEvent("test_event");

Note: in multi-instance setups, you can specify the SDK key to get a specific instance. eg Statsig.instance('client-YOUR_CLIENT_KEY')

In certain use cases, it is necessary to suspend cache and network usage until the user grants specific permissions. You can now start the SDK without storage or logging and enable this only after the user grants permission.

// start the client without storage or logging
const client = new StatsigClient(YOUR_CLIENT_KEY, {}, {
disableLogging: true,
disableStorage: true
});
await client.initializeAsync();

// then, once permissions have been granted
client.updateRuntimeOptions({disableLogging: false, disableStorage: false})

The SDK will queue up to 500 events in memory and will retry sending them when logging is enabled.