Skip to main content

Why Migrate?

  • Performance: 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 will only be available in Core
  • Maintenance: The legacy SDK is in maintenance mode and will only receive critical bug fixes

Installation

npm install @statsig/statsig-node-core

StatsigUser

In the Node Core SDK, StatsigUser is now a class instead of a TypeScript type interface. This is a key architectural change that affects how you create user objects.

Type Definitions

// 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
}

Creating Users

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" });
Key Difference: In Node Core, you must use new StatsigUser({...}) to create a user object. Plain objects will not work and will cause type errors.

API Changes

Key Package and Class Changes

FeatureNode Core SDKLegacy Node SDKStatus
Package@statsig/statsig-node-corestatsig-node⚠️ Renamed
OptionsStatsigOptionsStatsigOptions✓ Same
UserStatsigUserStatsigUser⚠️ Changed from type to class
Initializeawait statsig.initialize()await statsig.initialize()✓ Same
Check Gatestatsig.checkGate()statsig.checkGate()✓ Same
Get Configstatsig.getDynamicConfig()statsig.getConfig()⚠️ Renamed
Get Experimentstatsig.getExperiment()statsig.getExperiment()✓ Same
Get Layerstatsig.getLayer()statsig.getLayer()✓ Same
Log Eventstatsig.logEvent()statsig.logEvent()✓ Same
Shutdownawait statsig.shutdown()await statsig.shutdown()✓ Same
For more details on the new API, see Node Core SDK documentation.

Singleton Pattern Changes

The Node Core SDK provides a different approach to singleton management compared to the legacy SDK.
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();
Key Difference: The legacy SDK uses a single global singleton that you access directly via Statsig.methodName(). The Node Core SDK supports both instance-based usage (new Statsig()) and an explicit shared singleton pattern (Statsig.newShared() / Statsig.shared()). The instance-based approach is recommended as it provides better control over SDK lifecycle.

Config Value Access with get() Method

Starting from version 0.10.0, the Node Core SDK supports the get() method on DynamicConfig, Experiment, and Layer objects, matching the legacy SDK’s API.
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");
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

Below are complete, working examples showing the same functionality implemented in both the legacy Node SDK and the new Node Core SDK.
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);

Behavioral Changes

The SDK now uses a lightweight YAML-based User-Agent parser based on a reduced version of the ua-parser regex definitions.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, you should parse the User-Agent externally before sending it to Statsig.
User-Agent parsing and country lookup are now lazy-loaded by default to improve SDK initialization performance.If your application relies on these features being ready immediately after initialize(), you can opt in by setting:⚠️ Enabling these options will increase total initialization time.To disable these features entirely, set the StatsigOptions flags and .
The ID List functionality is disabled by default.To enable it, set the StatsigOptions flag .
The core SDK now fetches configuration specs from a new endpoint:
  • Old: v1/download_config_specs
  • New: v2/download_config_specs
If you are hosting your own pass-through proxy, ensure it supports and correctly routes the v2 endpoint.
  • If you are using the Statsig Forward Proxy (SFP), this endpoint migration is handled automatically.
There are different places you can define environment StatsigUser and StatsigOptions, server-core try StatsigUser then StatsigOptions, then default to production. Node SDK has different orders: StatsigOptions -> StatsigUser -> default to production.
** 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: We don’t support idlist data store.

StatsigOptions Changes

The table below shows the mapping between legacy SDK options and Server Core SDK options:
Old OptionNew / Notes
apiDeprecated
idlists_thread_limit, logging_interval, enable_debug_logs, events_flushed_callbackDeprecated
timeoutDeprecated (was only for network)
apiForDownloadConfigSpecsspecsUrl – must complete with endpoint
apiForIdListsidListsUrl – must complete with endpoint
apiForLogEventlogEventUrl – must complete with endpoint
initTimeoutMsinitTimeoutMs – applies to overall initialization (suggest adding +1000 ms if enabling UA)
rulesetsSyncIntervalMsspecsSyncIntervalMs (unit: milliseconds)
idListsSyncIntervalMsidListsSyncIntervalMs (unit: milliseconds)
loggingIntervalMseventLoggingFlushIntervalMs
loggingMaxBufferSizeeventLoggingMaxQueuesize
dataAdapterStill supported but interface changed
observability_client, sdk_error_callbackNow within observability_client interface
loggerNow OutputLoggerProvider interface
bootstrap_values, evaluation_callback, rules_updated_callbackNot yet supported
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.
  • For now - keep the old package in-place, it can be helpful to have both running in parallel momentarily during local testing for your upgrade. If needed (language dependent), you may want to alias the new package.
2

Update Initialization

  • Switch to the new initialization syntax. New SDK keys aren’t needed - if you choose to update them, ensure they have the same target apps.
  • If needed, use the appropriate builder or configuration pattern for setting options, and ensure you migrate the StatsigOptions you use, as some were renamed.
3

Update User Creation

  • Migrate to the new format for creating user objects.
  • If needed, use the builder pattern or updated constructor to set user properties.
4

Update Method Calls

  • Starting with a few calls, switch your config checks from the new to old SDK, replacing oldStatsig.getExperiment() with newStatsig.getExperiment()
  • As you change the package usage, conduct some testing of 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 if they’ve changed (notably, get_config() to get_dynamic_config)
  • Once comfortable with the operation of the new SDK from call-by-call migration, consider the use of refactoring tools to migrate the bulk of your remaining calls.
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, please reach out to us: