---
title: Node Core SDK Migration Guide
description: Migrate from the legacy Node JS Server SDK to the new Node JS Core SDK
product: general
token_estimate: 4439
---
# Node Core SDK Migration Guide

> For AI agents: a documentation index is available at [/llms.txt](/llms.txt). Append `.md` to any page URL for markdown, or send `Accept: text/markdown`.

## 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

#### Node Core

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

#### Node Legacy

```bash
npm install statsig-node
```

## 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

#### Node Core (Class)

```typescript
// 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;
}
```

#### Node Legacy (Type)

```typescript
// 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;
  };
```

### Creating users

#### Node Core

```typescript
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" });
```

#### Node Legacy

```typescript
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" },
};
```

> **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.

## 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.

#### Node Core (Instance-based + Shared Singleton)

```typescript
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();
```

#### Node Legacy (Global Singleton)

```typescript
// 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();
```

> **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.

### 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.

#### Node Core (0.10.0+)

```typescript
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");
```

#### Node Legacy

```typescript
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");
```

> **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.

## Complete sample code

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

#### Node Core (Complete Example)

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

#### Node Legacy (Complete Example)

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

## Behavioral changes

#### 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.

#### 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`.

#### ID List Feature

The ID List functionality is **disabled by default**.

To enable it, set the `StatsigOptions` flag `enableIdLists`.

#### 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.

#### 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.

#### 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.

## 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

1. **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.
2. **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.
3. **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.
4. **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.
5. **Test Thoroughly**

   - Verify that all of your configs are successfully migrated. Run your test suites end-to-end.
6. **Remove old SDK**

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

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

