# Node Core SDK Migration Guide

## Why migrate

- **Performance**: Node JS Core evaluates faster than the legacy SDK.
- **New Features**: Access to Parameter Stores, CMAB (Contextual Multi-Armed Bandits), observabilityClient, and more.
- **Future Support**: All new features and improvements are only available in Node JS Core.
- **Maintenance**: The legacy Node JS SDK is in maintenance mode and only receives critical bug fixes.

## Installation

{% codetabs %}

```bash Node Core
npm install @statsig/statsig-node-core
```

```bash Node Legacy
npm install statsig-node
```

{% /codetabs %}

## StatsigUser

In the Node Core SDK, `StatsigUser` is now a **class** instead of a TypeScript type interface. The change from type to class affects how you create user objects.

### Type definitions

{% codetabs %}

```typescript Node Core (Class)
// StatsigUser is a class with a constructor
class StatsigUser {
  constructor(
    args: ({ userID: string } | { customIDs: Record<string, string> }) &
      StatsigUserArgs,
  );

  // Static factory methods
  static withUserID(userId: string): StatsigUser;
  static withCustomIDs(customIds: Record<string, string>): StatsigUser;

  // Properties with getters/setters
  userID: string | null;
  customIDs: Record<string, string> | null;
  email: string | null;
  ip: string | null;
  userAgent: string | null;
  country: string | null;
  locale: string | null;
  appVersion: string | null;
  custom: Record<
    string,
    string | number | boolean | Array<string | number | boolean>
  > | null;
  privateAttributes: Record<
    string,
    string | number | boolean | Array<string | number | boolean>
  > | null;
  statsigEnvironment: {
    tier?: string;
    [key: string]: string | undefined;
  } | null;

  toJSON(): string;
}
```

```typescript Node Legacy (Type)
// StatsigUser is a TypeScript type (plain object)
type StatsigUser =
  // At least one of userID or customIDs must be provided
  ({ userID: string } | { customIDs: Record<string, string> }) & {
    userID?: string;
    customIDs?: Record<string, string>;
    email?: string;
    ip?: string;
    userAgent?: string;
    country?: string;
    locale?: string;
    appVersion?: string;
    custom?: Record<
      string,
      string | number | boolean | Array<string> | undefined
    >;
    privateAttributes?: Record<
      string,
      string | number | boolean | Array<string> | undefined
    > | null;
    statsigEnvironment?: StatsigEnvironment;
  };
```

{% /codetabs %}

### Creating users

{% codetabs %}

```typescript Node Core
import { StatsigUser } from "@statsig/statsig-node-core";

// Option 1: Using constructor with object
const user = new StatsigUser({
  userID: "user_123",
  email: "user@example.com",
  ip: "192.168.0.1",
  userAgent: "Mozilla/5.0",
  country: "US",
  custom: {
    subscription_level: "premium",
  },
});

// Option 2: Using static factory method
const userById = StatsigUser.withUserID("user_123");

// Option 3: Using customIDs
const userByCustomId = StatsigUser.withCustomIDs({ companyID: "company_456" });
```

```typescript Node Legacy
import { StatsigUser } from "statsig-node";

// Plain object - no constructor needed
const user: StatsigUser = {
  userID: "user_123",
  email: "user@example.com",
  ip: "192.168.0.1",
  userAgent: "Mozilla/5.0",
  country: "US",
  custom: {
    subscription_level: "premium",
  },
};

// Using customIDs
const userByCustomId: StatsigUser = {
  customIDs: { companyID: "company_456" },
};
```

{% /codetabs %}

{% callout type="note" %}
**Key Difference**: In Node Core, you must use `new StatsigUser({...})` to create a user object. Plain objects won't work and cause type errors.
{% /callout %}

## API changes

### Key package and class changes

| Feature        | Node Core SDK                | Legacy Node SDK              | Status                        |
| -------------- | ---------------------------- | ---------------------------- | ----------------------------- |
| Package        | `@statsig/statsig-node-core` | `statsig-node`               | ⚠️ Renamed                    |
| Options        | `StatsigOptions`             | `StatsigOptions`             | ✓ Same                        |
| User           | `StatsigUser`                | `StatsigUser`                | ⚠️ Changed from type to class |
| Initialize     | `await statsig.initialize()` | `await statsig.initialize()` | ✓ Same                        |
| Check Gate     | `statsig.checkGate()`        | `statsig.checkGate()`        | ✓ Same                        |
| Get Config     | `statsig.getDynamicConfig()` | `statsig.getConfig()`        | ⚠️ Renamed                    |
| Get Experiment | `statsig.getExperiment()`    | `statsig.getExperiment()`    | ✓ Same                        |
| Get Layer      | `statsig.getLayer()`         | `statsig.getLayer()`         | ✓ Same                        |
| Log Event      | `statsig.logEvent()`         | `statsig.logEvent()`         | ✓ Same                        |
| Shutdown       | `await statsig.shutdown()`   | `await statsig.shutdown()`   | ✓ Same                        |

