# Node Server SDK

{% callout type="tip" %}
Migrating from the legacy Node SDK? Refer to our

[Migration Guide](https://docs.statsig.com/server-core/migration-guides/node)

.
{% /callout %}

## Set up the SDK

{% steps %}
{% step title="Install the SDK" %}
```shell
npm i @statsig/statsig-node-core
```

The Node SDK is pre-built and compiled for different operating systems and CPU architectures. Package managers resolve the correct version automatically.

{% accordion-group %}
{% accordion title="Frozen Lockfile Setup" %}
If your service has locked dependencies with package-lock.json or pnpm-lock.yml, include all required platform versions. For example, if you develop locally on macOS and deploy to Linux:

```
dependencies {
    "@statsig/statsig-node-core-darwin-arm64": "0.1.0" // for macOS
    "@statsig/statsig-node-core-linux-x64-gnu": "0.1.0" // for linux x64 machines
}
```
{% /accordion %}

{% accordion title="Usage with Next.js/Webpack/esbuild" %}
`statsig-node-core` uses native binary files that you can't package with webpack/esbuild. To prevent errors, take the following steps:

{% tabs %}
{% tab title="Next.js" %}
In your `next.config.js` file, add the `@statsig/statsig-node-core` package to the `serverExternalPackages` array:

```jsx
const nextConfig = {
  serverExternalPackages: ['@statsig/statsig-node-core'],
}
```
{% /tab %}

{% tab title="esbuild" %}
Add the `--packages=external` flag to your build script:

```shell
esbuild --packages=external
```
{% /tab %}
{% /tabs %}
{% /accordion %}
{% /accordion-group %}
{% /step %}

{% step title="Initialize the SDK" %}
After installation, initialize the SDK using a [Server Secret Key from the Statsig console](https://console.statsig.com/api_keys).

{% callout type="warning" %}
Keep Server Secret Keys private. If you expose one, you can disable and recreate it in the Statsig console.
{% /callout %}

The optional `options` parameter accepts a `StatsigOptions` object to customize the SDK.

```jsx
// Basic initialization
import { Statsig, StatsigUser } from '@statsig/statsig-node-core';
//Or, in common JS, const { Statsig, StatsigUser } = require('@statsig/statsig-node-core');

const statsig = new Statsig("secret-key");
await statsig.initialize();

// or with StatsigOptions
const options: StatsigOptions = { environment: "staging" };

const statsigWithOptions = new Statsig("secret-key", options);
await statsigWithOptions.initialize();
```

`initialize` performs a network request. After `initialize` completes, virtually all SDK operations are synchronous (refer to [Evaluating Feature Gates in the Statsig SDK](https://blog.statsig.com/evaluating-feature-gates-in-the-statsig-sdk-a6f8881a1ad8)). The SDK fetches updates from Statsig in the background independently of API calls.
{% /step %}
{% /steps %}

## Working with the SDK

### Checking a feature flag/gate

After you initialize the SDK, you can fetch a [**Feature Gate**](https://docs.statsig.com/feature-flags/overview). Feature Gates create logic branches in code that you can roll out to different users from the Statsig Console. Gates are always **CLOSED** or **OFF** (equivalent to `return false;`) by default.

All APIs require a user object (refer to [Statsig user](#statsig-user)). For example:

```jsx
const user = new StatsigUser({ userID: "a-user" });

if (statsig.checkGate(user, "a_gate")) {
    // Gate is on, enable new feature
} else {
    // Gate is off
}
```

### Reading a dynamic config

Feature Gates are useful for on/off switches with optional user targeting. To send different values (strings, numbers, and similar types) to clients based on user attributes such as country, use [**Dynamic Configs**](https://docs.statsig.com/dynamic-config/overview). The API is similar to Feature Gates, but returns a full JSON object configurable on the server, from which you can fetch typed parameters.

```jsx
// Get the dynamic config
const config = statsig.getDynamicConfig(user, "a_config");

// Get typed values using the getValue() method
const itemName = config.getValue("product_name", "Awesome Product v1");
const price = config.getValue("price", 10.0);
const shouldDiscount = config.getValue("discount", false);

// Or access the entire value object directly
const value = config.value;
```

### Getting a layer/experiment

Use **Layers/Experiments** to run A/B/n experiments. Two APIs are available. Statsig recommends [Layers](https://docs.statsig.com/experiments/layers-overview) because they make parameters reusable and support mutually exclusive experiments.

```jsx
// Or, via individual experiments
const titleExp = statsig.getExperiment(user, "new_user_promo_title");
const priceExp = statsig.getExperiment(user, "new_user_promo_price");

const experimentTitle = titleExp.getValue("title", "Welcome to Statsig!");
const experimentDiscount = priceExp.getValue("discount", 0.1);

// Get values via Layer
const layer = statsig.getLayer(user, "user_promo_experiments");
const title = layer.getValue("title", "Welcome to Statsig!");
const discount = layer.getValue("discount", 0.1);
```

### Retrieving feature gate metadata

In certain scenarios, you may need more information about a gate evaluation than a boolean value. For additional metadata about the evaluation, use the Get Feature Gate API, which returns a FeatureGate object:

```jsx
const gate = statsig.getFeatureGate(statsigUser, "example_gate")
console.log(gate.rule_id)
console.log(gate.value)
```

### Parameter stores

Use Parameter Stores when you want to define a parameter without deciding whether it should be a Feature Gate, Experiment, or Dynamic Config. Parameter Stores let you change the parameter type at any point in the Statsig console without a new deployment. Parameter Stores are optional, but parameterizing your application provides future flexibility and allows non-technical Statsig users to turn parameters into experiments.

```jsx
const paramStore = statsig.getParameterStore(statsigUser, "my_parameters")
const paramStoreValue = paramStore.getValue('my_parameter_value')
```

### Logging an event

To track custom events, call the Log Event API. Specify the user, event name, and an optional value or metadata object:

```jsx
statsig.logEvent(
  user,
  "add_to_cart",
  null,
  {
    price: "9.99",
    item_name: "diet_coke_48_pack"
  }
);
```

Learn more about identifying users, group analytics, and best practices for logging events in the [logging events guide](https://docs.statsig.com/guides/logging-events).

### Sending events to Log Explorer

You can forward logs to Logs Explorer for convenient analysis using the Forward Log Line Event API. This lets you include custom metadata and event values with each log.

```jsx
const user = new StatsigUser({ userID: "a-user", custom: {
    service: "my-service",
    pod: "my-pod",
    namespace: "my-namespace",
    container: "my-container",
    // ...include any service-specific metadata
} });

// levels: trace, debug, info, log, warn, error
statsig.forwardLogLineEvent(user, "warn", "script failed to load", {
  cusom_metadata: "script_name:my-script"
  // ... include any event-specific metadata
});
```

## Using shared instance

To create a single Statsig instance accessible globally throughout your codebase, use the shared instance functionality, which provides a singleton pattern:

```jsx
// Create a shared instance that can be accessed globally
const statsig = Statsig.newShared("secret-key");
await statsig.initialize();

// Access the shared instance from anywhere in your code
const sharedStatsig = Statsig.shared();
const isFeatureEnabled = sharedStatsig.checkGate(user, "feature_name");

// Check if a shared instance exists
if (Statsig.hasSharedInstance()) {
  // Use the shared instance
}

// Remove the shared instance when no longer needed
Statsig.removeShared();
```

The shared instance helps when multiple parts of your codebase need Statsig without passing an instance between them.

* `Statsig.newShared(sdkKey, options)`: Creates a new shared instance of Statsig that you can access globally
* `Statsig.shared()`: Returns the shared instance
* `Statsig.hasSharedInstance()`: Checks if a shared instance exists (useful when the shared instance may not be ready yet)
* `Statsig.removeShared()`: Removes the shared instance (useful when you want to switch to a new shared instance)

{% callout type="note" %}
`hasSharedInstance()` and `removeShared()` are helpful in specific scenarios but aren't required in most use cases where the shared instance is set up near the top of your application.

Also note that only one shared instance can exist at a time. Attempting to create a second shared instance results in an error.
{% /callout %}

## Manual exposures

By default, the SDK automatically logs an exposure event when you check a gate, get a config, get an experiment, or call get() on a parameter in a layer. To delay exposure logging (for example, to log only after the user actually uses the feature), use manual exposures.

All main SDK functions (`checkGate`, `getDynamicConfig`, `getExperiment`, `getLayer`) accept an optional `disableExposureLogging` parameter. When set to `true`, the SDK doesn't automatically log an exposure. You can then log the exposure manually at a later time:

{% tabs %}
{% tab title="Feature Gates" %}
```jsx
const result = statsig.checkGate(aUser, 'a_gate_name', {disableExposureLogging: true});
```

```jsx
statsig.manuallyLogGateExposure(aUser, 'a_gate_name');
```
{% /tab %}

{% tab title="Dynamic Configs" %}
```jsx
const config = statsig.getDynamicConfig(aUser, 'a_dynamic_config_name',  {disableExposureLogging: true});
```

```jsx
statsig.manuallyLogDynamicConfigExposure(aUser, 'a_dynamic_config_name');
```
{% /tab %}

{% tab title="Experiments" %}
```jsx
const experiment = statsig.getExperiment(aUser, 'an_experiment_name',  {disableExposureLogging: true});
```

```jsx
statsig.manuallyLogExperimentExposure(aUser, 'an_experiment_name');
```
{% /tab %}

{% tab title="Layers" %}
```jsx
const layer = statsig.getLayer(aUser, 'a_layer_name',  {disableExposureLogging: true});
const paramValue = layer.get('a_param_name', 'fallback_value');
```

```jsx
statsig.manuallyLogLayerParameterExposure(aUser, 'a_layer_name', 'a_param_name');
```
{% /tab %}
{% /tabs %}

## Statsig user

The `StatsigUser` object represents a user in Statsig. You must provide a `userID` or at least one of the `customIDs` to identify the user.

When calling APIs that require a user, pass as much information as possible. Complete user information lets you take advantage of advanced gate and config conditions (like country or OS/browser-level checks) and correctly measure the impact of experiments on metrics and events. As explained in [why an ID is always required for server SDKs](https://docs.statsig.com/sdks/user#why-is-an-id-always-required-for-server-sdks), you must provide at least one identifier (userID or customID) to ensure a consistent experience for a given user.

In addition to userID, the top-level fields on StatsigUser are: email, ip, userAgent, country, locale, and appVersion. You can also pass any key-value pairs in an object or dictionary to the custom field and create targeting based on them.

### Private attributes

Private attributes are user attributes that Statsig uses for evaluation but doesn't forward to any integrations. They are useful for PII or sensitive data that you don't want to send to third-party services.

```typescript
const user = new StatsigUser({
  userID: "a-user-id",
  email: "user@example.com",
  ip: "192.168.1.1",
  userAgent: "Mozilla/5.0...",
  country: "US",
  locale: "en_US",
  appVersion: "1.0.0",
  custom: {
    // Custom fields
    plan: "premium",
    age: 25
  },
  customIDs: {
    // Custom ID types
    stableID: "stable-id-123"
  },
  privateAttributes: {
    // Private attributes not forwarded to integrations
    email: "private@example.com"
  }
});
```

## Statsig options

You can pass an optional `options` parameter in addition to `sdkKey` during initialization to customize the Statsig client.

{% accordion title="StatsigOptions" %}
### Parameters

{% parameter name="environment" type="string" %}
Environment parameter for evaluation.
{% /parameter %}

{% parameter name="specsUrl" type="string" %}
Custom URL for fetching feature specifications. Provide the full endpoint URL, which the SDK uses as-is.
{% /parameter %}

{% parameter name="idListsUrl" type="string" %}
Custom URL for fetching the ID list manifest (the `get_id_lists` endpoint). Provide the full endpoint URL, which the SDK uses as-is.
{% /parameter %}

{% parameter name="downloadIdListFileApi" type="string" %}
Origin (scheme and host only, no path) for downloading individual ID list files. Unlike `specsUrl` and `idListsUrl`, this takes an origin, not a full endpoint URL: the SDK appends each per-file path from the ID list manifest. Refer to [Routing endpoints through a forward proxy](#routing-endpoints-through-a-forward-proxy).
{% /parameter %}

{% parameter name="specsSyncIntervalMs" type="number" %}
How often the SDK updates specifications from Statsig servers (in milliseconds).
{% /parameter %}

{% parameter name="fallbackToStatsig" type="boolean" %}
Turn this on if you proxy `download_config_specs` / `get_id_lists` and want to fall back to the default Statsig endpoint to increase reliability.
{% /parameter %}

{% parameter name="logEventUrl" type="string" %}
Custom URL for logging events.
{% /parameter %}

{% parameter name="disableAllLogging" type="boolean" %}
If true, the SDK doesn't collect any logging within the session, including custom events and config check exposure events.
{% /parameter %}

{% parameter name="enableIdLists" type="boolean" %}
Required to be `true` when using segments with more than 1000 IDs. Refer to [ID List segments](https://docs.statsig.com/segments/add-id-list).
{% /parameter %}

{% parameter name="disableUserAgentParsing" type="boolean" %}
If true, the SDK doesn't parse User-Agent strings into `browserName`, `browserVersion`, `systemName`, `systemVersion`, and `appVersion` when needed for evaluation.
{% /parameter %}

{% parameter name="waitForUserAgentInit" type="boolean" %}
When true, the SDK waits until user agent parsing data is fully loaded during initialization (~1 second), ensuring parsing is ready before any evaluations.
{% /parameter %}

{% parameter name="disableUserCountryLookup" type="boolean" %}
If true, the SDK doesn't parse IP addresses (from `user.ip`) into country codes when needed for evaluation.
{% /parameter %}

{% parameter name="waitForCountryLookupInit" type="boolean" %}
When true, the SDK waits for country lookup data (e.g., GeoIP or YAML files) to fully load during initialization (~1 second), ensuring IP-to-country parsing is ready at evaluation time.
{% /parameter %}

{% parameter name="eventLoggingFlushIntervalMs" type="number" %}
How often the SDK flushes events to Statsig servers (in milliseconds).
{% /parameter %}

{% parameter name="eventLoggingMaxQueueSize" type="number" %}
Maximum number of events to queue before forcing a flush.
{% /parameter %}

{% parameter name="dataStore" type="DataStore" %}
An adapter with custom storage behavior for config specs. Can also continuously fetch updates in place of the Statsig network. Refer to [Data Stores](#data-store). For an example, go to the 1P Redis implementation [statsig-node-redis](https://github.com/statsig-io/node-js-server-sdk-redis).
{% /parameter %}

{% parameter name="specAdaptersConfig" type="SpecAdapterConfig[]" %}
Advanced settings to fetch from different sources (e.g., [statsig forward proxy](https://docs.statsig.com/infrastructure/api_proxy/introduction), your own proxy server, data store) or to use different network protocols (HTTP vs gRPC streaming).
{% /parameter %}

{% parameter name="observabilityClient" type="ObservabilityClient" %}
Interface to integrate observability metrics exposed by the SDK (e.g., config propagation delay, initialization time). Refer to [details](#observability-client).
{% /parameter %}

{% parameter name="persistentStorage" type="PersistentStorage" %}
Interface to use persistent storage within the SDK. Refer to [details](#persistent-storage).
{% /parameter %}

{% parameter name="proxyConfig" type="ProxyConfig" %}
Configuration for connecting through a proxy server.
{% /parameter %}

{% accordion title="ProxyConfig" %}
{% parameter name="proxyHost" type="string" %}
Proxy server host.
{% /parameter %}

{% parameter name="proxyPort" type="number" %}
Proxy server port.
{% /parameter %}

{% parameter name="proxyAuth" type="string" %}
Proxy authentication in the form "username:password".
{% /parameter %}

{% parameter name="proxyProtocol" type="string" %}
Protocol (e.g., "http", "https").
{% /parameter %}

{% parameter name="caCertPath" type="string" %}
Optional path to a PEM CA bundle for outbound TLS.
{% /parameter %}
{% /accordion %}
{% /accordion %}

### Proxy and custom network routing

Use `proxyConfig` if your service needs a standard outbound HTTP proxy. Use `specAdaptersConfig` if you are routing spec downloads through [Statsig Forward Proxy](https://docs.statsig.com/infrastructure/api_proxy/introduction) or another custom spec source.

```typescript
// Example usage:
const options: StatsigOptions = {
  environment: "staging",
  initTimeoutMs: 3000,
  proxyConfig: {
    proxyHost: "proxy.example.com",
    proxyPort: 8080,
    // proxyAuth: set if authentication is required
    proxyProtocol: "https",
    caCertPath: "/etc/ssl/certs/corporate-ca.pem",
  },
};

const statsig = new Statsig("secret-key", options);
await statsig.initialize();
```

Set `caCertPath` when your environment requires a custom PEM CA bundle for outbound TLS.

### Routing endpoints through a forward proxy

To send Statsig network traffic through your own proxy or CDN, override the endpoint options. These options don't all take the same kind of value:

* `specsUrl` and `idListsUrl` take a **full endpoint URL**. The SDK requests each URL as-is.
* `downloadIdListFileApi` takes an **origin only** (scheme and host, no path). Per-file download URLs are dynamic. Each one carries a unique file ID and signature embedded in the ID list manifest, so the SDK can't use a fixed URL. Instead, it takes each per-file path from the manifest and appends it to this origin.

```typescript
const options: StatsigOptions = {
  // Full endpoint URLs, used as-is:
  specsUrl: "https://proxy.example.com/v1/download_config_specs",
  idListsUrl: "https://proxy.example.com/v1/get_id_lists",
  // Origin only. The SDK appends each manifest file path:
  downloadIdListFileApi: "https://proxy.example.com",
};

const statsig = new Statsig("secret-key", options);
await statsig.initialize();
```

{% callout type="warning" %}
Set `downloadIdListFileApi` to an origin only, such as `https://proxy.example.com`. If you include a path such as `/v1/download_id_list_file`, the SDK appends the manifest path on top of it, producing a doubled path such as `.../v1/download_id_list_file/v1/download_id_list_file/<fileId>` and a 401 response.
{% /callout %}

Overriding only `specsUrl` and `idListsUrl` routes the ID list manifest through your proxy, but individual ID list **file** downloads still go to the Statsig CDN. Set `downloadIdListFileApi` to route those file downloads through your proxy as well.

## Shutting Statsig down

Statsig batches and periodically flushes events. To flush all logged events before shutdown, call `shutdown()` before your app or server shuts down:

```jsx
await statsig.shutdown();
```

## Client SDK bootstrapping | SSR

If you use the Statsig client SDK in a browser or mobile app, you can bootstrap the client SDK with values from the server SDK to avoid a network request on the client. This is useful for server-side rendering (SSR) or to reduce network requests on the client.

## Client initialize response

The Node Core SDK provides a method to generate a client initialize response that you can use to bootstrap client SDKs without requiring network requests.

```typescript
// Get client initialize response for a user
const values = statsig.getClientInitializeResponse(user, options);

// Pass values to a client SDK to initialize without a network request
```

{% accordion-group %}
{% accordion title="Initialize Response Options" %}
The `getClientInitializeResponse` method accepts an optional `options` parameter with the following properties:

```typescript
export interface ClientInitializeResponseOptions {
  hashAlgorithm?: string;        // Algorithm used for hashing gate/experiment names (default: 'djb2')
  clientSdkKey?: string;         // Client SDK key to use for initialization
  includeLocalOverrides?: boolean; // Whether to include local overrides in the response
  featureGateFilter?: Set<string>; // Filter to only include specific feature gates
  experimentFilter?: Set<string>;  // Filter to only include specific experiments
  dynamicConfigFilter?: Set<string>; // Filter to only include specific dynamic configs
  layerFilter?: Set<string>;     // Filter to only include specific layers
  paramStoreFilter?: Set<string>; // Filter to only include specific parameter stores
}
```
{% /accordion %}

{% accordion title="Hash Algorithm" %}
The `hashAlgorithm` option specifies which algorithm to use for hashing gate and experiment names in the client initialize response. The default is `'djb2'` for better performance and smaller payload size.

```typescript
// Use djb2 hashing algorithm for better performance
const values = statsig.getClientInitializeResponse(user, {
  hashAlgorithm: 'djb2',
});
```
{% /accordion %}

{% accordion title="Client SDK Key" %}
The `clientSdkKey` option lets you filter the response to only the specific feature gates, experiments, dynamic configs, layers, or parameter stores that a particular client key has access to, effectively letting you apply [target apps](https://docs.statsig.com/sdks/target-apps).

```typescript
// Specify a client SDK key
const values = statsig.getClientInitializeResponse(user, {
  clientSdkKey: 'client-key',
});
```
{% /accordion %}

{% accordion title="Filtering" %}
The filter options allow you to reduce the payload size by only including specific feature gates, experiments, dynamic configs, layers, or parameter stores in the response.

```typescript
// Only include specific feature gates and experiments
const values = statsig.getClientInitializeResponse(user, {
  featureGateFilter: new Set(['my_gate_1', 'my_gate_2']),
  experimentFilter: new Set(['my_experiment']),
});
```
{% /accordion %}

{% accordion title="Include Local Overrides" %}
The `includeLocalOverrides` option determines whether to consider [local overrides](#local-overrides) you've set when evaluating each config in the response.

```typescript
// Include local overrides in the response
const values = statsig.getClientInitializeResponse(user, {
  includeLocalOverrides: true,
});
```
{% /accordion %}

{% accordion title="Full Code Example" %}
Below is a complete example of using the client initialize response to bootstrap a client SDK. You can parallelize or inline the initialize response data with other requests to your server to eliminate additional round trips and latency.

```typescript
// Server-side code
import { Statsig, StatsigUser } from '@statsig/node-core';

// Initialize the server SDK
await Statsig.initialize('server-secret-key');

// In your API endpoint handler
app.get('/statsig-bootstrap', (req, res) => {
  // Create a user object from the request
  const user = new StatsigUser({
    userID: req.query.userID || '',
    email: req.query.email,
    ip: req.ip,
    userAgent: req.headers['user-agent'],
  });

  // Generate the client initialize response with filters
  const values = Statsig.getClientInitializeResponse(user, {
    hashAlgorithm: 'djb2',
    featureGateFilter: new Set(['onboarding_v2', 'new_checkout']),
    experimentFilter: new Set(['pricing_experiment']),
    layerFilter: new Set(['ui_layer']),
  });

  // Return the values to the client
  res.json({ statsigValues: values });
});
```

```typescript
// Client-side code using @statsig/js-client
import { Statsig } from '@statsig/js-client';

// Fetch bootstrap values from your API
const response = await fetch('/statsig-bootstrap');
const { statsigValues } = await response.json();

// Initialize the client SDK with the bootstrap values
await Statsig.initialize({
  sdkKey: 'client-sdk-key',
  initializeValues: statsigValues,
});
```
{% /accordion %}
{% /accordion-group %}

## SDK event subscriptions

The Statsig SDK provides an event subscription system that lets you listen for evaluation events and lifecycle events in real time. This is useful for debugging, analytics, custom logging, and integration with external systems.

### Supported events

The SDK supports subscribing to the following evaluation events:

* **`gate_evaluated`** - Fired when a feature gate is evaluated for a user
* **`dynamic_config_evaluated`** - Fired when a dynamic config is retrieved for a user
* **`experiment_evaluated`** - Fired when an experiment is evaluated for a user
* **`layer_evaluated`** - Fired when a layer is evaluated for a user
* **`specs_updated`** - Fired when the SDK updates its cached specs, including where the specs were loaded from
* **`"*"`** - Subscribe to all evaluation events

### SDK event data

Each event includes relevant context about the evaluation:

* **Gate Evaluated Events** include: `gate_name`, `value` (boolean), `rule_id`, `reason`
* **Dynamic Config Events** include: the full `dynamic_config` object with values and metadata
* **Experiment Events** include: the full `experiment` object with variant assignment and parameters
* **Layer Events** include: the full `layer` object with allocated experiment and parameters
* **Specs Updated Events** include `source`, `source_api`, and `values` metadata, where `values.time` is the timestamp of the last update to the project in the Statsig console

### Use cases

Event subscriptions are particularly useful for:

* **Debugging**: Monitor which features are being evaluated and their results
* **Analytics**: Track feature usage patterns and user segments
* **Custom Logging**: Send evaluation data to your own logging systems
* **Integration**: Forward events to external analytics or monitoring tools
* **Testing**: Verify that features are being evaluated as expected

### Best practices

* **Clean up subscriptions**: Always unsubscribe when you no longer need to listen for events to prevent memory leaks
* **Handle event data carefully**: Event objects may contain sensitive user information depending on your configuration
* **Use specific event types**: Subscribe to specific events rather than "\*" when possible for better performance
* **Avoid heavy processing**: Keep event handlers lightweight to avoid impacting SDK performance

```javascript expandable
const statsig = new Statsig('server-secret-key');

// Subscribe to gate evaluation events
const gateSubId = statsig.subscribe('gate_evaluated', (event) => {
  console.log('Gate evaluated:', {
    gateName: event.gate_name,
    value: event.value,
    ruleId: event.rule_id,
    reason: event.reason
  });
});

// Subscribe to dynamic config evaluation events
const configSubId = statsig.subscribe('dynamic_config_evaluated', (event) => {
  console.log('Config evaluated:', {
    configName: event.dynamic_config.name,
    values: event.dynamic_config.value
  });
});

// Subscribe to experiment evaluation events
const experimentSubId = statsig.subscribe('experiment_evaluated', (event) => {
  console.log('Experiment evaluated:', {
    experimentName: event.experiment.name,
    groupName: event.experiment.group_name,
    parameters: event.experiment.value
  });
});

// Subscribe to layer evaluation events
const layerSubId = statsig.subscribe('layer_evaluated', (event) => {
  console.log('Layer evaluated:', {
    layerName: event.layer.name,
    allocatedExperiment: event.layer.allocated_experiment_name,
    parameters: event.layer.value
  });
});

// Subscribe to specs updated events
const specsUpdatedSubId = statsig.subscribe('specs_updated', (event) => {
  console.log('Specs updated:', {
    source: event.data.source,
    sourceApi: event.data.source_api,
    projectLastUpdated: event.data.values.time,
  });
});

// Subscribe to all events
const allEventsSubId = statsig.subscribe('*', (event) => {
  console.log('Event received:', event.event_name, event);
});

// Unsubscribe from specific event types
statsig.unsubscribe('gate_evaluated');
statsig.unsubscribe('specs_updated');

// Unsubscribe using subscription ID
statsig.unsubscribeById(configSubId);

// Unsubscribe from all events
statsig.unsubscribeAll();
```

## Local overrides

Local Overrides let you override the values of gates, configs, experiments, and layers for testing. This is useful for local development or testing when you want to force a specific value without changing the configuration in the Statsig console.

{% tabs %}
{% tab title="Feature Gates" %}
```jsx
// Overrides the given gate to the specified value
Statsig.overrideGate("a_gate_name", true);

// Optional third parameter, overrides the gate only for a given ID
Statsig.overrideGate("a_gate_name", true, "userID-123");
```
{% /tab %}

{% tab title="Dynamic Configs" %}
```jsx
// Overrides the given dynamic config to the provided value
Statsig.overrideDynamicConfig("a_config_name", { key: "value" });

// Optional third parameter, overrides the dynamic config only for a given ID
Statsig.overrideDynamicConfig("a_config_name", { key: "value" }, "userID-123");
```
{% /tab %}

{% tab title="Experiments" %}
```jsx
// Overrides the given experiment to the provided value
Statsig.overrideExperiment("an_experiment_name", { key: "value" });

// Optional third parameter, overrides the experiment only for a given ID
Statsig.overrideExperiment("an_experiment_name", { key: "value" }, "userID-123");

// Overrides the given experiment to a particular groupname
Statsig.overrideExperimentByGroupName("an_experiment_name", "a_group_name");

// Alternatively, get the Experiment object for a given groupName
const groupExp = statsig.getExperimentByGroupName("pricing_experiment", "premium_group");
const premiumPrice = groupExp.getValue("price", 9.99);
```
{% /tab %}

{% tab title="Layers" %}
```jsx
// Overrides the given layer to the provided value
Statsig.overrideLayer("a_layer_name", { key: "value" });

// Optional third parameter, overrides the layer only for a given ID
Statsig.overrideLayer("a_layer_name", { key: "value" }, "userID-123");
```
{% /tab %}
{% /tabs %}

## Persistent storage

The Persistent Storage interface lets you implement custom storage for user-specific configurations. This enables you to persist user assignments across sessions, ensuring consistent experiment groups when a user returns. This is useful for client-side A/B testing where users must always receive the same variant.

```typescript expandable PersistentStorageInterface.ts
export interface PersistentStorage {
  load: (key: string) => UserPersistedValues | null;
  save: (key: string, config_name: string, data: StickyValues) => void;
  delete: (key: string, config_name: string) => void;
}

export interface StickyValues {
  value: boolean;
  json_value: Record<string, unknown>;
  rule_id: string;
  group_name: string | null;
  secondary_exposures: SecondaryExposure[];
  undelegated_secondary_exposures: SecondaryExposure[];
  config_delegate: string | null;
  explicit_parameters: string[] | null;
  time: number;
  configVersion?: number | undefined;
}

export type UserPersistedValues = Record<string, StickyValues>;

export interface SecondaryExposure {
  gate: string;
  gateValue: string;
  ruleId: string;
}
```

### Usage example

```typescript expandable PersistentStorageUsage.ts
import { PersistentStorage, StickyValues, UserPersistedValues } from '@statsig/statsig-node-core';

class MyPersistentStorage implements PersistentStorage {
  private storage = new Map<string, UserPersistedValues>();
  constructor() {
      this.load = this.load.bind(this);
      this.save = this.save.bind(this);
      this.delete = this.delete.bind(this);
  }

  load(key: string): UserPersistedValues | null {
    return this.storage.get(key) || null;
  }

  save(key: string, config_name: string, data: StickyValues): void {
    const existing = this.storage.get(key) || {};
    existing[config_name] = data;
    this.storage.set(key, existing);
  }

  delete(key: string, config_name: string): void {
    const existing = this.storage.get(key);
    if (existing) {
      delete existing[config_name];
      this.storage.set(key, existing);
    }
  }
  
  getUserPersistedValue(user: StatsigUser, idType: string): UserPersistedValues | null {
    const storageKey = this.getStorageKey(user, idType);
    if (storageKey !== null) {
      return this.load(storageKey);
    }
    return null;
  }

  private getStorageKey(user: StatsigUser, idType: string): string | null {
    const lowerCaseIdType = idType.toLowerCase();

    if (lowerCaseIdType === "user_id" || lowerCaseIdType === "userid") {
      const id = user.userID;
      return id ? `${id}:userID` : null;
    } else if (user.customIDs) {
      const id = user.customIDs[idType];
      return id ? `$\{id\}:${idType}` : null;
    }

    return null;
  }

}
```

{% callout type="note" %}
Statsig added persistent storage support in version 0.6.1 of the Node.js SDK.
{% /callout %}

## Data store

The Data Store interface lets you implement custom storage for Statsig configurations, enabling advanced caching strategies and integration with your preferred storage systems.

```typescript
export interface DataStore {
  initialize?: () => Promise<void>;
  shutdown?: () => Promise<void>;
  get?: (key: string) => Promise<DataStoreResponse>;
  set?: (key: string, value: string, time?: number) => Promise<void>;
  supportPollingUpdatesFor?: (key: string) => Promise<boolean>;
}

export interface DataStoreResponse {
  result?: string;
  time?: number;
}
```

For an example, go to the 1P implementation using Redis: [statsig-node-redis](https://github.com/statsig-io/node-js-server-sdk-redis).

## Custom output logger

The Output Logger interface lets you customize how the SDK logs messages, enabling integration with your own logging system and control over log verbosity.

{% accordion-group %}
{% accordion title="Logger Interface" %}
```typescript
interface OutputLoggerProvider {
  initialize?: () => void;
  debug?: (tag: string, message: string) => void;
  info?: (tag: string, message: string) => void;
  warn?: (tag: string, message: string) => void;
  error?: (tag: string, message: string) => void;
  shutdown?: () => void;
}
```
{% /accordion %}

{% accordion title="Implementation Example" %}
### Implementation example

```typescript
import { OutputLoggerProvider } from '@statsig/statsig-node-core';

class CustomOutputLogger implements OutputLoggerProvider {
  initialize = () => {
    console.log('Logger initialized');
  };

  debug = (tag: string, message: string) => {
    console.debug(`[$\{tag\}] ${message}`);
  };

  info = (tag: string, message: string) => {
    console.info(`[$\{tag\}] ${message}`);
  };

  warn = (tag: string, message: string) => {
    console.warn(`[$\{tag\}] ${message}`);
  };

  error = (tag: string, message: string) => {
    console.error(`[$\{tag\}] ${message}`);
  };

  shutdown = () => {
    console.log('Logger shutdown');
  };
}
```
{% /accordion %}

{% accordion title="Usage with StatsigOptions" %}
```typescript
import { Statsig, StatsigOptions } from '@statsig/statsig-node-core';

const customLogger = new CustomOutputLogger();

const options: StatsigOptions = {
  outputLoggerProvider: customLogger,
  outputLogLevel: 'info', // 'none' | 'debug' | 'info' | 'warn' | 'error'
};

const statsig = new Statsig('secret-key', options);
await statsig.initialize();
```
{% /accordion %}

{% accordion title="Notes" %}
* All methods in the `OutputLoggerProvider` interface are optional
* The `tag` parameter indicates the SDK component or category generating the log message
* Use `outputLogLevel` in StatsigOptions to control which log levels are actually called
* The logger initializes when the Statsig client initializes and shuts down when the client shuts down
{% /accordion %}
{% /accordion-group %}

## Observability client

The Observability Client interface lets you monitor the health of the SDK by integrating with your own observability systems. This enables you to track metrics, errors, and performance data. For more information on the metrics Statsig SDKs emit, refer to the [Monitoring documentation](https://docs.statsig.com/infrastructure/sdk-monitoring).

```typescript
export interface ObservabilityClient {
  initialize?: () => void;
  increment?: (metricName: string, value: number, tags: Record<string, string>) => void;
  gauge?: (metricName: string, value: number, tags: Record<string, string>) => void;
  dist?: (metricName: string, value: number, tags: Record<string, string>) => void;
  error?: (tag: string, error: string) => void;
}
```

## FAQs

### How do I run experiments for logged out users

### Common problems while installing

1. **Seeing SSL Error**

The binary files require certain SSL versions.

```shell
// Try run this
apt-get update && apt-get install libcurl4-openssl-dev -y && rm -rf /var/lib/apt/lists/*
```

2. **Docker build failing with platform-specific dependencies**

When building in Docker (Linux environment), the build may fail if your local `package-lock.json` or `yarn.lock` contains platform-specific dependencies for macOS. This happens because `npm install` on Mac pulls down Apple-specific variants, but Docker tries to use those locked dependencies on Linux.

**Solution:** Either install the Linux-specific variant during your Docker build step:

```dockerfile
RUN npm install @statsig/statsig-node-core-linux-x64-gnu
```

Or add both platform variants as dependencies in your `package.json`:

```json
"dependencies": {
  "@statsig/statsig-node-core": "X.Y.Z", // Common (Required)
  "@statsig/statsig-node-core-darwin-arm64": "X.Y.Z", // Mac Specific
  "@statsig/statsig-node-core-linux-x64-gnu": "X.Y.Z" // Linux Specific
}
```

## Reference

{% accordion title="All API Methods" %}
* `checkGate(user: StatsigUser, gateName: string, options?: EvaluationOptions): boolean`
* `getDynamicConfig(user: StatsigUser, configName: string, options?: EvaluationOptions): DynamicConfig`
* `getExperiment(user: StatsigUser, experimentName: string, options?: EvaluationOptions): DynamicConfig`
* `getLayer(user: StatsigUser, layerName: string, options?: EvaluationOptions): Layer`
* `getFeatureGate(user: StatsigUser, gateName: string, options?: EvaluationOptions): FeatureGate`
* `getParameterStore(user: StatsigUser, parameterStoreName: string, options?: EvaluationOptions): ParameterStore`
* `getPrompt(user: StatsigUser, promptName: string, options?: EvaluationOptions): Prompt`
* `getPromptSet(user: StatsigUser, promptSetName: string, options?: EvaluationOptions): PromptSet`
* `logEvent(user: StatsigUser, eventName: string, value?: string | number | null, metadata?: Record<string, string>): void`
* `forwardLogLineEvent(user: StatsigUser, level: string, message: string, metadata?: Record<string, string>): void`
* `manuallyLogGateExposure(user: StatsigUser, gateName: string): void`
* `manuallyLogDynamicConfigExposure(user: StatsigUser, configName: string): void`
* `manuallyLogExperimentExposure(user: StatsigUser, experimentName: string): void`
* `manuallyLogLayerParameterExposure(user: StatsigUser, layerName: string, parameterName: string): void`
* `overrideExperimentByGroupName(experimentName: string, groupName: string, id?: string | null): void`
* `getClientInitializeResponse(user: StatsigUser, options?: ClientInitializeResponseOptions): ClientInitializeResponse`
* `shutdown(): Promise<void>`
{% /accordion %}

{% accordion title="Fields Needed Methods" %}
The following methods return information about which user fields evaluation requires:

* `getGateFieldsNeeded(gateName: string): string[]`
* `getDynamicConfigFieldsNeeded(configName: string): string[]`
* `getExperimentFieldsNeeded(experimentName: string): string[]`
* `getLayerFieldsNeeded(layerName: string): string[]`

These methods return an array of strings representing the user fields required to properly evaluate the specified gate, config, experiment, or layer. Use these methods to:

* Optimize user object creation by including only necessary fields
* Understand which user attributes affect a particular feature
* Debug evaluation issues
{% /accordion %}
