Skip to content

Repository files navigation

Optimizely React SDK

This repository houses the React SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy).

Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at Optimizely.com, or see the developer documentation.

Table of Contents

Get Started

Refer to the React SDK's developer documentation for detailed instructions on getting started with using the SDK.

For React Native, review the React Native developer documentation.

Features

  • Modular, tree-shakeable architecture built on the JS SDK v6
  • Automatic datafile polling and hook re-evaluation on config changes
  • User context managed by the Provider
  • Async decision hooks for CMAB and async User Profile Service lookups
  • Server-safe entry point for React Server Components
  • Pre-built hooks for single-flag, multi-flag, and all-flag decisions

Compatibility

RequirementVersion
React>= 16.8.0
Node.js>= 18.0.0
Module formatESM only

Installation

npm install @optimizely/react-sdk

For React Native, the installation is slightly different. Check out the:

Quick Example

import{createInstance,createPollingProjectConfigManager,createBatchEventProcessor,OptimizelyProvider,useDecide,}from'@optimizely/react-sdk';constoptimizelyClient=createInstance({projectConfigManager: createPollingProjectConfigManager({sdkKey: 'your-optimizely-sdk-key',}),eventProcessor: createBatchEventProcessor(),});functionMyComponent(){const{ decision, isLoading, error }=useDecide('sort-algorithm');if(isLoading||error)returnnull;return(<>{decision.variationKey==='relevant_first'&&<RelevantFirstList/>}{decision.variationKey==='recent_first'&&<RecentFirstList/>}</>);}functionApp(){return(<OptimizelyProviderclient={optimizelyClient}timeout={500}user={{id: window.userId,attributes: {plan_type: 'bronze'}}}><MyComponent/></OptimizelyProvider>);}

Usage

Client Creation

createInstance

Creates an Optimizely Client instance that powers the Provider and hooks. The client does not hold user state — user management is handled by <OptimizelyProvider>.

Important: You must use createInstance from @optimizely/react-sdk, not from @optimizely/optimizely-sdk. A client created directly from the JS SDK will not work correctly with <OptimizelyProvider> and hooks.

arguments

  • config : object — Configuration object with the following fields:
ParameterTypeRequiredDescription
projectConfigManagerOpaqueConfigManagerYesManages the datafile. Create with createPollingProjectConfigManager or createStaticProjectConfigManager.
eventProcessorOpaqueEventProcessorNoProcesses and dispatches events. Create with createBatchEventProcessor or createForwardingEventProcessor.
jsonSchemaValidator{ validate(jsonObject: unknown): boolean }NoCustom validator for datafile schema validation.
loggerOpaqueLoggerNoLogger instance. Create with createLogger.
errorNotifierOpaqueErrorNotifierNoError notification handler. Create with createErrorNotifier.
userProfileServiceUserProfileServiceNoSynchronous user profile service for sticky bucketing.
userProfileServiceAsyncUserProfileServiceAsyncNoAsynchronous user profile service for sticky bucketing.
defaultDecideOptionsOptimizelyDecideOption[]NoDefault options applied to all decide calls.
clientEnginestringNoCustom client engine identifier.
clientVersionstringNoCustom client version identifier.
odpManagerOpaqueOdpManagerNoODP manager for audience segments and events. Create with createOdpManager.
vuidManagerOpaqueVuidManagerNoVisitor UID manager. Create with createVuidManager.
disposablebooleanNoWhen true, marks the client as single-use for SSR.
cmabobjectNoCMAB cache configuration: cacheSize (number), cacheTtl (number, ms), cache (custom cache instance).

For detailed configuration options for each factory (polling intervals, custom event dispatchers, ODP, VUID etc.), see the JavaScript SDK initialization guide.

returns

  • A Client instance.
import{createInstance,createPollingProjectConfigManager,}from'@optimizely/react-sdk';constoptimizely=createInstance({projectConfigManager: createPollingProjectConfigManager({sdkKey: 'your-optimizely-sdk-key',datafile: window.optimizelyDatafile,// optional: use as initial datafile while pollingautoUpdate: true,}),});

Config manager factories

FactoryUse case
createPollingProjectConfigManager({ sdkKey, datafile?, autoUpdate? })Fetches and polls for datafile updates. sdkKey is required.
createStaticProjectConfigManager({ datafile })Uses a fixed datafile with no polling.

Event processor factories

FactoryUse case
createBatchEventProcessor({ batchSize?, flushInterval? })Batches events before dispatching.
createForwardingEventProcessor()Forwards each event immediately.

Note: Event processing is opt-in. If no eventProcessor is passed to createInstance, no events are dispatched.

Other configurable modules

FactoryUse case
createOdpManager()Enables ODP integration (audience segments, events).
createVuidManager()Enables visitor UID tracking.
createErrorNotifier()Configures error notification.
createLogger({ logLevel })Creates a logger instance (see Logging).

Full client creation example

import{createInstance,createPollingProjectConfigManager,createBatchEventProcessor,createOdpManager,createLogger,DEBUG,}from'@optimizely/react-sdk';constoptimizely=createInstance({projectConfigManager: createPollingProjectConfigManager({sdkKey: 'your-sdk-key',}),eventProcessor: createBatchEventProcessor({batchSize: 10,flushInterval: 2000,}),odpManager: createOdpManager(),logger: createLogger({logLevel: DEBUG}),});

<OptimizelyProvider>

Required at the root level. Leverages React's Context API to provide the Optimizely client and user context to hooks.

props

PropTypeRequiredDescription
clientClientYesInstance created from createInstance.
user{ id?: string; attributes?: UserAttributes } | nullNoUser info object — id and attributes will be used to create the user context for all decisions and event tracking. Pass null, undefined, or omit while user info is being fetched — hooks will return { isLoading: true } until a resolved user is provided. For VUID-only mode (no user ID), pass user={{}}.
timeoutnumberNoMaximum time (in milliseconds) to wait for the SDK to become ready before hooks resolve with a loading state. Default: 30000.
qualifiedSegmentsstring[]NoPre-fetched ODP audience segments for the user. Use getQualifiedSegments to obtain these segments server-side.
skipSegmentsbooleanNoWhen true, skips background ODP segment fetching. Default: false.

Note: If user information is not yet available, you can render <OptimizelyProvider> without it — hooks will return { isLoading: true } until a resolved user is provided. For VUID-only mode (no user ID), pass user={{}}.

VUID-only example

import{createInstance,createPollingProjectConfigManager,createBatchEventProcessor,createOdpManager,createVuidManager,OptimizelyProvider,}from'@optimizely/react-sdk';constoptimizely=createInstance({projectConfigManager: createPollingProjectConfigManager({sdkKey: 'your-optimizely-sdk-key',}),eventProcessor: createBatchEventProcessor(),odpManager: createOdpManager(),vuidManager: createVuidManager({enableVuid: true,}),});functionApp(){return(<OptimizelyProviderclient={optimizely}user={{}}><MyComponent/></OptimizelyProvider>);}

Readiness

Before rendering real content, both the datafile and the user must be available to the SDK.

With datafile

Use createStaticProjectConfigManager to initialize with a pre-fetched datafile. The SDK is ready immediately with no network requests.

The datafile parameter accepts either a valid JSON string or a parsed datafile object.

import{OptimizelyProvider,createInstance,createStaticProjectConfigManager,createForwardingEventProcessor,}from'@optimizely/react-sdk';// Using a JSON string (e.g., fetched server-side and injected into the page)constoptimizelyClient=createInstance({projectConfigManager: createStaticProjectConfigManager({datafile: '{"version": "4", "flags": ...}',}),eventProcessor: createForwardingEventProcessor(),});// Using a parsed objectconstoptimizelyClient=createInstance({projectConfigManager: createStaticProjectConfigManager({datafile: {"version": "4","flags": ...},}),eventProcessor: createForwardingEventProcessor(),});functionAppWrapper(){return(<OptimizelyProviderclient={optimizelyClient}user={{id: window.userId}}><App/></OptimizelyProvider>);}

With SDK key

Use createPollingProjectConfigManager with an sdkKey to have the SDK fetch and poll for the datafile. Provide a timeout option to <OptimizelyProvider> so hooks can distinguish between the loading state and actual decisions. Hooks return isLoading: true while waiting and resolve once the SDK is ready or the timeout expires.

import{OptimizelyProvider,createInstance,createPollingProjectConfigManager,createForwardingEventProcessor,useDecide,}from'@optimizely/react-sdk';constoptimizelyClient=createInstance({projectConfigManager: createPollingProjectConfigManager({sdkKey: 'your-optimizely-sdk-key',}),eventProcessor: createForwardingEventProcessor(),});functionMyComponent(){const{ decision, isLoading, error }=useDecide('the-flag');if(isLoading)returnnull;if(error)return<div>Error: {error.message}</div>;return<div>{decision.enabled ? 'Feature On' : 'Feature Off'}</div>;}functionApp(){return(<OptimizelyProviderclient={optimizelyClient}timeout={500}user={{id: window.userId,attributes: {plan_type: 'bronze'}}}><MyComponent/></OptimizelyProvider>);}

With both SDK key and datafile

You can provide both an sdkKey and a datafile to createPollingProjectConfigManager. The SDK uses the given datafile immediately, then fetches and polls for updates in the background.

import{OptimizelyProvider,createInstance,createPollingProjectConfigManager,createForwardingEventProcessor,}from'@optimizely/react-sdk';constoptimizelyClient=createInstance({projectConfigManager: createPollingProjectConfigManager({sdkKey: 'your-optimizely-sdk-key',datafile: window.optimizelyDatafile,}),eventProcessor: createForwardingEventProcessor(),});functionAppWrapper(){return(<OptimizelyProviderclient={optimizelyClient}user={{id: window.userId}}><App/></OptimizelyProvider>);}

Hooks

useDecide

The primary hook for retrieving a feature flag decision. Automatically re-evaluates when the underlying datafile changes.

arguments

ParameterTypeRequiredDescription
flagKeystringYesThe key of the feature flag.
configobjectNoConfiguration object.
config.decideOptionsOptimizelyDecideOption[]NoArray of decide options.

returns{ decision, isLoading, error }

FieldTypeDescription
decisionOptimizelyDecision | nullThe decision result. null while loading or on error.
isLoadingbooleantrue while waiting for the SDK to become ready.
errorError | nullError object if the decision failed, otherwise null.
import{useDecide}from'@optimizely/react-sdk';functionLoginComponent(){const{ decision, isLoading, error }=useDecide('login-flag');if(isLoading)return<Loading/>;if(error)return<Errorerror={error}/>;return(<p><ahref={decision.enabled ? '/login-new' : '/login-default'}>Click to login</a></p>);}

useDecideForKeys

Retrieve decisions for multiple flag keys in a single call.

arguments

ParameterTypeRequiredDescription
flagKeysstring[]YesArray of flag keys.
configobjectNoConfiguration object.
config.decideOptionsOptimizelyDecideOption[]NoArray of decide options.

returns{ decisions, isLoading, error }

FieldTypeDescription
decisions{ [key: string]: OptimizelyDecision } | nullMap of flag keys to their decisions. null while loading or on error.
isLoadingbooleantrue while waiting for the SDK to become ready.
errorError | nullError object if the decisions failed, otherwise null.
import{useDecideForKeys}from'@optimizely/react-sdk';functionDashboard(){const{ decisions, isLoading }=useDecideForKeys(['flag-a','flag-b']);if(isLoading)return<Loading/>;return(<>{decisions['flag-a']?.enabled&&<FeatureA/>}{decisions['flag-b']?.enabled&&<FeatureB/>}</>);}

useDecideAll

Retrieve decisions for all active (unarchived) flags.

arguments

ParameterTypeRequiredDescription
configobjectNoConfiguration object.
config.decideOptionsOptimizelyDecideOption[]NoArray of decide options.

returns{ decisions, isLoading, error }

FieldTypeDescription
decisions{ [key: string]: OptimizelyDecision } | nullMap of flag keys to their decisions. null while loading or on error.
isLoadingbooleantrue while waiting for the SDK to become ready.
errorError | nullError object if the decisions failed, otherwise null.

Async decision hooks

Async variants call the underlying async SDK methods (decideAsync, decideForKeysAsync, decideAllAsync). Use these when your setup involves asynchronous operations such as CMAB (Contextual Multi-Armed Bandit) decisions or async User Profile Service lookups.

HookDescription
useDecideAsync(flagKey, config?)Async variant of useDecide
useDecideForKeysAsync(flagKeys[], config?)Async variant of useDecideForKeys
useDecideAllAsync(config?)Async variant of useDecideAll

These hooks have the same return types as their synchronous counterparts.

import{useDecideAsync}from'@optimizely/react-sdk';functionMyComponent(){const{ decision, isLoading, error }=useDecideAsync('flag-key');if(isLoading)return<Loading/>;if(error)return<Errorerror={error}/>;returndecision.enabled ? <NewFeature/> : <Default/>;}

useOptimizelyClient

Returns the Optimizely Client instance from context for direct SDK access.

import{useOptimizelyClient}from'@optimizely/react-sdk';functionMyComponent(){constclient=useOptimizelyClient();// Use client methods directly}

useOptimizelyUserContext

Returns the current user context, which can be used for event tracking and forced decisions.

returns

  • { userContext, isLoading, error } — Where userContext is an Optimizely UserContext object.
import{useOptimizelyUserContext}from'@optimizely/react-sdk';functionMyComponent(){const{ userContext }=useOptimizelyUserContext();consthandleClick=()=>{userContext?.trackEvent('button-clicked',{revenue: 100});};return<buttononClick={handleClick}>Click me</button>;}

Event Tracking

Use the useOptimizelyUserContext hook to track events:

import{useOptimizelyUserContext}from'@optimizely/react-sdk';functionSignupButton(){const{ userContext }=useOptimizelyUserContext();consthandleClick=()=>{userContext?.trackEvent('signup-clicked');};return<buttononClick={handleClick}>Signup</button>;}

Forced Decisions

Forced decisions are set on the userContext object:

import{useOptimizelyUserContext}from'@optimizely/react-sdk';functionMyComponent(){const{ userContext }=useOptimizelyUserContext();// Set a forced decisionuserContext?.setForcedDecision({flagKey: 'flag-1',ruleKey: 'rule-1'},{variationKey: 'variation-a'});// Remove a forced decisionuserContext?.removeForcedDecision({flagKey: 'flag-1',ruleKey: 'rule-1'});// Remove all forced decisionsuserContext?.removeAllForcedDecisions();}

Hooks that use the affected flag key automatically re-render when forced decisions change.

Logging

Logging is disabled by default. Pass a logger to createInstance to enable it:

import{createInstance,createPollingProjectConfigManager,createLogger,DEBUG,}from'@optimizely/react-sdk';constoptimizely=createInstance({projectConfigManager: createPollingProjectConfigManager({sdkKey: 'your-sdk-key'}),logger: createLogger({logLevel: DEBUG}),});

Log level constants DEBUG, INFO, WARN, and ERROR are exported for convenience.

Server-Side Rendering

The React SDK supports server-side rendering (SSR). A pre-fetched datafile is required for SSR — without one, the SDK cannot make decisions during server rendering.

Per-request client

Create a client inside the component with a pre-fetched datafile. Use useState with a lazy initializer to create the instance once. Pass the datafile for immediate readiness during server rendering, and set disposable: true on the server so the instance can be garbage collected without explicitly calling close().

'use client';import{useState}from'react';import{createInstance,createPollingProjectConfigManager,createBatchEventProcessor,OptimizelyProvider,OptimizelyDecideOption,useDecide,}from'@optimizely/react-sdk';exportdefaultfunctionPage({ datafile, userId }){constisServerSide=typeofwindow==='undefined';const[optimizely]=useState(()=>createInstance({projectConfigManager: createPollingProjectConfigManager({sdkKey: process.env.NEXT_PUBLIC_OPTIMIZELY_SDK_KEY||'',
datafile,}),eventProcessor: createBatchEventProcessor(),defaultDecideOptions: isServerSide ? [OptimizelyDecideOption.DISABLE_DECISION_EVENT] : [],disposable: isServerSide,}));return(<OptimizelyProviderclient={optimizely}user={{id: userId}}><MyComponent/></OptimizelyProvider>);}functionMyComponent(){const{ decision, isLoading }=useDecide('flag1');if(isLoading)returnnull;returndecision.enabled ? <p>Feature enabled</p> : <p>Feature disabled</p>;}

Module-level client

For a long-lived server process, create a singleton client at the module level with createPollingProjectConfigManager. The client fetches and polls for datafile updates in the background.

Provide a datafile for immediate readiness. Without one, the server may render decisions once the initial fetch completes, but the client will not have the datafile yet (fetch still in-flight) and will render a loading or default state — causing a hydration mismatch.

If an eventProcessor is configured on the server, use DISABLE_DECISION_EVENT to avoid firing duplicate decision events (the client will fire them after hydration).

import{createInstance,createPollingProjectConfigManager,createBatchEventProcessor,OptimizelyProvider,OptimizelyDecideOption,useDecide,}from'@optimizely/react-sdk';constoptimizely=createInstance({projectConfigManager: createPollingProjectConfigManager({sdkKey: process.env.NEXT_PUBLIC_OPTIMIZELY_SDK_KEY,datafile: preloadedDatafile,}),eventProcessor: createBatchEventProcessor(),defaultDecideOptions: [OptimizelyDecideOption.DISABLE_DECISION_EVENT],});functionMyComponent(){const{ decision, isLoading }=useDecide('flag1');if(isLoading)returnnull;returndecision.enabled ? <p>Feature enabled</p> : <p>Feature disabled</p>;}functionApp(){return(<OptimizelyProviderclient={optimizely}user={{id: 'user1'}}><MyComponent/></OptimizelyProvider>);}

getQualifiedSegments

A standalone async utility that fetches qualified ODP audience segments for a user, given a datafile. It parses the datafile to extract ODP configuration and segment conditions, queries the ODP GraphQL API, and returns only the segments where the user is qualified.

import{getQualifiedSegments}from'@optimizely/react-sdk';const{ segments, error }=awaitgetQualifiedSegments(userId,datafile);
ArgumentTypeDescription
userIdstringThe user ID to fetch qualified segments for
datafilestring | Record<string, any>The Optimizely datafile (JSON object or string)

Returns:Promise<QualifiedSegmentsResult>{ segments: string[], error: Error | null }

Caching recommendation: The ODP segment fetch adds latency to server rendering. Consider caching the result per user to avoid re-fetching on every request.

Pass the pre-fetched segments to the Provider:

<OptimizelyProviderclient={optimizely}user={{id: 'user-123'}}qualifiedSegments={segments}skipSegments={isServerSide}><App/></OptimizelyProvider>

React Server Components

The SDK provides a server-safe entry point via the react-server export condition. Frameworks that support this condition (e.g., Next.js App Router) automatically resolve @optimizely/react-sdk to the server entry point when importing from a Server Component. This entry point excludes hooks and Provider (which use client-only React APIs).

import{createInstance,createStaticProjectConfigManager}from'@optimizely/react-sdk';exportdefaultasyncfunctionServerComponent(){constclient=createInstance({projectConfigManager: createStaticProjectConfigManager({ datafile }),});awaitclient.onReady();constuserContext=client.createUserContext('user-123');constdecision=userContext.decide('flag-key');client.close();returndecision.enabled ? <NewFeature/> : <Default/>;}

Next.js Integration

For detailed Next.js examples covering both App Router and Pages Router patterns, see the Next.js Integration Guide.

Limitations

  • Datafile required — SSR requires a pre-fetched datafile. Using sdkKey alone falls back to a failed decision.
  • ODP segments — ODP audience segments require async I/O and are not available during server rendering. Use getQualifiedSegments to pre-fetch segments server-side and pass them via the qualifiedSegments prop on OptimizelyProvider.

For more details and workarounds, see the Next.js Integration Guide — Limitations.

Migrating from v3

For a comprehensive migration guide covering all breaking changes, see MIGRATION.md.

Rollout or Experiment a Feature User-by-User

To rollout or experiment on a feature by user rather than by random percentage, you will use Attributes and Audiences. To do this, follow the documentation on how to run a beta using the React code samples.

Additional Code

This repository includes the following third party open source code:

decompress-response Copyright © Sindre Sorhus License: MIT

js-tokens Copyright © Simon Lydell License: MIT

json-schema Copyright © Kris Zyp License: AFL-2.1 OR BSD-3-Clause

loose-envify Copyright © Andres Suarez License: MIT

mimic-response Copyright © Sindre Sorhus License: MIT

murmurhash License: MIT

react Copyright © Meta Platforms, Inc. and affiliates. License: MIT

tslib Copyright © Microsoft Corp. License: 0BSD

use-sync-external-store Copyright © Meta Platforms, Inc. and affiliates. License: MIT

uuid License: MIT

To regenerate the dependencies used by this package, run the following command:

npx license-checker --production --json | jq 'map_values({ licenses, publisher, repository }) | del(.[][] | nulls)'

Contributing

Please see CONTRIBUTING for more information.

Credits

First-party code subject to copyrights held by Optimizely, Inc. and its contributors and licensed to you under the terms of the Apache 2.0 license.

Other Optimizely SDKs

Releases

Packages

Used by

Contributors

Languages