For more details on the new API, refer to the [Node Core SDK documentation](https://docs.statsig.com/server-core/node-core).

### Singleton pattern changes

The Node Core SDK provides a different approach to singleton management compared to the legacy SDK.

{% codetabs %}

```typescript Node Core (Instance-based + Shared Singleton)
import { Statsig, StatsigUser } from "@statsig/statsig-node-core";

// Option 1: Instance-based (recommended for most use cases)
const statsig = new Statsig("secret-key");
await statsig.initialize();

// Option 2: Shared singleton pattern
Statsig.newShared("secret-key", options); // Creates the shared instance
await Statsig.shared().initialize(); // Access via Statsig.shared()

// Check if shared instance exists
if (Statsig.hasShared()) {
  const result = Statsig.shared().checkGate(user, "my_gate");
}

// Remove shared instance when done
Statsig.removeSharedInstance();
```

```typescript Node Legacy (Global Singleton)
// Single global singleton - no instance creation needed
await Statsig.initialize("secret-key", options);

// All methods called directly on the Statsig object
const result = Statsig.checkGate(user, "my_gate");

// Shutdown the global instance
await Statsig.shutdown();
```

{% /codetabs %}

{% callout type="note" %}
**Key Difference**: The legacy SDK uses a single global singleton that you access directly using `Statsig.methodName()`. The Node Core SDK supports both instance-based usage (`new Statsig()`) and an explicit shared singleton pattern (`Statsig.newShared()` / `Statsig.shared()`). Statsig recommends the instance-based approach because it provides better control over SDK lifecycle.
{% /callout %}

### Config value access with `get()` method

Starting in version **0.10.0**, the Node Core SDK supports the `get()` method on `DynamicConfig`, `Experiment`, and `Layer` objects, matching the legacy SDK's API.

{% codetabs %}

```typescript Node Core (0.10.0+)
import { Statsig, StatsigUser } from "@statsig/statsig-node-core";

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

const user = new StatsigUser({ userID: "user_123" });

// Get a dynamic config
const config = statsig.getDynamicConfig(user, "my_config");

// Access values using get() with type-safe fallback (available in 0.10.0+)
const stringValue = config.get("param_name", "default_string");
const numberValue = config.get("count", 0);
const boolValue = config.get("enabled", false);

// Alternative: getValue() also available
const value = config.getValue("param_name", "default");

// Same pattern works for experiments
const experiment = statsig.getExperiment(user, "my_experiment");
const variant = experiment.get("variant", "control");

// And layers
const layer = statsig.getLayer(user, "my_layer");
const layerValue = layer.get("param", "default");
```

```typescript Node Legacy
const Statsig = require("statsig-node");

await Statsig.initialize("secret-key");

const user = { userID: "user_123" };

// Get a dynamic config (note: getConfig vs getDynamicConfig)
const config = Statsig.getConfig(user, "my_config");

// Access values using get() with type-safe fallback
const stringValue = config.get("param_name", "default_string");
const numberValue = config.get("count", 0);
const boolValue = config.get("enabled", false);

// Alternative: getValue() also available
const value = config.getValue("param_name", "default");

// Same pattern works for experiments
const experiment = Statsig.getExperiment(user, "my_experiment");
const variant = experiment.get("variant", "control");

// And layers
const layer = Statsig.getLayer(user, "my_layer");
const layerValue = layer.get("param", "default");
```

{% /codetabs %}

{% callout type="note" %}
The `get<T>(paramName, fallback)` method provides type inference based on the fallback value type, making it easier to work with typed configuration values.
{% /callout %}

## Complete sample code

The following are complete, working examples showing the same functionality in both the legacy Node SDK and the Node Core SDK.

{% codetabs %}

```typescript Node Core (Complete Example)
import {
  Statsig,
  StatsigUser,
  StatsigOptions,
} from "@statsig/statsig-node-core";

async function main() {
  // 1. Configure options
  const options: StatsigOptions = {
    environment: "development",
    specsSyncIntervalMs: 60000,
    eventLoggingFlushIntervalMs: 10000,
  };

  // 2. Create SDK instance (instance-based approach)
  const statsig = new Statsig("secret-your-server-key", options);

  // 3. Initialize the SDK
  await statsig.initialize();

  // 4. Create a user (must use class constructor)
  const user = new StatsigUser({
    userID: "user_123",
    email: "user@example.com",
    custom: {
      subscription_tier: "premium",
      signup_date: "2024-01-15",
    },
  });

  // 5. Check a feature gate
  const showNewFeature = statsig.checkGate(user, "new_feature_gate");
  console.log("Show new feature:", showNewFeature);

  // 6. Get a dynamic config (note: getDynamicConfig, not getConfig)
  const config = statsig.getDynamicConfig(user, "app_config");
  const welcomeMessage = config.get("welcome_message", "Hello!");
  const maxItems = config.get("max_items", 10);
  console.log("Welcome message:", welcomeMessage);
  console.log("Max items:", maxItems);

  // 7. Get an experiment
  const experiment = statsig.getExperiment(user, "checkout_flow_test");
  const variant = experiment.get("variant", "control");
  const buttonColor = experiment.get("button_color", "#007bff");
  console.log("Experiment variant:", variant);
  console.log("Button color:", buttonColor);

  // 8. Get a layer
  const layer = statsig.getLayer(user, "homepage_layer");
  const heroText = layer.get("hero_text", "Welcome to our app");
  console.log("Hero text:", heroText);

  // 9. Log a custom event
  statsig.logEvent(user, "purchase_completed", 49.99, {
    item_id: "SKU_12345",
    currency: "USD",
  });

  // 10. Shutdown gracefully
  await statsig.shutdown();
  console.log("Statsig shutdown complete");
}

main().catch(console.error);
```

```typescript Node Legacy (Complete Example)
const Statsig = require("statsig-node");

async function main() {
  // 1. Configure options
  const options = {
    environment: { tier: "development" },
    rulesetsSyncIntervalMs: 60000,
    loggingIntervalMs: 10000,
  };

  // 2. Initialize the SDK (global singleton)
  await Statsig.initialize("secret-your-server-key", options);

  // 3. Create a user (plain object)
  const user = {
    userID: "user_123",
    email: "user@example.com",
    custom: {
      subscription_tier: "premium",
      signup_date: "2024-01-15",
    },
  };

  // 4. Check a feature gate
  const showNewFeature = Statsig.checkGate(user, "new_feature_gate");
  console.log("Show new feature:", showNewFeature);

  // 5. Get a dynamic config (note: getConfig, not getDynamicConfig)
  const config = Statsig.getConfig(user, "app_config");
  const welcomeMessage = config.get("welcome_message", "Hello!");
  const maxItems = config.get("max_items", 10);
  console.log("Welcome message:", welcomeMessage);
  console.log("Max items:", maxItems);

  // 6. Get an experiment
  const experiment = Statsig.getExperiment(user, "checkout_flow_test");
  const variant = experiment.get("variant", "control");
  const buttonColor = experiment.get("button_color", "#007bff");
  console.log("Experiment variant:", variant);
  console.log("Button color:", buttonColor);

  // 7. Get a layer
  const layer = Statsig.getLayer(user, "homepage_layer");
  const heroText = layer.get("hero_text", "Welcome to our app");
  console.log("Hero text:", heroText);

  // 8. Log a custom event
  Statsig.logEvent(user, "purchase_completed", 49.99, {
    item_id: "SKU_12345",
    currency: "USD",
  });

  // 9. Shutdown gracefully
  await Statsig.shutdown();
  console.log("Statsig shutdown complete");
}

main().catch(console.error);
```

{% /codetabs %}

## Behavioral changes

{% accordion-group %}
{% accordion title="User-Agent Parsing" %}
The SDK now uses a lightweight YAML-based User-Agent parser based on a reduced version of the [ua-parser regex definitions](https://github.com/statsig-io/statsig-server-core/blob/00ad0e4024ca5d30f21892c8f2f23e836165a509/statsig-rust/resources/ua_parser_regex_lite.yaml#L4).

This parser supports the following commonly used browsers:

- Chrome
- Firefox & Firefox Mobile
- Safari & Mobile Safari
- Chrome Mobile
- Android
- Edge & Edge Mobile
- IE Mobile
- Opera Mobile

If your use case requires identifying less common browsers, parse the User-Agent externally before sending it to Statsig.
{% /accordion %}

{% accordion title="Lazy Initialization: User-Agent Parser & Country Lookup" %}
User-Agent parsing and country lookup are now **lazy-loaded by default** to improve SDK initialization performance.

If your application requires these features to be ready immediately after `initialize()`, opt in by setting:

- `waitForUserAgentInit`
- `waitForCountryLookupInit`

Enabling these options increases total initialization time.

To disable these features entirely, set the `StatsigOptions` flags `disableUserAgentParsing` and `disableUserCountryLookup`.
{% /accordion %}

{% accordion title="ID List Feature" %}
The ID List functionality is **disabled by default**.

To enable it, set the `StatsigOptions` flag `enableIdLists`.
{% /accordion %}

{% accordion title="Endpoint Changes (v1 to v2)" %}
The core SDK now fetches configuration specs from a new endpoint:

- **Old**: `v1/download_config_specs`
- **New**: `v2/download_config_specs`

If you host your own **pass-through proxy**, ensure it supports and correctly routes the `v2` endpoint.

- If you use the **Statsig Forward Proxy (SFP)**, Statsig handles this endpoint migration automatically.

{% /accordion %}

{% accordion title="Environment Evaluation" %}
The server-core SDK evaluates environment in this order: StatsigUser, then StatsigOptions, then defaults to production. The Node SDK uses a different order: StatsigOptions, then StatsigUser, then defaults to production.
{% /accordion %}

{% accordion title="DataStore cache format" %}
**New DataStore cache format**

- **Old**: (Except Node v6+)
  - config_spec cache key is: statsig.cache
  - IDLists statsig.id_lists
- **New**
  - config_spec cache key: `statsig|/v2/download_config_specs|plain_text|{SHA256HashedBase64(secretkey)}`
  - IDLists: Statsig doesn't support idlist data store.

{% /accordion %}
{% /accordion-group %}

## StatsigOptions changes

The table below shows the mapping between legacy SDK options and Server Core SDK options:

| Old Option                                                                                 | New / Notes                                                                                  |
| ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| `api`                                                                                      | Deprecated                                                                                   |
| `idlists_thread_limit`, `logging_interval`, `enable_debug_logs`, `events_flushed_callback` | Deprecated                                                                                   |
| `timeout`                                                                                  | Deprecated (was only for network)                                                            |
| `apiForDownloadConfigSpecs`                                                                | `specsUrl` – must complete with endpoint                                                     |
| `apiForIdLists`                                                                            | `idListsUrl` – must complete with endpoint                                                   |
| `apiForLogEvent`                                                                           | `logEventUrl` – must complete with endpoint                                                  |
| `initTimeoutMs`                                                                            | `initTimeoutMs` – applies to overall initialization (suggest adding +1000 ms if enabling UA) |
| `rulesetsSyncIntervalMs`                                                                   | `specsSyncIntervalMs` (unit: milliseconds)                                                   |
| `idListsSyncIntervalMs`                                                                    | `idListsSyncIntervalMs` (unit: milliseconds)                                                 |
| `loggingIntervalMs`                                                                        | `eventLoggingFlushIntervalMs`                                                                |
| `loggingMaxBufferSize`                                                                     | `eventLoggingMaxQueuesize`                                                                   |
| `dataAdapter`                                                                              | Still supported but interface changed                                                        |
| `observability_client`, `sdk_error_callback`                                               | Now within `observability_client` interface                                                  |
| `logger`                                                                                   | Now `OutputLoggerProvider` interface                                                         |
| `bootstrap_values`, `evaluation_callback`, `rules_updated_callback`                        | Not yet supported                                                                            |

## Recommended migration path

{% steps %}
{% step title="Add the new Dependencies" %}

- Add the new SDK package/module and any required platform-specific dependencies for your environment.
- Update import or require statements to reference the new SDK namespace or module.
- Keep the old package in place during local testing; having both running in parallel briefly can help. Alias the new package if needed.

{% /step %}

{% step title="Update Initialization" %}

- Switch to the new initialization syntax. New SDK keys aren't required. If you update them, ensure they have the same target apps.
- Use the appropriate builder or configuration pattern for setting options, and migrate the StatsigOptions you use, because some were renamed.

{% /step %}

{% step title="Update User Creation" %}

- Migrate to the new format for creating user objects.
- Use the builder pattern or updated constructor to set user properties if needed.

{% /step %}

{% step title="Update Method Calls" %}

- Start by migrating a few calls, replacing `oldStatsig.getExperiment()` with `newStatsig.getExperiment()`.
- Test your service locally or with existing test patterns to build confidence in the new SDK's operation.
- As you migrate additional calls, update method names that changed (notably, `get_config()` to `get_dynamic_config()`).
- After completing the initial migration, use refactoring tools to migrate remaining calls in bulk.

{% /step %}

{% step title="Test Thoroughly" %}

- Verify that all of your configs are successfully migrated. Run your test suites end-to-end.

{% /step %}

{% step title="Remove old SDK" %}

- Remove references to the old SDK, and clean up old initialization and import logic.

{% /step %}
{% /steps %}

## Need help

If you encounter any issues during migration, reach out:

- [Statsig Slack Community](https://statsig.com/slack)
- [GitHub Issues](https://github.com/statsig-io/statsig-server-core/issues)
