> ## Documentation Index
> Fetch the complete documentation index at: https://launchdarkly.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Node.js (client-side) SDK 3.x to 4.0 migration guide

<View title="Developer" />

<View title="Federal docs" />

<View title="EU docs" />

This topic explains the changes in the Node.js SDK 4.0 release and how to adapt code that uses a 3.x version of the [Node.js SDK](/docs/sdk/client-side/node-js) to use version 4.0 or later.

**Version 4.0 includes several breaking changes**.

Before you migrate to version 4.0, update to the latest 3.x version. Some of the changes that are mandatory in 4.0 were originally added in a 3.x version and made optional.

## Supported Node.js versions for the 4.0 SDK

The minimum Node.js version for LaunchDarkly 4.0 SDK is 18.

LaunchDarkly no longer supports earlier Node.js versions, as stated in the [End of Life policy](https://launchdarkly.com/policies/end-of-life-policy/).

Version 4.0 uses the native `fetch` and `crypto.randomUUID` internally so the SDK no longer depends on `node-fetch` or polyfills.

## Package rename

The package name changed from `launchdarkly-node-client-sdk` to `@launchdarkly/node-client-sdk`.

First uninstall the v3.x package, then install the v4.0 package:

<CodeGroup>
  ```bash title="Uninstall v3.x" lines wrap theme={null}
  npm uninstall launchdarkly-node-client-sdk
  ```

  ```bash title="Install v4.0" lines wrap theme={null}
  npm install @launchdarkly/node-client-sdk
  ```
</CodeGroup>

### Update imports

Update any imports:

<CodeGroup>
  ```js title="Import v3.x" lines wrap theme={null}
  import { initialize } from 'launchdarkly-node-client-sdk';
  ```

  ```js title="Import v4.0" lines wrap theme={null}
  import { createClient } from '@launchdarkly/node-client-sdk';
  ```
</CodeGroup>

## References

Update the following references:

* New signature: `createClient(envKey, initialContext, options)`: the initial context is required as the second argument.
* Must call `start()`: The client is no longer ready when `createClient` returns. After `createClient()`, the app must call `client.start()` (optionally with `LDStartOptions`: `timeout`, `bootstrap`, `identifyOptions`). The promise returned by `start()` resolves when the first identify completes (or times out, or fails).
* No `identify()` before `start()`: Calling `identify()` before `start()` is an error (logged and rejected). Use `identify()` only after `start()` has been called, for subsequent context changes.

## Client initialization

In v4.0 of the SDK, client initialization has changed in the following ways:

* The `initialize` method was removed.
* Initialization is now split into two parts: `createClient` and `start`.
* `LDOptions`, which was formerly passed into the `initialize()` function, is now split into two types: `LDStartOptions` and `LDOptions`. To learn more, read [Changes to LDOptions](#changes-to-ldoptions).

This two-step process ensures that you can register all event listeners and perform any necessary setup before the client begins connecting to LaunchDarkly. This eliminates race conditions where events might be missed if listeners were registered after the client had already started initializing.

Here is the new client initialization method:

<CodeGroup>
  ```js title="Node.js SDK v4.0" lines wrap theme={null}
    import { createClient } from '@launchdarkly/js-client-sdk';

    // Create client
    const client = createClient('example-client-side-id', context, options);

    // Then start the client
    client.start();
  ```

  ```js title="Node.js SDK v3.x" lines wrap theme={null}
    import LDClient from 'launchdarkly-js-client-sdk';

    const client = LDClient.initialize('example-client-side-id', context, options);
  ```

  ```ts title="Node.js SDK v3.x (TypeScript)" lines wrap theme={null}
  import * as LDClient from 'launchdarkly-js-client-sdk';

  const client = LDClient.initialize('example-client-side-id', context, options);
  ```
</CodeGroup>

### Client initialization flow

In v4.0 of the SDK, client initialization flow has changed in the following ways:

* In v4.0 the `waitForInitialization()` method returns a result object instead of rejecting promises. The v3.x `waitUntilReady()` method has been removed.
* `waitForInitialization()` now always resolves, never rejects, and returns a result object with a `status` field.
* `timeout` is now specified as an option object, `{ timeout: 5 }`, instead of a direct parameter.
* The result object lets you handle all cases, including success, failure, and timeout, without try/catch.

Here is the new client initialization flow:

<CodeGroup>
  ```js title="Node.js SDK v4.0" expandable lines wrap theme={null}
    // Recommended: Using waitForInitialization (always resolves with status)
    const result = await client.waitForInitialization({ timeout: 5 });

    if (result.status === 'complete') {
    // Client initialized successfully
    } else if (result.status === 'failed') {
    // Client failed to initialize
    console.error('Initialization failed:', result.error);
    } else if (result.status === 'timeout') {
    // Initialization timed out
    console.error('Initialization timed out');
    }

    // Note: Events still work if you prefer that approach
    client.on('ready', () => {
    // Client is ready (success or failure)
    });
    client.on('initialized', () => {
    // Client initialized successfully
    });
    client.on('failed', (err) => {
    // Client failed to initialize
    });
  ```

  ```js title="Node.js SDK v3.x" expandable lines wrap theme={null}
    // Option 1: Using waitUntilReady (never rejects)
    await client.waitUntilReady();

    // Option 2: Using waitForInitialization (rejects on failure)
    try {
    await client.waitForInitialization(5);
    } catch (err) {
    // Failure - but this could be an unhandled rejection if not caught
    }

    // Option 3: Using event listeners
    client.on('ready', () => {
    // Client is ready (success or failure)
    });
    client.on('initialized', () => {
    // Client initialized successfully
    });
    client.on('failed', (err) => {
    // Client failed to initialize
    });
  ```
</CodeGroup>

## Changes to LDOptions

In v4.0 of the SDK, `LDOptions` has changed in the following ways:

* The `bootstrap` option moved from `LDOptions` to `LDStartOptions` and `identify`. Bootstrapping data is now part of the initialization of a client instance. `identify` is a key part of the client initialization process required to associate the instance with an initial context.
* The SDK now defaults to using local storage-based caching. Previously, to enable local storage caching, you needed to set this as a special value for the `bootstrap` property.
* Version 3.x of the SDK used the `streamUrl`, `baseUrl`, and `eventsUrl` properties to specify the base URIs for alternative service endpoints. Version 4.0 of the SDK uses the `streamUri`, `baseUri`, and `eventsUri` properties to specify the base URIs for alternative service endpoints.

To learn more, read the following SDK documentation:

* [LDOptions](https://launchdarkly.github.io/js-core/packages/sdk/node-client/docs/interfaces/LDOptions.html)
* [LDStartOptions](https://launchdarkly.github.io/js-core/packages/sdk/node-client/docs/interfaces/LDStartOptions.html)
* [LDIdentifyOptions](https://launchdarkly.github.io/js-core/packages/sdk/node-client/docs/interfaces/LDIdentifyOptions.html)

## Identify flow

Version 4.0 `identify()` returns a promise that always resolves to an `LDIdentifyResult` object. It does not throw. Instead, success or failure is indicated by the resolved value.

Here are the return type and statuses:

* Return type: `Promise<LDIdentifyResult>`
* Result statuses:
  * `{ status: 'completed' }`: identification succeeded
  * `{ status: 'error', error: Error }`: Identification failed.
  * `{ status: 'timeout', timeout: number }`: Identification did not complete within the configured timeout.
  * `{ status: 'shed' }`: The identify was shed, for example, when using `sheddable: true` and a newer identify superseded it.

Here is the identify call:

<CodeGroup>
  ```typescript title="Identify, v4.0" lines wrap theme={null}
  try {
    await client.identify(newContext);
    // success
  } catch (err) {
    // handle error or timeout
  }
  ```
</CodeGroup>

Here is what the identify call returns:

<CodeGroup>
  ```typescript title="Result object, v4.0" lines wrap theme={null}
  const result = await client.identify(newContext);
  if (result.status === 'completed') {
    // success
  } else if (result.status === 'error') {
    // result.error
  } else if (result.status === 'timeout') {
    // result.timeout (seconds)
  }
  ```
</CodeGroup>

You can still `await client.identify(context)` without inspecting the result if you do not need to handle errors or timeouts explicitly.

## Changes to events

Version 4.0 of the SDK includes changes to the `allFlags()` method and the `sendEvents` configuration.

### Changes to analytics events

In v4.0 of the SDK, the `allFlags()` method no longer sends [analytics events](/docs/sdk/concepts/events). To learn more, read [Getting all flags](/docs/sdk/features/all-flags#node-js-client-side).

### Changes to sending events

In v4.0 of the SDK, you can disable sending events by [setting the `sendEvents` configuration](https://launchdarkly.github.io/js-core/packages/sdk/node-client/docs/interfaces/LDOptions.html#sendEvents) to `false`.

## Flag listener changes

In v4.0 of the SDK, flag listeners have changed in the following ways:

* The `change` event listener now receives `(context, changedKeys)`, where `changedKeys` is an array of strings. The SDK no longer returns the flag value object along with this event.
* The event does not include flag values. You must call `variation()` to get the current value.
* `change:<example-flag-key>` event listener now only receives `(context)`.

Here is the new flag listener method:

<CodeGroup>
  ```js title="Node.js SDK v4.0" expandable lines wrap theme={null}
  // General change event - fires when any flags change
  client.on('change', (context, changedKeys) => {
    // context: The LDContext for which flags changed
    // changedKeys: Array of flag keys that changed

    // Still need to call variation() to get current values
    changedKeys.forEach(flagKey => {
      const flagValue = client.variation(flagKey, defaultValue);
    });
  });

  // Specific flag change event - fires when a specific flag changes
  client.on('change:example-flag-key', (context) => {
    // Only fires when 'my-flag' changes
    const flagValue = client.variation('example-flag-key', false);
  });
  ```

  ```js title="Node.js SDK v3.x" lines wrap theme={null}
  // The exact signature may have varied, but typically:
    client.on('change', (changedFlags) => {
    // changedFlags is a key value pair where the flag key is mapped
    // to a diff object.
  });
  ```
</CodeGroup>

## Runtime connection-mode control

Version 4.0 starts in the mode `initialConnectionMode` specifies. The default mode is `streaming`. You can change the mode at runtime using `setConnectionMode`, and the current mode read back using `getConnectionMode`. Valid modes are `offline`, `streaming`, and `polling`.

Here is an example:

<CodeGroup>
  ```typescript title="Connection mode, v4.0" lines wrap theme={null}
  const client = createClient(envKey, initialContext, {
    initialConnectionMode: 'streaming',
  });
  await client.start();

  await client.setConnectionMode('offline'); // disconnect from LD
  await client.setConnectionMode('polling'); // resume in polling mode
  ```
</CodeGroup>

Version 3.x is configuration-only and has no runtime control over connection mode.

## Storage configuration

Version 4.0 configures the persistent cache, which includes anonymous-key persistence and last-known flag values, using a `storage` option on `LDOptions`. The option accepts any object that satisfies the `Storage` interface, including `get`, `set`, and `clear`, replacing the previous `localStoragePath` field.

Omitting `storage` uses the built-in file-backed cache at `<cwd>/ldclient-user-cache`:

<CodeGroup>
  ```typescript title="Storage configuration, v3.x" lines wrap theme={null}
  const client = createClient(envKey, initialContext, {
    localStoragePath: '/var/cache/ldclient',
  });
  ```

  ```typescript title="Storage configuration, v4.0" lines wrap theme={null}
  import { createClient, createDefaultStorage } from '@launchdarkly/node-client-sdk';

  const client = createClient(envKey, initialContext, {
    storage: createDefaultStorage({ localStoragePath: '/var/cache/ldclient' }),
  });
  ```
</CodeGroup>

To set up custom storage implementation:

<CodeGroup>
  ```typescript title="Custom storage implementation, v4.0" lines wrap theme={null}
  import type { Storage } from '@launchdarkly/js-client-sdk-common';

  const inMemory: Storage = {
    get: async (key) => cache.get(key) ?? null,
    set: async (key, value) => { cache.set(key, value); },
    clear: async (key) => { cache.delete(key); },
  };

  const client = createClient(envKey, initialContext, {
    storage: inMemory,
  });
  ```
</CodeGroup>

## Persistent cache format

The on-disk cache used by anonymous-key persistence and last-known flag
values changed format:

* Version 3.x stored entries using `node-localstorage`, with one file per key inside `<localStoragePath>/ldclient-user-cache/`
* Version 4.0 stores all entries in a single `<localStoragePath>/ldclient-user-cache/ldcache.json` file

The default location `<cwd>/ldclient-user-cache` is unchanged, but existing v3.x cache data will not be read. The next anonymous identify will generate a fresh anonymous key. Existing flag values will repopulate from the network on the next sync.

## Secure mode hash

Version 3.x accepted the secure mode hash as a positional second argument to `identify()`.

Version 4.0 accepts the hash both at construction time as a static default
and per-identify using `LDIdentifyOptions`. When you provide a per-identify hash, it takes precedence over the config-level value for that call and all subsequent network requests until the next `identify`.

Here is an example:

<CodeGroup>
  ```js title="Secure mode hash, v3.x" lines wrap theme={null}
  // Before
  client.identify(newContext, newHash, callback);
  ```

  ```typescript title="Secure mode hash, v4.0" lines wrap theme={null}
  // Static hash -- applies to all identifies that do not pass their own
  const client = createClient(envKey, initialContext, { hash: 'default-hash' });
  await client.start();

  // Per-identify hash -- overrides the config hash for this call
  await client.identify(newContext, { hash: 'per-context-hash' });

  // No hash on this call -- falls back to 'default-hash'
  await client.identify(anotherContext);
  ```
</CodeGroup>

If your application never needs to rotate the hash, pass it once in `LDOptions` and omit it from individual `identify` calls.

## Mobile key support

By default, `createClient` treats the first argument as a client-side ID. To authenticate with a mobile key instead, set `useMobileKey: true`.

Here is an example:

<CodeGroup>
  ```typescript title="Mobile key, v4.0" lines wrap theme={null}
  const client = createClient(mobileKey, initialContext, {
    useMobileKey: true,
  });
  await client.start();
  ```
</CodeGroup>

`useMobileKey` defaults to `false`, so existing code that passes a client-side ID continues to work without changes.

Mobile keys do not support secure mode. Setting both `useMobileKey: true` and `hash` (the secure mode hash) causes `createClient` to throw at construction time:

<CodeGroup>
  ```typescript title="Invalid configuration" lines wrap theme={null}
  // Throws: Invalid configuration: secure mode "hash" is not supported when "useMobileKey" is true.
  createClient(mobileKey, initialContext, {
    useMobileKey: true,
    hash: 'some-hash',
  });
  ```
</CodeGroup>

## Removed dependencies

These dependencies were removed in v4.0:

* `node-localstorage` was replaced by an in-tree fs-backed implementation

## What was deprecated

All types and methods that were marked as deprecated in the last 3.x release have been removed from the 4.0 release. If you were using these with a recent version previously, you should already have received deprecation warnings at compile time, with suggestions about their recommended replacements.

For a full list of deprecated types and methods, read the [release notes in GitHub](https://github.com/launchdarkly/node-client-sdk/releases).
