For AI agents: a documentation index is available at /docs/llms.txt. Append .md to any page URL for markdown, or send Accept: text/markdown.
Experiment JavaScript SDK
Official documentation for Amplitude Experiment's Client-side JavaScript SDK implementation.
Install
Choose the right package for your integration surface before installing:
| Integration | Package | Notes |
|---|---|---|
| New browser integration: Analytics, Session Replay, and Feature Experiment | @amplitude/unified (npm) | Single entry point for all Amplitude browser products. Includes Feature Experiment. |
| Existing Browser SDK 2 integration: add Feature Experiment | @amplitude/experiment-js-client (npm) | Keep @amplitude/analytics-browser and initialize with Experiment.initializeWithAmplitudeAnalytics(). |
| Browser: Feature Experiment only | @amplitude/experiment-js-client (npm) | Use when you don't send Analytics or Session Replay data to Amplitude. Set an exposure tracking provider. |
| Server | @amplitude/experiment-node-server (npm) | Refer to the Experiment Node.js SDK. |
| Script tag (CDN) | Add @amplitude/experiment-js-client separately | The Unified Script includes Web Experiment but not Feature Experiment. Add this package alongside the Unified Script for Feature Experiment. |
Feature Experiment vs Web Experiment
- Feature Experiment (
@amplitude/experiment-js-client): Code-based feature flags and A/B tests evaluated at runtime. The@amplitude/unifiednpm package includes it. The Unified Script (CDN) doesn't include it. - Web Experiment (
@amplitude/experiment-tag): No-code visual editor for A/B testing web pages. The Unified Script (CDN) includes it. The@amplitude/unifiednpm package doesn't include it.
For the full breakdown, refer to Product support by installation method.
Unified SDK for multi-product browser integrations
If you're starting a new browser integration and need Analytics and Session Replay alongside Feature Experiment, install the Browser Unified SDK (@amplitude/unified) instead of installing those packages separately. Don't load @amplitude/unified and @amplitude/analytics-browser on the same page; choose one.
If you already run @amplitude/analytics-browser, adding @amplitude/experiment-js-client alongside it is a supported setup. You don't need to migrate to the Unified SDK.
# New browser integration: Analytics + Session Replay + Feature Experiment
npm install @amplitude/unified
# Feature Experiment on its own, or alongside an existing @amplitude/analytics-browser integration
npm install --save @amplitude/experiment-js-client
Quick start
The right way to initialize the Experiment SDK depends on whether you use an Amplitude SDK for analytics or a third party (for example, Segment).
import { Experiment } from '@amplitude/experiment-js-client';
// (1) Initialize the experiment client with Amplitude Analytics.
const experiment = Experiment.initializeWithAmplitudeAnalytics(
'DEPLOYMENT_KEY'
);
// (2) Fetch variants and await the promise result.
await experiment.fetch();
// (3) Lookup a flag's variant.
const variant = experiment.variant('FLAG_KEY');
if (variant.value === 'on') {
// Flag is on
} else {
// Flag is off
}
Initialize
Initialize the SDK in your application on startup. The deployment key argument you pass into the apiKey parameter must live in the same Amplitude project to which you send events.
initializeWithAmplitudeAnalytics(apiKey: string, config?: ExperimentConfig): ExperimentClient
| Parameter | Description |
|---|---|
apikey | Required. The deployment key which authorizes fetch requests and determines which flags to evaluate for the user |
config | The client configuration to customize SDK client behavior. |
The initializer returns a singleton instance, so subsequent initializations for the same instance name always return the initial instance. To create multiple instances, use the instanceName configuration.
import { Experiment } from '@amplitude/experiment-js-client';
const experiment = initializeWithAmplitudeAnalytics('DEPLOYMENT_KEY');
Angular apps
In an Angular app that uses Zone.js, initialize the client and call start() or fetch() outside the Angular zone, for example in main.ts before bootstrapApplication, or inside NgZone.runOutsideAngular. After start(), the client polls for flag configuration updates on a recurring timer when pollOnStart is true, which is the default. With the Amplitude Analytics integration, the client also runs a recurring timer to flush exposure events. A recurring timer that starts inside the Angular zone keeps the app from becoming stable, which delays anything that waits for stability, such as service worker registration with Angular's default registerWhenStable strategy.
variant() reads from the client's local store and makes no network request, so you can call it from components. If a component updates its state when a fetch() promise that started outside the zone resolves, make the update inside NgZone.run so Angular runs change detection.
Configuration
Configure the SDK client during initialization.
Integrations
If you use either Amplitude or Segment Analytics SDKs to track events into Amplitude, set up an integration on initialization. Integrations automatically implement provider interfaces to enable a more streamlined developer experience by making it easier to manage user identity and track exposures events.
Manage user consent
The Experiment SDK doesn't expose a consent API. There's no consentOptions configuration, ConsentStatus type, or setConsentStatus() method.
To support GDPR-style consent flows, gate Amplitude initialization and variant fetches on the signal from your consent management platform (CMP). Don't initialize the analytics SDK or call fetch() or start() until the user grants consent.
import * as amplitude from '@amplitude/analytics-browser';
import { Experiment } from '@amplitude/experiment-js-client';
// Call this only after your CMP reports that the user granted consent.
async function onConsentGranted() {
amplitude.init('API_KEY');
const experiment = Experiment.initializeWithAmplitudeAnalytics('DEPLOYMENT_KEY');
await experiment.fetch();
}
If a user withdraws consent, stop calling fetch() and clear any stored variants with clear().
Fetch
Fetches variants for a user and stores the results in the client for fast access. The function remote evaluates the user for flags associated with the deployment used to initialize the SDK client.
Fetch on user identity change
If you want the most up-to-date variants for the user, it's recommended that you call fetch() whenever the user state changes in a meaningful way. For example, if the user logs in and receives a user ID, or has a user property set which may affect flag or experiment targeting rules.
Pass new user properties explicitly to fetch() instead of relying on user enrichment before remote evaluation. Remote user-property sync through a separate system has no timing guarantees for fetch(), which can create a race condition.
fetch(user?: ExperimentUser, options?: FetchOptions): Promise<Client>
| Parameter | Requirement | Description |
|---|---|---|
user | optional | Explicit user information to pass with the request to evaluate. The SDK merges this user information with user information provided from integrations through the user provider, preferring properties passed explicitly to fetch() over provided properties. |
options | optional | Explicit flag keys to fetch. |
const user = {
user_id: 'user@company.com',
device_id: 'abcdefg',
user_properties: {
'premium': true,
},
};
await experiment.fetch(user);
If you're using an integration or a custom user provider then you can fetch without inputting the user.
await experiment.fetch();
If fetch() times out (default 10 seconds) or fails for any reason, the SDK client returns and retries in the background with back-off. You may configure the timeout or disable retries in the configuration options during SDK client initialization.
Start
Fetch vs start
Use start if you're using client-side local evaluation. If you're only using remote evaluation, call fetch instead of start.
Start the SDK by getting flag configurations from the server and fetching remote evaluation variants for the user. The SDK is ready when the returned promise resolves.
start(user?: ExperimentUser): Promise<void>
| Parameter | Requirement | Description |
|---|---|---|
user | optional | Explicit user information to pass with the request to fetch variants. The SDK merges this user information with user information provided from integrations through the user provider, preferring properties passed explicitly to fetch() over provided properties. Also sets the user in the SDK for reuse. |
Call start() when your application is initializing, after user information is available to evaluate or fetch variants. The promise resolves after loading local evaluation flag configurations and fetching remote evaluation variants.
Configure the behavior of start() by setting fetchOnStart in the SDK configuration on initialization to improve performance based on the needs of your application.
- If your application never relies on remote evaluation, set
fetchOnStarttofalseto avoid increased startup latency caused by remote evaluation. - If your application relies on remote evaluation, but not right at startup, you may set
fetchOnStarttofalseand callfetch()and await the promise separately.
await experiment.start();
Variant
Access a variant for a flag or experiment from the SDK client's local store.
Automatic exposure tracking
When you use an integration or set a custom exposure tracking provider, variant() automatically tracks an exposure event through the tracking provider. To disable this functionality, configure automaticExposureTracking to be false, and track exposures manually using exposure().
variant(key: string, fallback?: string | Variant): Variant
| Parameter | Requirement | Description |
|---|---|---|
key | required | The flag key to identify the flag or experiment to access the variant for. |
fallback | optional | The value to return if no variant was found for the given flagKey. |
When determining which variant a user has been bucketed into, you'll want to compare the variant value to a well-known string.
const variant = experiment.variant('<FLAG_KEY>');
if (variant.value === 'on') {
// Flag is on
} else {
// Flag is off
}
Access a variant's payload
A variant may also be configured with a dynamic payload of arbitrary data. Access the payload field from the variant object after checking the variant's value.
const variant = experiment.variant('<FLAG_KEY>');
if (variant.value === 'on') {
const payload = variant.payload;
}
A null variant value means that the user hasn't been bucketed into a variant. You may use the built in fallback parameter to provide a variant to return if the store doesn't contain a variant for the given flag key.
const variant = experiment.variant('<FLAG_KEY>', { value: 'control' });
if (variant.value === 'control') {
// Control
} else if (variant.value === 'treatment') {
// Treatment
}
All
Access all variants stored by the SDK client.
all(): Variants
Clear
Clear all variants in the cache and storage.
clear(): void
You can call clear after user logout to clear the variants in cache and storage.
experiment.clear();
Exposure
Manually track an exposure event for the current variant of the given flag key through configured integration or custom exposure tracking provider. Generally used in conjunction with setting the automaticExposureTracking configuration optional to false.
exposure(key: string): void
| Parameter | Requirement | Description |
|---|---|---|
key | required | The flag key to identify the flag or experiment variant to track an exposure event for. |
const variant = experiment.variant('<FLAG_KEY>');
// Do other things...
experiment.exposure('<FLAG_KEY>');
if (variant.value === 'control') {
// Control
} else if (variant.value === 'treatment') {
// Treatment
}
Providers
Integrations
If you use Amplitude or Segment analytics SDKs along side the Experiment Client SDK, Amplitude recommends you use an integration instead of implementing custom providers.
Provider implementations enable a more streamlined developer experience by making it easier to manage user identity and track exposures events.
User provider
The SDK client uses the user provider to access the most up-to-date user information only when needed (for example, when fetch() is called). The user provider is optional, but helps if you have a user information store already set up in your application. With a user provider, you don't need to manage two separate user info stores in parallel. Separate stores can create divergent user state if the application user store is updated and experiment isn't (or vice versa).
interface ExperimentUserProvider {
getUser(): ExperimentUser;
}
To use your custom user provider, set the userProvider configuration option with an instance of your custom implementation on SDK initialization.
const experiment = Experiment.initialize('<DEPLOYMENT_KEY>', {
userProvider: new CustomUserProvider(),
});
Exposure tracking provider
Amplitude highly recommends implementing an exposure tracking provider. Exposure tracking increases the accuracy and reliability of experiment results and improves visibility into which flags and experiments a user is exposed to.
export interface ExposureTrackingProvider {
track(exposure: Exposure, groups?: Record<string, string[]>): void;
}
The implementation of track() should track an event of type $exposure with two event properties, flag_key and variant, from the Exposure argument. The optional second argument contains the evaluation user's groups. Forward them as event-level groups, not event properties. The SDK calls track() without this argument when the user's groups are missing or empty, the exposure has no variant, or the user has more than 10 group values. The event must reach Amplitude Analytics in the same project as the [deployment] used to initialize the SDK client, and for the same user that variants were fetched for.
import * as amplitude from '@amplitude/analytics-browser';
import type { ExposureTrackingProvider } from '@amplitude/experiment-js-client';
const exposureTrackingProvider: ExposureTrackingProvider = {
track: (exposure, groups) => {
amplitude.track('$exposure', exposure, { groups });
},
};
To use your custom user provider, set the exposureTrackingProvider configuration option with an instance of your custom implementation on SDK initialization.
const experiment = Experiment.initialize('<DEPLOYMENT_KEY>', {
exposureTrackingProvider: new CustomExposureTrackingProvider(),
});
Bootstrapping
You may want to bootstrap the experiment client with an initial set of flags or variants when variants come from an external source (for example, not from calling fetch() on the SDK client). Use cases include local evaluation, server-side rendering, or integration testing on specific variants.
Bootstrapping variants
To bootstrap the client with a predefined set of variants, set the flags and variants in the initialVariants configuration object, then set the source to Source.InitialVariants so that the SDK client prefers the bootstrapped variants over any previously fetched & stored variants for the same flags.
const experiment = Experiment.initialize('<DEPLOYMENT_KEY>', {
// Map flag keys to variant objects. The variant object may either be
// pre-evaluation (SSR) or input manually in for testing.
initialVariants: {
"<FLAG_KEY>": {
"value": "<VARIANT>"
}
},
source: Source.InitialVariants,
});
Bootstrapping flag configurations
You may choose to bootstrap the SDK with an initial set of local evaluation flag configurations using the initialFlags configuration. The SDK evaluates these flag configurations when variant is called, unless an updated flag config or variant is loaded with start or fetch.
To download initial flags, use the evaluation flags API
const experiment = Experiment.initialize('<DEPLOYMENT_KEY>', {
initialFlags: "<FLAGS_JSON>",
});
HTTP client
You can provide a custom HTTP client implementation to handle network requests made by the SDK. This is useful for environments with specific networking requirements or when you need to customize request handling.
export interface SimpleResponse {
status: number;
body: string;
}
export interface HttpClient {
request(
requestUrl: string,
method: string,
headers: Record<string, string>,
data: string,
timeoutMillis?: number,
): Promise<SimpleResponse>;
}
To use your custom HTTP client, set the httpClient configuration option with an instance of your implementation on SDK initialization.
const experiment = Experiment.initialize('<DEPLOYMENT_KEY>', {
httpClient: new CustomHttpClient(),
});
Custom logging
Control log verbosity with the logLevel configuration, or implement the Logger interface to integrate your own logging solution.
Log levels
LogLevel.Disable- No loggingLogLevel.Error- Errors only (default)LogLevel.Warn- Errors and warningsLogLevel.Info- Errors, warnings, and informational messagesLogLevel.Debug- Errors, warnings, info, and debug messagesLogLevel.Verbose- All messages including verbose details
import { Experiment, LogLevel } from '@amplitude/experiment-js-client';
// Only log errors
const experiment = Experiment.initialize('<DEPLOYMENT_KEY>', {
logLevel: LogLevel.Error
});
// Log errors and warnings
const experiment = Experiment.initialize('<DEPLOYMENT_KEY>', {
logLevel: LogLevel.Warn
});
// Log everything (verbose)
const experiment = Experiment.initialize('<DEPLOYMENT_KEY>', {
logLevel: LogLevel.Verbose
});
Custom logger
Implement the Logger interface to use your own logging solution:
import { Experiment, Logger, LogLevel } from '@amplitude/experiment-js-client';
// Implement the Logger interface
class CustomLogger implements Logger {
error(message?: any, ...optionalParams: any[]): void {
// Send errors to your logging service
myLoggingService.error(message, ...optionalParams);
}
warn(message?: any, ...optionalParams: any[]): void {
myLoggingService.warn(message, ...optionalParams);
}
info(message?: any, ...optionalParams: any[]): void {
myLoggingService.info(message, ...optionalParams);
}
debug(message?: any, ...optionalParams: any[]): void {
myLoggingService.debug(message, ...optionalParams);
}
verbose(message?: any, ...optionalParams: any[]): void {
myLoggingService.verbose(message, ...optionalParams);
}
}
// Initialize with custom logger
const experiment = Experiment.initialize('<DEPLOYMENT_KEY>', {
loggerProvider: new CustomLogger(),
logLevel: LogLevel.Warn
});
Debug flag (deprecated)
The debug configuration flag is deprecated. Use logLevel instead.
// Deprecated: Sets logLevel to Debug
const experiment = Experiment.initialize('<DEPLOYMENT_KEY>', {
debug: true
});
// Preferred: Use logLevel instead
const experiment = Experiment.initialize('<DEPLOYMENT_KEY>', {
logLevel: LogLevel.Debug
});
Was this helpful?