Overview
This page is a full reference for Optimizely's SDKs.
Read below to learn how to install one of our SDKs and run experiments in your code. You can use the toggles on the upper-right to see implementations in other languages.
Quick links
If you're just getting started, we recommend the links below:
- Example usage: overview of the core components of the SDK.
- Getting started guide: install the SDK and run an experiment in a few minutes.
- FAQs: list of the most commonly asked questions.
Example usage
The code at right illustrates the basic usage of the SDK to run an experiment in your code.
First, you need to initialize an Optimizely client based on your Optimizely project's datafile.
To run an experiment, you'll want to activate the experiment at the point you want to split traffic in your code. The activate function returns which variation the current user is assigned to. The activate function also sends an event to Optimizely to record that the current user has been exposed to the experiment. In this example, we've created an experiment my_experiment with two variations control and treatment.
You'll also want to track events for your key conversion metrics. In this example, there is one conversion metric, my_conversion. The track function can be used to track events across multiple experiments, and will be counted for each experiment only if activate has previously been called for the current user.
Note that the SDK requires you to provide your own unique user IDs for all of your activate and track calls. See User IDs for more details and best practices on what user IDs to provide.
You are responsible for creating and distributing OptimizelyManager around your application's components. The manager handles
setting up caches, datafile syncing, and more off of the main thread. See Initialization for more details.
Example Code
from optimizely import optimizely # Initialize an Optimizely client optimizely_client = optimizely.Optimizely(datafile) # Activate user in an experiment variation = optimizely_client.activate('my_experiment', user_id) if variation == 'control': # Execute code for variation A elif variation == 'treatment': # Execute code for variation B else: # Execute default code # Track conversion event optimizely_client.track('my_conversion', user_id)
Example Code
import com.optimizely.ab.Optimizely; // Initialize an Optimizely client optimizelyClient = Optimizely.builder(datafile, myEventHandler).build(); // Activate user in an experiment Variation variation = optimizelyClient.activate("my_experiment", userId); if (variation != null) { if (variation.is("control")) { // Execute code for variation A } else if (variation.is("treatment")) { // Execute code for variation B } } else { // Execute default code } // Track conversion event optimizelyClient.track("my_conversion", userId);
Example Code
using OptimizelySDK; // Initialize an Optimizely client Optimizely OptimizelyClient = new Optimizely(datafile); // Activate user in an experiment var variation = OptimizelyClient.Activate("my_experiment", userId); if (variation != null) { if (variation == "control") { // Execute code for variation A } else if (variation == "treatment") { // Execute code for variation B } } else { // Execute default code } // Track conversion event OptimizelyClient.Track("my_conversion", userId);
Example Code
import com.optimizely.ab.Optimizely; // Get an Optimizely client OptimizelyClient optimizelyClient = optimizelyManager.getOptimizely(); // Activate user in an experiment Variation variation = optimizelyClient.activate("my_experiment", "user_id"); if (variation != null) { if (variation.is("control")) { // Execute code for variation A } else if (variation.is("treatment")) { // Execute code for variation B } } else { // Execute default code } // Track conversion event optimizelyClient.track("my_conversion", user_id);
Example Code
require "optimizely" # Initialize an Optimizely client optimizely_client = Optimizely::Project.new(datafile) # Activate user in an experiment variation = optimizely_client.activate("my_experiment", user_id) if variation == 'control' # Execute code for variation A elsif variation == 'treatment' # Execute code for variation B else # Execute default code end # Track conversion event optimizely_client.track("my_conversion", user_id)
Example Code
var optimizely = require('optimizely-server-sdk'); // Initialize an Optimizely client var optimizelyClient = optimizely.createInstance({ datafile: datafile }); // Activate user in an experiment var variation = optimizelyClient.activate("my_experiment", userId); if (variation === 'control') { // Execute code for variation A } else if (variation === 'treatment') { // Execute code for variation B } else { // Execute default code } // Track conversion event optimizelyClient.track("my_conversion", userId);
Example Code
var optimizely = require('optimizely-client-sdk'); // Initialize an Optimizely client var optimizelyClientInstance = optimizely.createInstance({ datafile: datafile }); // Alternatively, if you don't use CommonJS or npm, you can install the minified snippet and use the globally exported varible as follows var optimizelyClientInstance = window.optimizelyClient.createInstance({ datafile: datafile }); // Activate user in an experiment var variation = optimizelyClientInstance.activate("my_experiment", userId); if (variation === 'control') { // Execute code for variation A } else if (variation === 'treatment') { // Execute code for variation B } else { // Execute default code } // Track conversion event optimizelyClientInstance.track("my_conversion", userId);
Example Code
use Optimizely\Optimizely; // Initialize an Optimizely client $optimizelyClient = new Optimizely($datafile); // Activate user in an experiment $variation = $optimizelyClient->activate('my_experiment', $userId); if ($variation == 'control') { // Execute code for variation A } elseif ($variation == 'treatment') { // Execute code for variation B } else { // Execute default code } // Track conversion event $optimizelyClient->track('my_conversion', $userId);
Example Code
// Initialize an Optimizely manager OPTLYManager *optlyManager = [OPTLYManager init:^(OPTLYManagerBuilder * _Nullable builder) { builder.projectId = @"projectId"; }]; // Initialize an Optimizely client by asynchronously downloading the datafile [optlyManager initializeWithCallback:^(NSError * _Nullable error, OPTLYClient * _Nullable client) { // Activate user in an experiment OPTLYVariation *variation = [client activate:@"my_experiment" userId:@"userId"]; if ([variation.variationKey isEqualToString:@"control"]) { // Execute code for the control } else if ([variation.variationKey isEqualToString:@"treatment"]) { // Execute code for the treatment } else { // Execute default code } // Track conversion event [client track:@"my_conversion" userId:@"userId"]; }];
Example Code
// Initialize an Optimizely manager let optimizelyManager : OPTLYManager? = OPTLYManager.init {(builder) in builder!.projectId = "projectId" } // Initialize an Optimizely client by asynchronously downloading the datafile optimizelyManager?.initialize(callback: { [weak self] (error, optimizelyClient) in // Activate user in an experiment if let variation = optimizelyClient?.activate("my_experiment", userId: "userId") { if (variation.variationKey == "control") { // Execute code for the control } else if (variation.variationKey == "treatment") { // Execute code for the treatment } } else { // execute default code } // Track conversion event optimizelyClient?.track("my_conversion", userId: "userId") })
Installation
The Python SDK is distributed through PyPi. To install, simply use pip or add optimizely-sdk to your requirements.txt.
The Java SDK is distributed through Bintray. The core-api and httpclient Bintray packages are optimizely-sdk-core-api and optimizely-sdk-httpclient respectively. You can find repository information as well as instructions on how to install the dependencies on Bintray. Gradle repository and dependency configurations are shown on the right. The SDK is compatible with Java 1.6 and above.
core-api requires org.slf4j:slf4j-api:1.7.16 and a supported JSON parser. We currently integrate with Jackson, GSON, json.org, and json-simple. If multiple of these parsers are available at runtime, one will be used by core-api according to the aforementioned selection priority. If none of these packages are already provided in your project's classpath, one will need to be added.
core-httpclient-impl requires org.apache.httpcomponents:httpclient:4.5.2 and provides an asynchronous event dispatcher which is described in the Event Dispatcher section. This library isn't required and you may provide a custom EventHandler implementation which uses a different networking stack.
Code Shrinking
Building with Proguard enabled to shrink your application requires adding rules to preserve code that might otherwise be discarded. Add these rules to your Proguard file, typically named proguard-rules.pro.
The C# SDK is distributed through NuGet. To install, run the command shown in the Package Manager Console.
The gem for the Ruby SDK is distributed through RubyGems. To install, simply use gem or bundler to install the gem optimizely-sdk.
The Node SDK is distributed through npm. To install, simply run npm install optimizely-server-sdk --save.
The JavaScript SDK is distributed through npm. To install, simply run npm install optimizely-client-sdk --save.
If you aren't able to install via npm or to use the SDK in a non-CommonJS fashion, follow the instructions in the SDK's README to build a minified snippet.
Our PHP SDK is available through Composer. To install simply composer require optimizely/optimizely-sdk or add optimizely/optimizely-sdk in the require section of your composer.json.
Our iOS and tvOS SDKs are available for distribution through CocoaPods, Carthage, or manual installation.
Requirements
Please note below that <platform> is used to represent the platform on which you are building your app. Currently, we support iOS and tvOS platforms.
Cocoapods
Add the following line to the Podfile:
pod 'OptimizelySDKiOS'Alternatively, if you are developing on tvOS, use:
pod 'OptimizelySDKTVOS'Make sure
use_frameworks!is also included in the Podfile:target 'Your App' do use_frameworks! pod 'OptimizelySDKiOS' endRun the following command:
pod install
Further installation instructions through Cocoapods can be found here.
Carthage
Add the following lines to the Cartfile:
github "optimizely/objective-c-sdk" github "jsonmodel/jsonmodel" github "ccgus/fmdb"Run the following command:
carthage updateLink the frameworks to your project. Go to your project target's Link Binary With Libraries and drag over the following from the Carthage/Build/<platform> folder:
FMDB.framework JSONModel.framework OptimizelySDKCore.framework OptimizelySDKDatafileManager.framework OptimizelySDKEventDispatcher.framework OptimizelySDKShared.framework OptimizelySDKUserProfile.framework OptimizelySDK<platform>.framework
To ensure that proper bitcode-related files and dSYMs are copied when archiving your app, you will need to install a Carthage build script:
- Add a new Run Script phase in your target's Build Phase.
- In the script area include:
/usr/local/bin/carthage copy-frameworks - Add the frameworks to the Input Files list:
$(SRCROOT)/Carthage/Build/<platform>/FMDB.framework $(SRCROOT)/Carthage/Build/<platform>/JSONModel.framework $(SRCROOT)/Carthage/Build/<platform>/OptimizelySDKCore.framework $(SRCROOT)/Carthage/Build/<platform>/OptimizelySDKDatafileManager.framework $(SRCROOT)/Carthage/Build/<platform>/OptimizelySDKEventDispatcher.framework $(SRCROOT)/Carthage/Build/<platform>/OptimizelySDKShared.framework $(SRCROOT)/Carthage/Build/<platform>/OptimizelySDKUserProfile.framework $(SRCROOT)/Carthage/Build/<platform>/OptimizelySDK
.framework
Futher installation instructions through Carthage can be found here.
Manual Installation
The universal framework can be used in an application without the need for a third-party dependency manager. The universal framework packages up all Optimizely X Mobile modules, which include:
OptimizelySDKCore OptimizelySDKShared OptimizelySDKDatafileManager OptimizelySDKEventDispatcher OptimizelySDKUserProfile
The framework also embeds its third-party dependencies:
FMDB JSONModel
The universal framework for iOS includes builds for the following architectures:
i386 x86_64 ARMv7 ARMv7s ARM64
The universal framework for tvOS includes build for the following architectures:
x86_64 ARM64
Bitcode is enabled for both the iOS and tvOS universal frameworks.
In order to install the universal framework, follow the steps below:
Unzip the framework, then drag the framework to your project in Xcode. Xcode should prompt you to select a target. Go to Build Phases and make sure that the framework is under the Link Binary with Libraries section.
Go to the General tab and add the framework to the Embedded Binaries section. If the Embedded Binaries section is not visible, add the framework in the Copy Files section (you can add this section in Build Settings).
The Apple store will reject your app if you have the universal framework installed as it includes simulator binaries. Therefore, a script to strip the extra binaries needs to be run before you upload the app. To do this, go to Build Phases and add a Run Script section by clicking the
+symbol. Copy and paste the following script (make sure you replace theFRAMEWORK_NAMEwith the proper framework name):FRAMEWORK="FRAMEWORK_NAME" FRAMEWORK_EXECUTABLE_PATH="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/$FRAMEWORK.framework/$FRAMEWORK" EXTRACTED_ARCHS=() for ARCH in $ARCHS do lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH" EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH") done lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}" rm "${EXTRACTED_ARCHS[@]}" rm "$FRAMEWORK_EXECUTABLE_PATH" mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
If you choose to build the universal framework yourself, you can do so by running the OptimizelySDKiOS-Universal or OptimizelySDKTVOS-Universal schemes. The frameworks are output in the OptimizelySDKUniversal/generated-frameworks folder.
Our iOS and tvOS SDKs are available for distribution through CocoaPods, Carthage, or manual installation.
Requirements
Please note below that <platform> is used to represent the platform on which you are building your app. Currently, we support iOS and tvOS platforms.
Cocoapods
Add the following line to the Podfile:
pod 'OptimizelySDKiOS'Alternatively, if you are developing on tvOS, use:
pod 'OptimizelySDKTVOS'Make sure
use_frameworks!is also included in the Podfile:target 'Your App' do use_frameworks! pod 'OptimizelySDKiOS' endRun the following command:
pod install
Further installation instructions through Cocoapods can be found here.
Carthage
Add the following lines to the Cartfile:
github "optimizely/objective-c-sdk" github "jsonmodel/jsonmodel" github "ccgus/fmdb"Run the following command:
carthage updateLink the frameworks to your project. Go to your project target's Link Binary With Libraries and drag over the following from the Carthage/Build/<platform> folder:
FMDB.framework JSONModel.framework OptimizelySDKCore.framework OptimizelySDKDatafileManager.framework OptimizelySDKEventDispatcher.framework OptimizelySDKShared.framework OptimizelySDKUserProfile.framework OptimizelySDK<platform>.framework
To ensure that proper bitcode-related files and dSYMs are copied when archiving your app, you will need to install a Carthage build script:
- Add a new Run Script phase in your target's Build Phase.
- In the script area include:
/usr/local/bin/carthage copy-frameworks - Add the frameworks to the Input Files list:
$(SRCROOT)/Carthage/Build/<platform>/FMDB.framework $(SRCROOT)/Carthage/Build/<platform>/JSONModel.framework $(SRCROOT)/Carthage/Build/<platform>/OptimizelySDKCore.framework $(SRCROOT)/Carthage/Build/<platform>/OptimizelySDKDatafileManager.framework $(SRCROOT)/Carthage/Build/<platform>/OptimizelySDKEventDispatcher.framework $(SRCROOT)/Carthage/Build/<platform>/OptimizelySDKShared.framework $(SRCROOT)/Carthage/Build/<platform>/OptimizelySDKUserProfile.framework $(SRCROOT)/Carthage/Build/<platform>/OptimizelySDK
.framework
Futher installation instructions through Carthage can be found here.
Manual Installation
The universal framework can be used in an application without the need for a third-party dependency manager. The universal framework packages up all Optimizely X Mobile modules, which include:
OptimizelySDKCore OptimizelySDKShared OptimizelySDKDatafileManager OptimizelySDKEventDispatcher OptimizelySDKUserProfile
The framework also embeds its third-party dependencies:
FMDB JSONModel
The universal framework for iOS includes builds for the following architectures:
i386 x86_64 ARMv7 ARMv7s ARM64
The universal framework for tvOS includes build for the following architectures:
x86_64 ARM64
Bitcode is enabled for both the iOS and tvOS universal frameworks.
In order to install the universal framework, follow the steps below:
Unzip the framework, then drag the framework to your project in Xcode. Xcode should prompt you to select a target. Go to Build Phases and make sure that the framework is under the Link Binary with Libraries section.
Go to the General tab and add the framework to the Embedded Binaries section. If the Embedded Binaries section is not visible, add the framework in the Copy Files section (you can add this section in Build Settings).
The Apple store will reject your app if you have the universal framework installed as it includes simulator binaries. Therefore, a script to strip the extra binaries needs to be run before you upload the app. To do this, go to Build Phases and add a Run Script section by clicking the
+symbol. Copy and paste the following script (make sure you replace theFRAMEWORK_NAMEwith the proper framework name):FRAMEWORK="FRAMEWORK_NAME" FRAMEWORK_EXECUTABLE_PATH="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/$FRAMEWORK.framework/$FRAMEWORK" EXTRACTED_ARCHS=() for ARCH in $ARCHS do lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH" EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH") done lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}" rm "${EXTRACTED_ARCHS[@]}" rm "$FRAMEWORK_EXECUTABLE_PATH" mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
If you choose to build the universal framework yourself, you can do so by running the OptimizelySDKiOS-Universal or OptimizelySDKTVOS-Universal schemes. The frameworks are output in the OptimizelySDKUniversal/generated-frameworks folder.
The Android SDK packages are available on Jcenter:
- android-sdk: Includes all modules
- event-handler: Dispatches decision and track events to Optimizely
- user-profile: Makes bucketing persistent
- shared: Shared code between all modules
The android-sdk module transitively depends on the event-handler, user-profile, and shared modules. It also includes the latest version of gson and slf4j logger. Gradle can be used to exclude any dependency if you want to replace one with another version or implementation.
To add the android-sdk and all modules to your project simply include the following in your app's build.gradle in the dependencies block:
compile 'com.optimizely.ab:android-sdk:+'
Check android-sdk repo on Bintray to determine the latest version.
Code Shrinking
Building with Proguard enabled to shrink your application requires adding rules to preserve code that might otherwise be discarded. Add these rules to your Proguard file, typically named proguard-rules.pro.
Example Code
pip install optimizely-sdk
Example Code
repositories { jcenter() } dependencies { compile 'com.optimizely.ab:core-api:1.2.0' compile 'com.optimizely.ab:core-httpclient-impl:1.2.0' // The SDK integrates with multiple JSON parsers, here we use // Jackson. compile 'com.fasterxml.jackson.core:jackson-core:2.7.1' compile 'com.fasterxml.jackson.core:jackson-annotations:2.7.1' compile 'com.fasterxml.jackson.core:jackson-databind:2.7.1' }
Example Code
repositories { jcenter() } dependencies { compile 'com.optimizely.ab:android-sdk:+' }
Example Code
gem install optimizely-sdk
Example Code
Install-Package Optimizely.SDK
Example Code
npm install optimizely-server-sdk --save
Example Code
php composer.phar require optimizely/optimizely-sdk
Example Code
npm install optimizely-client-sdk --save
Datafile
The SDKs are required to read and parse a datafile at initialization.
The datafile is in JSON format and compactly represents all the instructions needed to activate experiments and track events in your code without requiring any blocking network requests. For example, the datafile displayed on the right represents an example project from the Getting started guide and the basic usage above.
Unless you are building your own SDK, there shouldn't be any need to interact with the datafile directly. Our SDKs should provide all the interfaces you need to interact with Optimizely after parsing the datafile.
Access datafile via CDN
You can fetch the datafile for your Optimizely project from Optimizely's CDN. For example, if the ID of your project is 12345 you can access the file at the link below:
https://cdn.optimizely.com/json/12345.json
You can easily access this link from your Optimizely project settings.
Access datafile via REST API
Alternatively, you can also access the datafile via Optimizely's authenticated REST API. For example, if the ID of your project is 12345 you can access the file at:
https://www.optimizelyapis.com/experiment/v1/projects/12345/json
Please note that as with other requests to the REST API, you will have to authenticate with an API token and use the preferred request library of your language.
Example datafile
{ "accountId": "12345", "projectId": "23456", "revision": "6", "version": "2", "experiments": [ { "key": "my_experiment", "id": "45678", "layerId": "34567", "status": "Running", "variations": [ { "id": "56789", "key": "control" }, { "id": "67890", "key": "treatment" } ], "trafficAllocation": [ { "entityId": "56789", "endOfRange": 5000 }, { "entityId": "67890", "endOfRange": 10000 } ], "audienceIds": [], "forcedVariations": {} } ], "events": [ { "experimentIds": [ "34567" ], "id": "56789", "key": "my_conversion" } ], "audiences": [], "attributes": [], "groups": [] }
Example datafile
{ "accountId": "12345", "projectId": "23456", "revision": "6", "version": "2", "experiments": [ { "key": "my_experiment", "id": "45678", "layerId": "34567", "status": "Running", "variations": [ { "id": "56789", "key": "control" }, { "id": "67890", "key": "treatment" } ], "trafficAllocation": [ { "entityId": "56789", "endOfRange": 5000 }, { "entityId": "67890", "endOfRange": 10000 } ], "audienceIds": [], "forcedVariations": {} } ], "events": [ { "experimentIds": [ "34567" ], "id": "56789", "key": "my_conversion" } ], "audiences": [], "attributes": [], "groups": [] }
Example datafile
{ "accountId": "12345", "projectId": "23456", "revision": "6", "version": "2", "experiments": [ { "key": "my_experiment", "id": "45678", "layerId": "34567", "status": "Running", "variations": [ { "id": "56789", "key": "control" }, { "id": "67890", "key": "treatment" } ], "trafficAllocation": [ { "entityId": "56789", "endOfRange": 5000 }, { "entityId": "67890", "endOfRange": 10000 } ], "audienceIds": [], "forcedVariations": {} } ], "events": [ { "experimentIds": [ "34567" ], "id": "56789", "key": "my_conversion" } ], "audiences": [], "attributes": [], "groups": [] }
Example datafile
{ "accountId": "12345", "projectId": "23456", "revision": "6", "version": "2", "experiments": [ { "key": "my_experiment", "id": "45678", "layerId": "34567", "status": "Running", "variations": [ { "id": "56789", "key": "control" }, { "id": "67890", "key": "treatment" } ], "trafficAllocation": [ { "entityId": "56789", "endOfRange": 5000 }, { "entityId": "67890", "endOfRange": 10000 } ], "audienceIds": [], "forcedVariations": {} } ], "events": [ { "experimentIds": [ "34567" ], "id": "56789", "key": "my_conversion" } ], "audiences": [], "attributes": [], "groups": [] }
Example datafile
{ "accountId": "12345", "projectId": "23456", "revision": "6", "version": "2", "experiments": [ { "key": "my_experiment", "id": "45678", "layerId": "34567", "status": "Running", "variations": [ { "id": "56789", "key": "control" }, { "id": "67890", "key": "treatment" } ], "trafficAllocation": [ { "entityId": "56789", "endOfRange": 5000 }, { "entityId": "67890", "endOfRange": 10000 } ], "audienceIds": [], "forcedVariations": {} } ], "events": [ { "experimentIds": [ "34567" ], "id": "56789", "key": "my_conversion" } ], "audiences": [], "attributes": [], "groups": [] }
Example datafile
{ "accountId": "12345", "projectId": "23456", "revision": "6", "version": "2", "experiments": [ { "key": "my_experiment", "id": "45678", "layerId": "34567", "status": "Running", "variations": [ { "id": "56789", "key": "control" }, { "id": "67890", "key": "treatment" } ], "trafficAllocation": [ { "entityId": "56789", "endOfRange": 5000 }, { "entityId": "67890", "endOfRange": 10000 } ], "audienceIds": [], "forcedVariations": {} } ], "events": [ { "experimentIds": [ "34567" ], "id": "56789", "key": "my_conversion" } ], "audiences": [], "attributes": [], "groups": [] }
Example datafile
{ "accountId": "12345", "projectId": "23456", "revision": "6", "version": "2", "experiments": [ { "key": "my_experiment", "id": "45678", "layerId": "34567", "status": "Running", "variations": [ { "id": "56789", "key": "control" }, { "id": "67890", "key": "treatment" } ], "trafficAllocation": [ { "entityId": "56789", "endOfRange": 5000 }, { "entityId": "67890", "endOfRange": 10000 } ], "audienceIds": [], "forcedVariations": {} } ], "events": [ { "experimentIds": [ "34567" ], "id": "56789", "key": "my_conversion" } ], "audiences": [], "attributes": [], "groups": [] }
Example datafile
{ "accountId": "12345", "projectId": "23456", "revision": "6", "version": "2", "experiments": [ { "key": "my_experiment", "id": "45678", "layerId": "34567", "status": "Running", "variations": [ { "id": "56789", "key": "control" }, { "id": "67890", "key": "treatment" } ], "trafficAllocation": [ { "entityId": "56789", "endOfRange": 5000 }, { "entityId": "67890", "endOfRange": 10000 } ], "audienceIds": [], "forcedVariations": {} } ], "events": [ { "experimentIds": [ "34567" ], "id": "56789", "key": "my_conversion" } ], "audiences": [], "attributes": [], "groups": [] }
Example datafile
{ "accountId": "12345", "projectId": "23456", "revision": "6", "version": "2", "experiments": [ { "key": "my_experiment", "id": "45678", "layerId": "34567", "status": "Running", "variations": [ { "id": "56789", "key": "control" }, { "id": "67890", "key": "treatment" } ], "trafficAllocation": [ { "entityId": "56789", "endOfRange": 5000 }, { "entityId": "67890", "endOfRange": 10000 } ], "audienceIds": [], "forcedVariations": {} } ], "events": [ { "experimentIds": [ "34567" ], "id": "56789", "key": "my_conversion" } ], "audiences": [], "attributes": [], "groups": [] }
Example datafile
{ "accountId": "12345", "projectId": "23456", "revision": "6", "version": "2", "experiments": [ { "key": "my_experiment", "id": "45678", "layerId": "34567", "status": "Running", "variations": [ { "id": "56789", "key": "control" }, { "id": "67890", "key": "treatment" } ], "trafficAllocation": [ { "entityId": "56789", "endOfRange": 5000 }, { "entityId": "67890", "endOfRange": 10000 } ], "audienceIds": [], "forcedVariations": {} } ], "events": [ { "experimentIds": [ "34567" ], "id": "56789", "key": "my_conversion" } ], "audiences": [], "attributes": [], "groups": [] }
Synchronization
In order to receive experiment updates, the SDK needs to periodically synchronize the datafile from Optimizely.
Our Android SDK includes an OptimizelyManager class that can be used to handle this synchronization for you. It periodically downloads the latest datafile from Optimizely asynchronously.
You should create an OptimizelyManager inside of Application#onCreate(). The example shows how to store the manager as a field in your Application subclass and provide a getter for it. This getter will be used to get the manager in Activities and Services. OptimizelyManager can also be used with dependency injection frameworks such as Dagger and Guice.
OptimizelyManager.builder() is used to make an OptimizelyManager. The builder allows you to configure the Android SDK.
.withEventHandlerDispatchInterval(long interval, TimeUnit) Controls how often the Android SDK will attempt to dispatch events to Optimizely results if there are events stored that failed to dispatch initially. Dispatch happens in an Android Service and will occur even if the app is not opened back up by the user. The service will only be scheduled when events that failed to dispatch are stored. It will be unscheduled when all stored events are flushed. Default: 1L, TimeUnit.Day
.withDataFileDownloadInterval(long interval, TimeUnit) Controls how often the Android SDK will attempt to sync the datafile. This sync happens in the background via an Android Service even when the app is not open. It helps ensure that the Android SDK's cached datafile is up to date. This service runs indefinitely on interval specified. Default: 1L,TimeUnit.Day
Working with multiple projects
You can have multiple Optimizely projects running in the same app. This can be accomplished by having one OptimizelyManager instance per project ID. Datafile caches, scheduling settings, and Optimizely objects are managed per project ID.
Note: You do not need to use OptimizelyManager. You are free to fetch the datafile on your own and create an Optimizely object via Optimizely.builder(String dataFile, EventHandler). You must also use the included EventHandler or implement your own. This is explained in detail below.
The OPTLYDatafileManager can be used to synchronize the datafile. The following datafile implementations are available:
OPTLYDatafileManagerDefault: Periodically downloads the datafile from Optimizely in the background (the default interval is set to 2 minutes). The datafile is saved if it has been updated. However, the SDK does not see this updated datafile until a new client is initialized with the new datafile. TheOptimizelySDKiOSandOptimizelySDKTVOSframeworks use this implementation out of the box.OPTLYDatafileManagerBasic: Basic implementation that downloads the datafile but does not include polling and saving. TheOptimizelySDKSharedframework uses this implementation out of the box.OPTLYDatafileMangerNoOp: No-op implementation that does not attempt to download the datafile.
The OPTLYDatafileManager can be used to synchronize the datafile. The following datafile implementations are available:
OPTLYDatafileManagerDefault: Periodically downloads the datafile from Optimizely in the background (the default interval is set to 2 minutes). The datafile is saved if it has been updated. However, the SDK does not see this updated datafile until a new client is initialized with the new datafile. TheOptimizelySDKiOSandOptimizelySDKTVOSframeworks use this implementation out of the box.OPTLYDatafileManagerBasic: Basic implementation that downloads the datafile but does not include polling and saving. TheOptimizelySDKSharedframework uses this implementation out of the box.OPTLYDatafileMangerNoOp: No-op implementation that does not attempt to download the datafile.
Example Code
public class MyApplication extends Application { private OptimizelyManager optimizelyManager; public OptimizelyManager getOptimizelyManager() { return optimizelyManager; } @Override public void onCreate() { super.onCreate(); optimizelyManager = OptimizelyManager.builder() .withEventHandlerDispatchInterval(1, TimeUnit.DAYS) .withDataFileDownloadInterval(1, TimeUnit.DAYS) .build(); } }
Example Code
OPTLYDatafileManagerDefault *datafileManager = [OPTLYDatafileManagerDefault initWithBuilderBlock:^(OPTLYDatafileManagerBuilder * _Nullable builder) { builder.projectId = @"1234"; builder.datafileFetchInterval = 120; // seconds }]; OPTLYManager *optlyManager = [OPTLYManager initWithBuilderBlock:^(OPTLYManagerBuilder * _Nullable builder) { builder.projectId = @"1234"; builder.datafileManager = datafileManager; }];
Example Code
let datafileManager: OPTLYDatafileManagerDefault? = OPTLYDatafileManagerDefault.initWithBuilderBlock({ (builder) in builder!.projectId = "1234" builder!.datafileFetchInterval = 120 // seconds }) let optimizelyManager: OPTLYManager? = OPTLYManager.initWithBuilderBlock({ (builder) in builder!.projectId = "1234" builder!.datafileManager = datafileManager })
Webhooks
If you are managing your datafile from a server-side application, we recommend configuring webhooks to maintain the most up-to-date version of the datafile. Your supplied endpoint will be sent a POST request whenever the respective project is modified. Anytime the datafile is updated, you must re-instantiate the Optimizely object in the SDK for the changes to take affect.
You can setup a webhook in your project settings and add the URL the webhook service should ping.
The webhook payload structure is shown at right. As of today, we support one event type, project.datafile_updated.
OptimizelyClient object with the OptimizelyManager#initialize method to get a new object backed by the new datafile. Read more in the Initialization section.
OPTLYClient object with one of the OPTLYManager.init() methods to get a new object backed by the new datafile. Read more in the Initialization section.
OPTLYClient object with one of the [OPTLYManager init] methods to get a new object backed by the new datafile. Read more in the Initialization section.
Example Code
{ "project_id": 1234, "timestamp": 1468447113, "event": "project.datafile_updated", "data": { "revision": 1, "origin_url": "https://optimizely.s3.amazonaws.com/json/1234.json", "cdn_url": "https://cdn.optimizely.com/json/1234.json" } }
Example Code
{ "project_id": 1234, "timestamp": 1468447113, "event": "project.datafile_updated", "data": { "revision": 1, "origin_url": "https://optimizely.s3.amazonaws.com/json/1234.json", "cdn_url": "https://cdn.optimizely.com/json/1234.json" } }
Example Code
{ "project_id": 1234, "timestamp": 1468447113, "event": "project.datafile_updated", "data": { "revision": 1, "origin_url": "https://optimizely.s3.amazonaws.com/json/1234.json", "cdn_url": "https://cdn.optimizely.com/json/1234.json" } }
Example Code
{ "project_id": 1234, "timestamp": 1468447113, "event": "project.datafile_updated", "data": { "revision": 1, "origin_url": "https://optimizely.s3.amazonaws.com/json/1234.json", "cdn_url": "https://cdn.optimizely.com/json/1234.json" } }
Example Code
{ "project_id": 1234, "timestamp": 1468447113, "event": "project.datafile_updated", "data": { "revision": 1, "origin_url": "https://optimizely.s3.amazonaws.com/json/1234.json", "cdn_url": "https://cdn.optimizely.com/json/1234.json" } }
Example Code
{ "project_id": 1234, "timestamp": 1468447113, "event": "project.datafile_updated", "data": { "revision": 1, "origin_url": "https://optimizely.s3.amazonaws.com/json/1234.json", "cdn_url": "https://cdn.optimizely.com/json/1234.json" } }
Example Code
{ "project_id": 1234, "timestamp": 1468447113, "event": "project.datafile_updated", "data": { "revision": 1, "origin_url": "https://optimizely.s3.amazonaws.com/json/1234.json", "cdn_url": "https://cdn.optimizely.com/json/1234.json" } }
Example Code
{ "project_id": 1234, "timestamp": 1468447113, "event": "project.datafile_updated", "data": { "revision": 1, "origin_url": "https://optimizely.s3.amazonaws.com/json/1234.json", "cdn_url": "https://cdn.optimizely.com/json/1234.json" } }
Example Code
{ "project_id": 1234, "timestamp": 1468447113, "event": "project.datafile_updated", "data": { "revision": 1, "origin_url": "https://optimizely.s3.amazonaws.com/json/1234.json", "cdn_url": "https://cdn.optimizely.com/json/1234.json" } }
Example Code
{ "project_id": 1234, "timestamp": 1468447113, "event": "project.datafile_updated", "data": { "revision": 1, "origin_url": "https://optimizely.s3.amazonaws.com/json/1234.json", "cdn_url": "https://cdn.optimizely.com/json/1234.json" } }
Versioning
For iOS, tvOS and Android projects, the datafile is versioned so that we can maintain backwards compatibility with SDKs that have been released out into the wild with older versions of the datafile. This will ensure that in the event our datafile schema changes, experiments will still run in apps that have not been updated with the latest version of the SDK.
All versions of the datafile for your Optimizely project are accessible via the CDN. For example, if the ID of your project is 12345 you can access v3 the datafile at the link below:
https://cdn.optimizely.com/public/12345/datafile_v3.json
We will upload every supported version of the datafile to our CDN so that SDKs that are pegged to an older version will be able to retrieve a datafile that is compatible with that SDK.
If you are using OPTLYManager (for iOS or tvOS) or OptimizelyManager (for Android) to manage datafile updates, these will gracefully handle datafile versioning. However, if you are managing the datafile on your own, then you have to make sure to fetch the correct version of the datafile according to the SDK version you are using.
Optimizely client
In SDK versions 0.1.1 and above, you can optionally pass in a skip_json_validation property as True to skip JSON schema validation of the datafile upon optimizely instantiation. Skipping JSON schema validation enhances instantiation performance. The skip_json_validation property should only be used if the datafile is being pulled from the REST API or CDN.
In SDK versions 0.1.1 and above, you can optionally pass in a skip_json_validation property as true to skip JSON schema validation of the datafile upon Project instantiation. Skipping JSON schema validation enhances instantiation performance. The skip_json_validation property should only be used if the datafile is being pulled from the REST API or CDN.
In addition to the datafile, you'll need to provide an EventHandler object as an argument to the Optimizely.builder function. You can use our default event handler implementation, as shown in the code at right, or provide your own implementation as described in the Event dispatcher section.
Optimizely.builder throws a checked exception, ConfigParseException, if the datafile provided is malformed or has an incorrect schema.
You can optionally pass in a skipJSONValidation property as true to skip JSON schema validation of the datafile upon optimizely instantiation. Skipping JSON schema validation enhances instantiation performance. The skipJSONValidation property should only be used if the datafile is being pulled from the REST API or CDN.
You can optionally pass in a skipJSONValidation property as true to skip JSON schema validation of the datafile upon optimizely instantiation. Skipping JSON schema validation enhances instantiation performance. The skipJSONValidation property should only be used if the datafile is being pulled from the REST API or CDN.
If you're using OptimizelyManager you can retrieve a client using OptimizelyManager#getOptimizely() on the main thread, as shown.
The getOptimizely method is annotated @NonNull and will always return an OptimizelyClient instance. If OptimizelyManager#getOptimizely() is called before the OptimizelyClient instance has been started for the first time then a "dummy" OptimizelyClient instance will be returned. This dummy instance logs errors when any of its methods are used. This technique prevents NullPointerExceptions and allows OptimizelyManager#getOptimizely() to be annotated with @NonNull.
In the manager's builder block, you can also specify your own datafile manager, event dispatcher, error handler, event dispatcher, logger, and user profile as long as the implementation conforms to the OPTLYDatafileManager, OPTLYEventDispatcher, OPTLYErrorHandler, OPTLYEventDispatcher, OPTLYLogger, and OPTLYUserProfile protocols, respectively.
See Initialization options below for various options for initializing the client.
In the manager's builder block, you can also specify your own datafile manager, event dispatcher, error handler, event dispatcher, logger, and user profile as long as the implementation conforms to the OPTLYDatafileManager, OPTLYEventDispatcher, OPTLYErrorHandler, OPTLYEventDispatcher, OPTLYLogger, and OPTLYUserProfile protocols, respectively.
See Initialization options below for various options for initializing the client.
Example Code
import optimizely # Initialize an Optimizely client optimizely_client = optimizely.Optimizely(datafile) # Skip JSON schema validation (SDK versions 0.1.1 and above) optimizely_client = optimizely.Optimizely(datafile, skip_json_validation=True)
Example Code
import com.optimizely.ab.Optimizely; import com.optimizely.ab.config.parser.ConfigParseException; import com.optimizely.ab.event.AsyncEventHandler; import com.optimizely.ab.event.EventHandler; // Creates an async event handler with a max buffer of // 20,000 events and a single dispatcher thread EventHandler eventHandler = new AsyncEventHandler(20000, 1); Optimizely optimizelyClient; try { // Initialize an Optimizely client optimizelyClient = Optimizely.builder(datafile, eventHandler).build(); } catch (ConfigParseException e) { // Handle exception gracefully return; }
Example Code
using OptimizelySDK; Optimizely OptimizelyClient; try { OptimizelyClient = new Optimizely(datafile); } catch (Exception e) { // Handle exception gracefully return; }
Example Code
final OptimizelyManager optimizelyManager = ((MyApplication) getApplication()).getOptimizelyManager(); OptimizelyClient optimizely = optimizelyManager.getOptimizely();
Example Code
require "optimizely" # Initialize an Optimizely client optimizely_client = Optimizely::Project.new(datafile) # Skip JSON schema validation (SDK versions 0.1.1 and above) optimizely_client = Optimizely::Project.new(datafile, nil, nil, nil, true)
Example Code
var optimizely = require('optimizely-server-sdk'); // Or ES6 import optimizely from 'optimizely-server-sdk' // Initialize an Optimizely client var optimizelyClient = optimizely.createInstance({ datafile: datafile }); // Skip JSON schema validation for the datafile var optimizelyClient = optimizely.createInstance({ datafile: datafile, skipJSONValidation: true });
Example Code
use Optimizely\Optimizely; // Initialize an Optimizely client $optimizelyClient = new Optimizely($datafile); // Skip JSON schema validation $optimizelyClient = new Optimizely($datafile, null, null, null, true);
Example Code
var optimizely = require('optimizely-client-sdk'); // Or ES6 import optimizely from 'optimizely-client-sdk'; // Initialize an Optimizely client var optimizelyClientInstance = optimizely.createInstance({ datafile: datafile }); // Skip JSON schema validation for the datafile var optimizelyClientInstance = optimizely.createInstance({ datafile: datafile, skipJSONValidation: true });
Example Code
OPTLYClient *optimizelyClient = [optimizelyManager initializeWithDatafile:datafile];
Example Code
let optimizelyClient: OPTLYClient? = optimizelyManager?.initialize(withDatafile:jsonDatafile!)
Initialization options
Depending on where your experiments are running in the app you may want to initialize your client differently.
Starting on the First Activity
If you stored your OptimizelyManager as a field in your Application implementation and provided a getter you can retrieve it in your first Activity. First, get an instance of Application via Activity#getApplication()). Next, cast the Application object to the type of your Application instance. In the example this is MyApplication. Then you can use any public getter you defined such as .getOptimizelyManager. Service and Fragment also have .getApplication(). This is a convenient way to pass around the OptimizelyManager. Other ways are possible, such as injecting with a Dependency Injection framework such as Dagger.
On the first launch of app the Android SDK does not have a cached datafile so one needs to be fetched from the network. Network requests should not occur on the main thread so the Optimizely instance must be injected asynchronously. Use OptimizelyManager.initialize(Activity, OptimizelyStartListener) to request an Optimizely object that can be used for testing. This call can also be put in Activity#onStart() instead of Activity#onCreate(Bundle) #stop(Context) for starting somewhere besides Activity like Service. Stopping is only managed by the Android SDK when starting from Activity.
Synchronous Start on the First Activity on the First Launch
The Android SDK can be started from a datafile that is compiled into your app ahead of time. This allows you to setup tests on the first Activity of your app before the UI is shown to the user without a blocking network request. Simply get your datafile and place it in src/<source_set>/res/raw. It can be named whatever you want. Use OptimizelyManager#initialize(Context, @RawRes int) to build an instance of OptimizelyClient. This instance will be the cached instance returned by OptimizelyManager#getOptimizely(). This cached
instance will be replaced by the instance built after calling OptimizelyManager.initialize(Activity, OptimizelyStartListener).
Starting on Subsequent Activities, Fragments, or Services
For subsequent Activities, Fragments, or Services, you can use the OptimizelyManager#getOptimizely() method as shown.
The OPTLYManager class provides several options for initializing your client depending where you are running experiments in the app. See OPTLYManager.h for full details.
Asynchronous call
Use initializeClientWithCallback to asynchronously initialize a client using a datafile downloaded from the Optimizely CDN. If the client cannot be initialized, the error will be set in the callback.
Synchronous call from local cache
Use initializeClient to synchronously initialize a client using a datafile from the local cache. If it fails to load from local cache it will return a dummy instance.
Synchronous call with provided datafile
Use initializeClientWithDatafile to synchronously initialize a client using a provided datafile. If the datafile is bad, then the client will try to get the datafile from local cache (if it exists). If it fails to load from local cache it will return a dummy instance.
The OPTLYManager class provides several options for initializing your client depending where you are running experiments in the app. See OPTLYManager.h for full details.
Asynchronous call
Use initializeClientWithCallback to asynchronously initialize a client using a datafile downloaded from the Optimizely CDN. If the client cannot be initialized, the error will be set in the callback.
Synchronous call from local cache
Use initializeClient to synchronously initialize a client using a datafile from the local cache. If it fails to load from local cache it will return a dummy instance.
Synchronous call with provided datafile
Use initializeClientWithDatafile to synchronously initialize a client using a provided datafile. If the datafile is bad, then the client will try to get the datafile from local cache (if it exists). If it fails to load from local cache it will return a dummy instance.
See SDK configuration for full details on how you can customize your Optimizely client.
Example Code
public class FirstActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); OptimizelyManager optimizelyManager = ((MyApplication) getApplication()).getOptimizelyManager(); optimizelyManager.initialize(this, new OptimizelyStartListener() { @Override public void onStart(OptimizelyClient optimizely) { assert optimizely == optimizelyManager.getOptimizely(); Intent intent = new Intent(FirstActivity.this, SubsequentActivity.class); startActivity(intent); } }); } } public class FirstActivitySynchronousStart extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_first); OptimizelyManager optimizelyManager = ((MyApplication) getApplication()).getOptimizelyManager(); assert optimizelyManager.getOptimizely() == null; OptimizelyClient optimizely = optimizelyManager.initialize(MyActivity.this, R.raw.data_file); assert optimizely != null; } } public class SubsequentActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_subsequent); final OptimizelyManager optimizelyManager = ((MyApplication) getApplication()).getOptimizelyManager(); OptimizelyClient optimizely = optimizelyManager.getOptimizely(); assert optimizely != null; } }
Experiments
Use the activate function to run an experiment at any point in your code.
The activate function requires an experiment key and a user ID. The experiment key should match the experiment key you provide when setting up the experiment in the Optimizely web portal. The user ID is a string to uniquely identify the participant in the experiment (read more in User IDs below).
The activate function returns which variation the current user is assigned to. If the experiment is running and the user satisfies audience conditions for the experiment, the function returns a variation based on a deterministic murmur hash of the provided experiment key and user ID. This function also respects whitelisting and and user profiles.
activate returns None.
activate returns null.
activate returns null.
activate returns nil.
activate returns null.
activate returns null.
activate returns null.
activate returns nil.
activate returns nil.
activate returns null.
Make sure that your code adequately deals with the case when the experiment is not activated i.e. execute the default variation.
The activate function also sends an event to Optimizely to record that the current user has been exposed to the experiment. You should call activate at the point you want to record an experiment exposure to Optimizely. If you don't want to record an experiment exposure, you can use an alternative function below to get the variation.
Example Code
experiment_key = 'my_experiment' user_id = 'user123' # Conditionally activate an experiment for the provided user variation = optimizely_client.activate(experiment_key, user_id) if variation == 'control': # Execute code for control variation elif variation == 'treatment': # Execute code for treatment variation else: # Execute default code
Example Code
String experimentKey = "my_experiment"; String userId = "user123"; // Conditionally activate an experiment for the provided user Variation variation = optimizelyClient.activate(experimentKey, userId); if (variation != null) { if (variation.is("control")) { // Execute code for control variation } else if (variation.is("treatment")) { // Execute code for treatment variation } } else { // Execute default code }
Example Code
var experimentKey = "my_experiment"; var userId = "user123"; // Conditionally activate an experiment for the provided user var variation = OptimizelyClient.Activate(experimentKey, userId); if (variation != null) { if (variation == "control") { // Execute code for control variation } else if (variation == "treatment") { // Execute code for treatment variation } } else { // Execute default code }
Example Code
OptimizelyClient optimizelyClient = optimizelyManager.getOptimizely(); String experimentKey = "my_experiment"; String userId = "user123"; // Conditionally activate an experiment for the provided user Variation variation = optimizelyClient.activate(experimentKey, userId); if (variation != null) { if (variation.is("control")) { // Execute code for control variation } else if (variation.is("treatment")) { // Execute code for treatment variation } } else { // Execute default code }
Example Code
experiment_key = 'my_experiment' user_id = 'user123' # Conditionally activate an experiment for the provided user variation = optimizely_client.activate(experiment_key, user_id) if variation == 'control' # Execute code for control variation elsif variation == 'treatment' # Execute code for treatment variation else # Execute default code end
Example Code
var experimentKey = 'my_experiment'; var userId = 'user123'; // Conditionally activate an experiment for the provided user var variation = optimizelyClient.activate(experimentKey, userId); if (variation === 'control') { // Execute code for control variation } else if (variation === 'treatment') { // Execute code for treatment variation } else { // Execute default code }
Example Code
var experimentKey = 'my_experiment'; var userId = 'user123'; // Conditionally activate an experiment for the provided user var variation = optimizelyClientInstance.activate(experimentKey, userId); if (variation === 'control') { // Execute code for control variation } else if (variation === 'treatment') { // Execute code for treatment variation } else { // Execute default code }
Example Code
$experimentKey = 'my_experiment'; $userId = 'user123'; // Conditionally activate an experiment for the provided user $variation = $optimizelyClient->activate($experimentKey, $userId); if ($variation == 'control') { // Execute code for control variation } elseif ($variation == 'treatment') { // Execute code for treatment variation } else { # Execute default code }
Example Code
// Conditionally activate an experiment for the provided user OPTLYVariation *variation = [optimizely activate:@"my_experiment" userId:@"user123"]; if ([variation.variationKey isEqualToString:@"control"]) { // Execute code for control variation } else if ([variation.variationKey isEqualToString:@"treatment"]) { // Execute code for treatment variation } else { // Execute default code }
Example Code
// Conditionally activate an experiment for the provided userId if let variation = optimizely?.activate("my_experiment", userId: "user123") { if (variation.variationKey == "control") { // Execute code for control variation } else if (variation.variationKey == "treatment") { // Execute code for treatment variation } } else { // Execute default code }
User attributes
If you'd like to be able to segment your experiment data based on attributes of your users, you should include the optional attributes argument when activating experiments. Optimizely will include these attributes in the recorded event data so you can segment them on the Optimizely results page.
Passing attributes will also allow you to target your experiments to a particular audience you've defined in Optimizely. If the provided experiment is targeted to an audience, Optimizely will evaluate whether the user falls in an audience that is associated with the experiment before bucketing.
For more information on managing audiences and attributes, see our Optiverse article.
Example Code
experiment_key = 'my_experiment' user_id = 'user123' attributes = {'device': 'iPhone', 'ad_source': 'my_campaign'} # Conditionally activate an experiment for the provided user variation = optimizely.activate(experiment_key, user_id, attributes)
Example Code
String experimentKey = "my_experiment"; String userId = "user123"; Map<String, String> attributes = new HashMap<String, String>(); attributes.put("DEVICE", "iPhone"); attributes.put("AD_SOURCE", "my_campaign"); // Conditionally activate an experiment for the provided user Variation variation = optimizelyClient.activate(experimentKey, userId, attributes);
Example Code
using OptimizelySDK.Entity; var experimentKey = "my_experiment"; var userId = "user123"; UserAttributes attributes = new UserAttributes { { "DEVICE", "iPhone" }, { "AD_SOURCE", "my_campaign" } }; // Conditionally activate an experiment for the provided user var variation = OptimizelyClient.Activate(experimentKey, userId, attributes);
Example Code
OptimizelyClient optimizely = optimizelyManager.getOptimizely(); String experimentKey = "my_experiment"; String userId = "user123"; Map<String, String> attributes = new HashMap<String, String>(); attributes.put("DEVICE", "Nexus 6P"); attributes.put("AD_SOURCE", "my_campaign"); // Conditionally activate a experiment for the provided user Variation variation = optimizely.activate(experimentKey, userId, attributes);
Example Code
experiment_key = 'my_experiment' user_id = 'user123' attributes = {'DEVICE' => 'iPhone', 'AD_SOURCE' => 'my_campaign'} # Conditionally activate an experiment for the provided user variation = optimizely_client.activate(experiment_key, user_id, attributes)
Example Code
var experimentKey = 'my_experiment'; var userId = 'user123'; var attributes = { 'device': 'iphone', 'ad_source': 'my_campaign' }; // Conditionally activate an experiment for the provided user var variation = optimizely.activate(experimentKey, userId, attributes);
Example Code
var experimentKey = 'my_experiment'; var userId = 'user123'; var attributes = { 'device': 'iphone', 'ad_source': 'my_campaign' }; // Conditionally activate an experiment for the provided user var variation = optimizelyClientInstance.activate(experimentKey, userId, attributes);
Example Code
$experimentKey = 'my_experiment'; $userId = 'user123'; $attributes = [ 'device' => 'iphone', 'ad_source' => 'my_campaign' ]; // Conditionally activate an experiment for the provided user $variation = $optimizelyClient->activate($experimentKey, $userId, $attributes);
Example Code
NSDictionary *attributes = @{@"device" : @"iPhone", @"ad_source" : @"my_campaign"}; OPTLYVariation *variation = [optimizely activate:@"my_experiment" userId:@"user123" attributes:attributes];
Example Code
let attributes = ["device" : "iPhone", "ad_source" : "my_campaign"] let variation: OPTLYVariation? = optimizely?.activate("my_experiment", userId:"user123", attributes:attributes)
Variation assignments
If you would like to retrieve the variation assignment for a given experiment and user without sending a network request to Optimizely, use the code shown on the right. This function has identical behavior to activate except that no event is dispatched to Optimizely.
You may want to use this if you're calling activate in a different part of your application, or in a different part of your stack, and you want to fork code for your experiment in multiple places. It is still necessary to call activate at least once to register that the user was exposed to the experiment.
Example Code
experiment_key = 'my_experiment' user_id = 'user123' # Get the active variation for the provided user variation = optimizely_client.get_variation(experiment_key, user_id)
Example Code
String experimentKey = "my_experiment"; String userId = "user123"; // Get the active variation for the provided user Variation variation = optimizelyClient.getVariation(experimentKey, userId);
Example Code
var experimentKey = "my_experiment"; var userId = "user123"; // Get the active variation for the provided user var variation = OptimizelyClient.GetVariation(experimentKey, userId);
Example Code
String experimentKey = "my_experiment"; String userId = "user123"; Optimizely optimizelyClient = optimizelyManager.getOptimizely(); // Get the active variation for the provided user Variation variation = optimizelyClient.getVariation(experimentKey, userId);
Example Code
experiment_key = 'my_experiment' user_id = 'user123' # Get the active variation for the provided user variation = optimizely_client.get_variation(experiment_key, user_id)
Example Code
$experimentKey = 'my_experiment'; $userId = 'user123'; # Get the active variation for the provided user $variation = $optimizelyClient->getVariation($experimentKey, $userId);
Example Code
var experimentKey = 'my_experiment'; var userId = 'user123'; // Get the active variation for the provided user var variation = optimizelyClientInstance.getVariation(experimentKey, userId);
Example Code
var experimentKey = 'my_experiment'; var userId = 'user123'; // Get the active variation for the provided user var variation = optimizelyClient.getVariation(experimentKey, userId);
Example Code
// Get the active variation for an experiment for the provided user OPTLYVariation *variation = [optimizely variation:@"my_experiment" userId:@"user123"];
Example Code
// Get the active variation for an experiment for the provided user let variation: OPTLYVariation? = optimizely?.variation("my_experiment", userId:"user123")
Events
You can easily track conversion events from your code using the track function.
The track function requires an event key and a user ID. The event key should match the event key you provided when setting up events in the Optimizely web portal. The user ID should match the user ID provided in the activate function.
The track function can be used to track events across multiple experiments, and will be counted for each experiment only if activate has previously been called for the current user.
To enable segmentation of metrics on the Optimizely results page, you'll also need to pass the same user attributes you used in the activate call.
activate has been called. If you are not seeing results on the Optimizely results page, make sure that you are calling activate before tracking conversion events.
Example Code
event_key = 'my_conversion' user_id = 'user123' attributes = {'device': 'iPhone', 'ad_source': 'my_campaign'} # Track a conversion event for the provided user optimizely_client.track(event_key, user_id) # Track a conversion event for the provided user with attributes optimizely_client.track(event_key, user_id, attributes);
Example Code
String eventKey = "my_conversion"; String userId = "user123"; Map<String, String> attributes = new HashMap<String, String>(); attributes.put("DEVICE", "iPhone"); attributes.put("AD_SOURCE", "my_campaign"); // Track a conversion event for the provided user optimizelyClient.track(eventKey, userId); // Track a conversion event for the provided user with attributes optimizelyClient.track(eventKey, userId, attributes);
Example Code
using OptimizelySDK.Entity; var eventKey = "my_conversion"; var userId = "user123"; UserAttributes attributes = new UserAttributes { { "DEVICE", "iPhone" }, { "AD_SOURCE", "my_campaign" } }; // Track a conversion event for the provided user OptimizelyClient.Track(eventKey, userId); // Track a conversion event for the provided user with attributes OptimizelyClient.Track(eventKey, userId, attributes);
Example Code
String eventKey = "my_conversion"; String userId = "user123"; Map<String, String> attributes = new HashMap<String, String>(); attributes.put("DEVICE", "iPhone"); attributes.put("AD_SOURCE", "my_campaign"); Optimizely optimizelyClient = optimizelyManager.getOptimizely(); // Track a conversion event for the provided user optimizelyClient.track(eventKey, userId); // Track a conversion event for the provided user with attributes optimizelyClient.track(eventKey, userId, attributes);
Example Code
event_key = 'my_conversion' user_id = 'user123' attributes = { 'device' => 'iPhone', 'adSource' => 'my_campaign' } # Track a conversion event for the provided user optimizely_client.track(event_key, user_id) # Track a conversion event for the provided user with attributes optimizely_client.track(event_key, user_id, attributes)
Example Code
var eventKey = 'my_conversion'; var userId = 'user123'; var attributes = { device: 'iPhone', ad_source: 'my_campaign' }; // Track a conversion event for the provided user optimizelyClient.track(eventKey, userId); // Track a conversion event for the provided user with attributes optimizelyClient.track(eventKey, userId, attributes);
Example Code
var eventKey = 'my_conversion'; var userId = 'user123'; var attributes = { device: 'iPhone', ad_source: 'my_campaign' }; // Track a conversion event for the provided user optimizelyClientInstance.track(eventKey, userId); // Track a conversion event for the provided user with attributes optimizelyClient.track(eventKey, userId, attributes);
Example Code
$eventKey = 'my_conversion'; $userId = 'user123'; $attributes = [ 'device' => 'iphone', 'ad_source' => 'my_campaign' ]; // Track a conversion event for the provided user $optimizelyClient->track($eventKey, $userId); // Track a conversion event for the provided user with attributes $optimizelyClient->track($eventKey, $userId, $attributes);
Example Code
NSDictionary *attributes = @{@"device" : @"iPhone", @"ad_source" : @"my_campaign"}; // Track a conversion event for the provided user [optimizely track:@"my_conversion" userId:@"user123"]; // Track a conversion event for the provided user with attributes [optimizely track:@"my_conversion" userId:@"user123" attributes:attributes];
Example Code
let attributes = ["device" : "iPhone", "ad_source" : "my_campaign"] // Track a conversion event for the provided user optimizely?.track("my_conversion", userId:"user123") // Track a conversion event for the provided user with attributes optimizely?.track("my_conversion", userId:"user123", attributes:attributes)
Event tags
Event tags are contextual metadata about conversion events that you track.
You can use event tags to attach any key/value data you wish to events. For example, for a product purchase event you may want to attach a product SKU, product category, order ID, and purchase amount. Event tags can be strings, integers, floating point numbers, or Boolean values.
You can include event tags with an optional argument in track as shown on the right.
Event tags are distinct from user attributes which should be reserved for user-level targeting and segmentation. Event tags do not affect audiences or the Optimizely results page, and do not need to be registered in the Optimizely web interface.
Event tags are accessible via raw data export in the event_features column. You should include any event tags you need to reconcile your conversion event data with your data warehouse.
Reserved tags
The following tag keys are reserved and will be included in their corresponding fields in the Optimizely event API payload. They're bundled into event tags for your convenience. Use them if you'd like to benefit from specific reporting features such as revenue metrics.
revenue- An integer value that is tracked in aggregate as an experiment metric. Revenue is recorded in cents: if you'd like to record a revenue value of $64.32 use6432.
Example Code
event_key = 'my_conversion' user_id = 'user123' attributes = { 'device': 'iPhone', 'adSource': 'my_campaign' } event_tags = { 'category': 'shoes', 'purchasePrice': 64.32, 'revenue': 6432 # reserved "revenue" tag } # Track event with user attributes and event tags optimizely_client.track(event_key, user_id, attributes, event_tags) # Track event with event tags and without user attributes optimizely_client.track(event_key, user_id, event_tags=event_tags)
Example Code
String eventKey = "my_conversion"; String userId = "user123"; Map<String, String> attributes = new HashMap<String, String>(); attributes.put("DEVICE", "iPhone"); attributes.put("AD_SOURCE", "my_campaign"); Map<String, Object> eventTags = new HashMap<String, Object>(); eventTags.put("purchasePrice", 64.32f); eventTags.put("category", "shoes"); // reserved "revenue" tag, eventTags.put("revenue", 6432); // Track event with user attributes and event tags optimizely.track(eventKey, userId, attributes, eventTags);
Example Code
using OptimizelySDK.Entity; var eventKey = "my_conversion"; var userId = "user123"; UserAttributes attributes = new UserAttributes { { "DEVICE", "iPhone" }, { "AD_SOURCE", "my_campaign" } }; EventTags tags = new EventTags { { "purchasePrice", 64.32 }, { "category", "shoes" }, { "revenue", 6432 } // reserved "revenue" tag }; // Track event with user attributes and event tags OptimizelyClient.Track(eventKey, userId, attributes, tags);
Example Code
event_key = 'my_conversion' user_id = 'user123' attributes = { 'device' => 'iPhone', 'adSource' => 'my_campaign' } event_tags = { 'category' => 'shoes', 'purchasePrice' => 64.32, 'revenue' => 6432 # reserved "revenue" tag } # Track event with user attributes and event tags optimizely_client.track(event_key, user_id, attributes, event_tags) # Track event with event tags and without user attributes optimizely_client.track(event_key, user_id, nil, event_tags)
Example Code
$eventKey = 'my_conversion'; $userId = 'user123'; $attributes = [ 'device' => 'iphone', 'ad_source' => 'my_campaign' ]; $eventTags = [ 'category' => 'shoes', 'purchasePrice' => 64.32, 'revenue' => 6432 # reserved "revenue" tag ]; # Track event with user attributes and event tags $optimizelyClient->track($eventKey, $userId, $attributes, $eventTags); # Track event with event tags and without user attributes $optimizelyClient->track($eventKey, $userId, null, $eventTags);
Example Code
var eventKey = "my_conversion"; var userId = "user123"; var attributes = { DEVICE: 'iPhone', AD_SOURCE: 'my_campaign' }; var eventTags = { category: 'shoes', purchasePrice: 64.32, revenue: 6432 // reserved "revenue" tag }; // Track event with user attributes and event tags optimizely.track(eventKey, userId, attributes, eventTags);
Example Code
var eventKey = "my_conversion"; var userId = "user123"; var attributes = { DEVICE: 'iPhone', AD_SOURCE: 'my_campaign' }; var eventTags = { category: 'shoes', purchasePrice: 64.32, revenue: 6432 // reserved "revenue" tag }; // Track event with user attributes and event tags optimizely.track(eventKey, userId, attributes, eventTags);
Example Code
NSDictionary *attributes = @{@"device" : @"iPhone", @"ad_source" : @"my_campaign"}; NSDictionary *eventTags = @{ @"purchasePrice" : @64.32, @"category" : @"shoes", @"revenue": @6432 // reserved "revenue" tag }; // Track event with user attributes and event tags [optimizely track:@"my_conversion" userId:@"user123" attributes:attributes eventTags:eventTags];
Example Code
let attributes = ["device" : "iPhone", "ad_source" : "my_campaign"] var eventTags = Dictionary<String, Any>() eventTags["purchasePrice"] = 64.32 eventTags["category"] = "shoes" eventTags["revenue"] = 6432 // reserved "revenue" tag // Track event with user attributes and event tags optimizely?.track("my_conversion", userId:"user123", attributes:attributes, eventTags:eventTags)
Example Code
String eventKey = "my_conversion"; String userId = "user123"; Map<String, String> attributes = new HashMap<String, String>(); attributes.put("DEVICE", "iPhone"); attributes.put("AD_SOURCE", "my_campaign"); Map<String, Object> eventTags = new HashMap<String, Object>(); eventTags.put("purchasePrice", 64.32f); eventTags.put("category", "shoes"); // reserved "revenue" tag, eventTags.put("revenue", 6432); Optimizely optimizelyClient = optimizelyManager.getOptimizely(); // Track event with user attributes and event tags optimizelyClient.track(eventKey, userId, attributes, eventTags);
Tracking with other SDKs
You can use any of our SDKs to track events, so you can run experiments that span multiple applications, services, or devices. All of our SDKs have the same bucketing and targeting behavior so you'll see the exact same output from experiment activation and tracking, provided you are using the same datafile and user IDs.
For example, if you're running experiments on your server you can activate experiments with our Python, Java, Ruby, C#, Node, or PHP SDKs, but track user actions client-side using our JavaScript, Objective-C or Android SDKs.
If you plan on using multiple SDKs for the same project, make sure that all SDKs are sharing the same datafile and user IDs.
User IDs
User IDs are used to uniquely identify the participants in your experiments. You can supply any string you wish for user IDs depending on your experiment design.
For example, if you're running experiments on anonymous users, you can use a 1st party cookie or device ID to identify each participant. If you're running experiments on known users, you can use a universal user identifier (UUID) to identify each participant. If you're using UUIDs then you can run experiments that span multiple devices or applications and ensure that users have a consistent treatment.
User IDs don't necessarily need to correspond to individual users. If you're running experiments in a B2B SaaS application, you may want to pass account IDs to the SDK to ensure that every user in a given account has the same treatment.
Below are some additional tips for using user IDs.
Ensure user IDs are unique: It is essential that user IDs are unique among the population you are using for experiments. Optimizely will bucket users and provide experiment metrics based on this user ID that you provide.
Anonymize user IDs: The user IDs you provide will be sent to Optimizely servers exactly as you provide them. You are responsible for anonymizing any personally identifiable data such as email addresses in accordance with your company's policies.
Use IDs from 3rd party platforms: If you are measuring the impact of your experiments in a 3rd party analytics platform, we recommend leveraging the same user ID from that platform. This will help ensure data consistency and make it easier to reconcile events between systems.
Use one namespace per project: Optimizely generally assumes a single namespace of user IDs for each project. If you are using multiple different conventions for user IDs in a single project (e.g. anonymous visitor IDs for some experiments and UUIDs for others) then Optimizely will be unable to enforce rules such as mutual exclusivity between experiments.
Use either logged-out vs. logged-in IDs: We do not currently provide a mechanism to alias logged-out IDs with logged-in IDs. If you are running experiments that span both logged-out and logged-in states (e.g. experiment on a signup funnel and track conversions after the user has logged in), you must persist logged-out IDs for the lifetime of the experiment.
Variables
Use Live Variables to parameterize your app with features you'd like to experiment with in real-time.
Variables can be declared and initialized once in your app. After deploying those variables to production, you can subsequently run unlimited experiments on different values of those variables without doing another code deploy. Variables can be controlled from the Optimizely UI so you can roll out features and tweak the behavior of your app in real-time. See our Optiverse article for more details on how to control variables from the Optimizely user interface.
getVariable functions includes the following arguments:
- variableKey: A unique name of the variable, as defined in the Optimizely UI.
- userId: A unique identifier for the current user.
- attributes (optional): A map of user attributes and values for the current user.
- activateExperiment: If set to
true, record impression events to Optimizely for the experiment(s) that include this variable.
null. Your code must correctly handle cases where a null value is returned.
Note: If your experiment can be defined completely by a set of variables, it is not necessary to call activate to run the experiment in your code. You can just add getters for each of the relevant variables and subsequently create new experiments on the fly. The activateExperiment parameter can be used to ensure that impressions are correctly recorded in Optimizely in the event you are not using activate. If you are using activate for an experiment that includes the live variable then you can set this to false to avoid redundant network requests.
getVariable functions includes the following arguments:
- variableKey: A unique name of the variable, as defined in the Optimizely UI.
- userId: A unique identifier for the current user.
- attributes (optional): A map of user attributes and values for the current user.
- activateExperiment: If set to
true, record impression events to Optimizely for the experiment(s) that include this variable. - error (optional): An error value to return if the variable key is not present in the datafile.
nil is returned. If an error is found, a warning message is logged, and an error will be propagated to the user.
Note: If your experiment can be defined completely by a set of variables, it is not necessary to call activate to run the experiment in your code. You can just add getters for each of the relevant variables and subsequently create new experiments on the fly. The activateExperiment parameter can be used to ensure that impressions are correctly recorded in Optimizely in the event you are not using activate. If you are using activate for an experiment that includes the live variable then you can set this to false to avoid redundant network requests.
getVariable functions includes the following arguments:
- variableKey: A unique name of the variable, as defined in the Optimizely UI.
- userId: A unique identifier for the current user.
- attributes (optional): A map of user attributes and values for the current user.
- activateExperiment: If set to
true, record impression events to Optimizely for the experiment(s) that include this variable. - error (optional): An error value to return if the variable key is not present in the datafile.
nil is returned. If an error is found, a warning message is logged, and an error will be propagated to the user.
Note: If your experiment can be defined completely by a set of variables, it is not necessary to call activate to run the experiment in your code. You can just add getters for each of the relevant variables and subsequently create new experiments on the fly. The activateExperiment parameter can be used to ensure that impressions are correctly recorded in Optimizely in the event you are not using activate. If you are using activate for an experiment that includes the live variable then you can set this to false to avoid redundant network requests.
Example Code
// Get the value of a boolean variable bool myVariable = [optimizely variableBoolean:@"myVariable" userId:userId] activateExperiment:True]; // Get the value of a double variable double myVariable = [optimizely variableDouble:@"myVariable" userId:userId activateExperiment:True]; // Get the value of an integer variable int myVariable = [optimizely variableInteger:@"myVariable" userId:userId activateExperiment:True]; // Get the value of a string variable NSString *myVariable = [optimizely variableString:@"myVariable" userId:userId activateExperiment:True];
Example Code
// Get the value of a boolean variable let myVariable = optimizely?.variableBoolean:("myVariable", userId:"user123", activateExperiment:true) // Get the value of a double variable let myVariable = optimizely?.variableDouble:("myVariable", userId:"user123", activateExperiment:true) // Get the value of an integer variable let myVariable = optimizely?.variableInteger:("myVariable", userId:"user123", activateExperiment:true) // Get the value of a string variable let myVariable = optimizely?.variableString:("myVariable", userId:"user123", activateExperiment:true)
Example Code
String userId = "user123"; // Get the value of a Boolean variable Boolean myVariable = optimizelyClient.getVariableBoolean("myVariable", userId, true); // Get the value of a floating point variable Float myVariable = optimizelyClient.getVariableFloat("myVariable", userId, true); // Get the value of an integer variable Integer myVariable = optimizelyClient.getVariableInteger("myVariable", userId, true); // Get the value of a string variable String myVariable = optimizelyClient.getVariableString("myVariable", userId, true);
Exclusion groups
You can use Exclusion Groups to keep your experiments mutually exclusive and eliminate interaction effects. For more information on how to set up exclusion groups in the Optimizely UI, see our Optiverse article.
Experiments that are part of a group have the exact same interface in the SDK: you can call activate and track like any other experiment. The SDK will ensure that two experiments in the same group will never be activated for the same user.
Whitelists
You can use Whitelisting to force users into specific variations for QA purposes. For more information on how to set up whitelists in the Optimizely UI, see our Optiverse article.
Whitelists are included in your datafile in the forcedVariations field. You don't need to do anything differently in the SDK; if you've set up a whitelist, experiment activation will force the variation output based on the whitelist you've provided. Whitelisting overrides audience targeting and traffic allocation. Whitelisting does not work if the experiment is not running.
IP anonymization
In some countries, you may be required to remove the last block of an IP address to protect the identity of your visitors. Optimizely allows you to easily remove the last block of your visitors' IP address before we store results data. This feature is currently available only for iOS, tvOS, and Android projects. To enable IP anonymization, see our Optiverse article.
SDK configuration
You can optionally provide a number of parameters to your Optimizely client to configure how the SDK behaves:
- Event dispatcher: Configure how the SDK sends events to Optimizely
- Logger: Configure how the SDK logs messages when certain events occur
- Error handler: Configure how errors should be handled in your production code
- User profile: Configure what user state is persisted by the SDK
The SDK provides default implementations of event dispatching, logging, and error handling out of the box, but we encourage you to override the default behavior if you have different requirements in your production application. If you'd like to edit the default behavior, refer to the reference implementations in the SDK source code for examples.
Event dispatcher
You can optionally change how the SDK sends events to Optimizely by providing an event dispatcher method. You should provide your own event dispatcher if you have networking requirements that aren't met by our default dispatcher.
The event dispatching function takes an event object with three properties: httpVerb, url, and params, all of which are built out for you in EventBuilder. A POST request should be sent to url with params in the body (be sure to stringify it to JSON) and {content-type: 'application/json'} in the headers.
We also provide another event dispatcher which calls
curl externally to POST data to Optimizely. This event dispatcher has better performance as the process forks and executes in the background, thereby not blocking your application. You need to make sure that you have curl installed on your server to be able to use it. The code for this curl based event dispatcher can be seen here.
By default, our C# SDK uses an asynchronous event dispatcher as shown on the right.
We provide an asynchronous event dispatcher, optimizely-sdk-httpclient, that requires org.apache.httpcomponents:httpclient:4.5.2 and is parameterized by in-memory queue size and number of dispatch worker threads.
To use your own event dispatcher, implement the dispatchEvent method in our EventHandler interface. dispatchEvent takes in a LogEvent object containing all of the information needed to dispatch an event to Optimizely's backend. Events should be sent to LogEvent.getEndpointUrl as a POST request with LogEvent.getBody as the payload and content-type: "application/json" as the header.
The included event handler implementation includes queueing and flushing so will work even if an app is offline. It uses an IntentService to queue up events to dispatch. If they fail to send they are stored in a sqlite database and scheduled to be dispatched later. You will need to implement EventDispatcher to use your own.
The included event handler implementation, OptimizelySDKEventDispatcher, includes queueing and flushing so will work even if an app is offline. You will need to implement OPTLYEventDispatcher to use your own.
The included event handler implementation, OptimizelySDKEventDispatcher, includes queueing and flushing so will work even if an app is offline. You will need to implement OPTLYEventDispatcher to use your own.
Example Code
from .event_dispatcher import EventDispatcher as event_dispatcher // Create an Optimizely client with the default event dispatcher optimizely_client = optimizely.Optimizely(datafile, event_dispatcher=event_dispatcher)
Example Code
import com.optimizely.ab.Optimizely; import com.optimizely.ab.config.parser.ConfigParseException; import com.optimizely.ab.event.AsyncEventHandler; import com.optimizely.ab.event.EventHandler; // Creates an async event handler with a max buffer of // 20,000 events and a single dispatcher thread EventHandler eventHandler = new AsyncEventHandler(20000, 1); Optimizely optimizelyClient; try { // Create an Optimizely client with the default event dispatcher optimizelyClient = Optimizely.builder(datafile, eventHandler).build(); } catch (ConfigParseException e) { // Handle exception gracefully return; }
Example Code
using OptimizelySDK.Event.Dispatcher; // Create an Optimizely client with the default event dispatcher Optimizely OptimizelyClient = new Optimizely( datafile: config.ProjectConfigJson, eventDispatcher: new Event.Dispatcher.DefaultEventDispatcher());
Example Code
EventHandler eventHandler = YourEventHandler.getInstance(); // Create an Optimizely client with your own event handler Optimizely optimizelyClient = Optimizely.builder(datafile, eventHandler).build();
Example Code
# Create an Optimizely client with the default event dispatcher optimizely_client = Optimizely::Project.new(datafile, Optimizely::EventDispatcher.new)
Example Code
var defaultEventDispatcher = require('optimizely-server-sdk/lib/plugins/event_dispatcher'); // Create an Optimizely client with the default event dispatcher var optimizelyClient = optimizely.createInstance({ datafile: datafile, eventDispatcher: defaultEventDispatcher });
Example Code
var defaultEventDispatcher = require('optimizely-client-sdk/lib/plugins/event_dispatcher'); // Create an Optimizely client with the default event dispatcher var optimizelyClientInstance = optimizely.createInstance({ datafile: datafile, eventDispatcher: defaultEventDispatcher });
Example Code
use Optimizely\Event\Dispatcher\DefaultEventDispatcher; // Create an Optimizely client with the default event dispatcher $optimizelyClient = new Optimizely($datafile, new DefaultEventDispatcher());
Example Code
CustomEventDispatcher<OPTLYEventDispatcher> *customEventDispatcher = [[CustomEventDispatcher alloc] init]; // initialize your Manager (settings will propagate to OPTLYClient and Optimizely) OPTLYManager *manager = [OPTLYManager init:^(OPTLYManagerBuilder * _Nullable builder) { builder.projectId = kProjectId; builder.eventDispatcher = eventDispatcher; }];
Example Code
let customEventDispatcher: CustomEventDispatcher = CustomEventDispatcher.init() // initialize your Manager (settings will propagate to OPTLYClient and Optimizely) let manager: OPTLYManager? = OPTLYManager.init({ (builder) in builder!.datafile = datafile builder!.eventDispatcher = customEventDispatcher })
Logger
The logger logs information about your experiments to help you with debugging.
In the Python SDK, logging functionality is not enabled by default. To improve your experience setting up the SDK and configuring your production environment, we recommend that you pass in a logger for your Optimizely client.
You can use our SimpleLogger implementation out of the box, available here.
In the Ruby SDK, logging functionality is not enabled by default. To improve your experience setting up the SDK and configuring your production environment, we recommend that you pass in a logger for your Optimizely client.
You can use our SimpleLogger implementation out of the box, available here.
In the Node SDK, logging functionality is not enabled by default. To improve your experience setting up the SDK and configuring your production environment, we recommend that you pass in a logger for your Optimizely client.
You can use our Logger implementation out of the box, available here.
By default, the JavaScript SDK uses our Logger implementation, available here. You can also implement your own logger and pass it into the builder when creating an Optimizely instance.
It's also possible to configure the log level threshold of the default logger without implementing your own logger. You can do so by passing the constructor an integer logLevel. Possible levels are enumerated here.
In the Java SDK, logging functionality is not enabled by default. To improve your experience setting up the SDK and configuring your production environment, we recommend that you pass in a logger for your Optimizely client.
For the Java SDK, we require the use of an SLF4J implementation.
For the Android SDK, you can use our Android SLF4J logger. Logging verbosity can be controlled via the android-logger.properties file. This file can be placed in src/main/resources. You can also include a copy in src/release/resources with different settings for the build you release to the Play Store.
Read about advanced configuration options here.
OptimizelyLogLevelInfo. You can initialize it with any log level and pass it in when creating an Optimizely instance. You can also implement your own logger and pass it in the builder when creating an Optimizely instance.
OptimizelyLogLevel.Info. You can initialize it with any log level and pass it in when creating an Optimizely instance. You can also implement your own logger and pass it in when creating an Optimizely instance.
The log levels vary slightly between SDKs but generally fall in the following buckets:
| Log level | Description |
|---|---|
CRITICAL |
Events that cause the app to crash are logged. |
ERROR |
Events that prevent experiments from functioning |
| correctly (e.g., invalid datafile in initialization, | |
| invalid experiment keys or event keys) are logged. | |
| The user can take action to correct. | |
WARNING |
Events that don't prevent experiments from functioning |
| correctly, but can have unexpected outcomes (e.g., | |
| future API deprecation, logger or error handler are | |
| not set properly, nil values from getters) are logged. | |
DEBUG |
Any information related to errors that can help us |
| debug the issue (e.g., experiment is not running, user | |
| is not in experiment, the bucket value generated by | |
| our helper method) are logged. | |
INFO |
Events of significance (e.g, activate started, |
| activate succeeded, tracking started, tracking | |
| succeeded) are logged. This is helpful in showing the | |
| lifecycle of an API call. |
Example Code
from .logger import NoOpLogger as logger optimizely_client = optimizely.Optimizely(datafile, event_dispatcher=event_dispatcher, logger=logger)
Example Code
optimizely_client = Optimizely::Project.new(datafile, Optimizely::EventDispatcher.new, Optimizely::NoOpLogger.new)
Example Code
using OptimizelySDK.Logger; ILogger Logger = new Log4NetLogger(); Optimizely OptimizelyClient = new Optimizely( datafile: config.ProjectConfigJson, logger: Logger);
Example Code
var defaultLogger = require('optimizely-server-sdk/lib/plugins/logger'); var optimizelyClient = optimizely.createInstance({ datafile: datafile, eventDispatcher: defaultEventDispatcher, logger: defaultLogger.createLogger(), });
Example Code
var defaultLogger = { log: function(message) { console.log(message); }, }; // custom logger var optimizelyClientInstance = optimizely.createInstance({ datafile: datafile, eventDispatcher: defaultEventDispatcher, logger: defaultLogger, }); // custom logLevel (2, DEBUG) with default logger var optimizelyClientInstance = optimizely.createInstance({ datafile: datafile, logLevel: 2, });
Example Code
use Optimizely\Logger\DefaultLogger; $optimizelyClient = new Optimizely($datafile, null, new DefaultLogger());
Example Code
CustomLogger *customLogger = [[CustomLogger alloc] init]; OPTLYManager *manager = [OPTLYManager init:^(OPTLYManagerBuilder * _Nullable builder) { builder.projectId = kProjectId; builder.logger = customLogger; }]; OPTLYLoggerDefault *optlyLogger = [[OPTLYLoggerDefault alloc] initWithLogLevel:OptimizelyLogLevelAll]; OPTLYManager *manager = [OPTLYManager init:^(OPTLYManagerBuilder * _Nullable builder) { builder.projectId = kProjectId; builder.logger = optlyLogger; }];
Example Code
let customLogger: CustomLogger? = CustomLogger() let manager: OPTLYManager? = OPTLYManager.init({ (builder) in builder!.projectId = projectId builder!.logger = customLogger }) let optlyLogger: OPTLYLoggerDefault? = OPTLYLoggerDefault.init(logLevel: OptimizelyLogLevel.All) let manager: OPTLYManager? = OPTLYManager.init({ (builder) in builder!.projectId = projectId builder!.logger = optlyLogger })
Example Code
# android-logger.properties example # Core logger.com.optimizely.ab.Optimizely=DEBUG:Optly.core logger.com.optimizely.ab.annotations=DEBUG:Optly.annotations logger.com.optimizely.ab.bucketing=DEBUG:Optly.bucketing logger.com.optimizely.ab.config=DEBUG:Optly.config logger.com.optimizely.ab.error=DEBUG:Optly.error logger.com.optimizely.ab.event=DEBUG:Optly.event logger.com.optimizely.ab.internal=DEBUG:Optly.internal # Android logger.com.optimizely.ab.android.sdk=DEBUG:Optly.androidSdk logger.com.optimizely.ab.android.event_handler=DEBUG:Optly.eventHandler logger.com.optimizely.ab.android.shared=DEBUG:Optly.shared logger.com.optimizely.ab.android.user_profile=DEBUG:Optly.userProfile # Disable most SDK logs by commenting all other lines and uncommenting below #logger.com.optimizely.ab=ASSERT:Optly
Error handler
You can provide your own custom error handler logic to standardize across your production environment. This error handler will be called in the following situations:
- Unknown experiment key referenced
- Unknown event key referenced
If the error handler isn’t overridden, a no-op error handler is used by default.
errorHandler property in the OPTLYManagerBuilder object during initialization. The error handler you create must conform to the OPTLYErrorHandler protocol. Overriding this module will allow you to standardize error and exception handling across your application.
errorHandler property in the OPTLYManagerBuilder object during initialization. The error handler you create must conform to the OPTLYErrorHandler protocol. Overriding this module, will allow you to standardize error and exception handling across your application.
Example Code
from .error_handler import NoOpErrorHandler as error_handler optimizely_client = optimizely.Optimizely(datafile, event_dispatcher=event_dispatcher, logger=logger, error_handler=error_handler)
Example Code
import com.optimizely.ab.Optimizely; import com.optimizely.ab.error.ErrorHandler; import com.optimizely.ab.error.RaiseExceptionErrorHandler; import com.optimizely.ab.event.AsyncEventHandler; import com.optimizely.ab.event.EventHandler; EventHandler eventHandler = new AsyncEventHandler(20000, 1); // Default handler that raises exceptions ErrorHandler errorHandler = new RaiseExceptionErrorHandler(); Optimizely optimizelyClient; try { // Create an Optimizely client with the default event dispatcher optimizelyClient = Optimizely.builder(datafile, eventHandler). .withErrorHandler(errorHandler) .build(); } catch (ConfigParseException e) { // Handle exception gracefully return; }
Example Code
using OptimizelySDK.ErrorHandler; Optimizely OptimizelyClient = new Optimizely( datafile: config.ProjectConfigJson, errorHandler: new DefaultErrorHandler());
Example Code
// Event handler is required to make Optimizely EventHandler eventHandler = YourEventHandler.getInstance(); // You can optionally provide an error handler ErrorHandler errorHandler = YourErrorHandler.getInstance(); Optimizely optimizelyClient = Optimizely.builder(datafile, eventHandler) .withErrorHandler(errorHandler) .build();
Example Code
optimizely_client = Optimizely::Project.new(datafile, Optimizely::EventDispatcher.new, Optimizely::NoOpLogger.new, Optimizely::NoOpErrorHandler.new)
Example Code
var defaultErrorHandler = require('optimizely-server-sdk/lib/plugins/error_handler'); optimizelyClient = optimizely.createInstance({ datafile: datafile, errorHandler: defaultErrorHandler, eventDispatcher: defaultEventDispatcher, logger: defaultLogger.createLogger(), });
Example Code
var defaultErrorHandler = require('optimizely-server-sdk/lib/plugins/error_handler'); var optimizelyClientInstance = optimizely.createInstance({ datafile: datafile, errorHandler: defaultErrorHandler, eventDispatcher: defaultEventDispatcher, logger: defaultLogger.createLogger(), });
Example Code
use Optimizely\ErrorHandler\DefaultErrorHandler; $optimizelyClient = new Optimizely($datafile, null, null, new DefaultErrorHandler());
Example Code
CustomErrorHandler *customErrorHandler = [[CustomErrorHandler alloc] init]; OPTLYManager *manager = [OPTLYManager init:^(OPTLYManagerBuilder * _Nullable builder) { builder.projectId = kProjectId; builder.errorHandler = customErrorHandler; }];
Example Code
let customErrorHandler: CustomErrorHandler? = CustomErrorHandler() let manager: OPTLYManager? = OPTLYManager.init({ (builder) in builder!.projectId = projectId builder!.errorHandler = customErrorHandler })
User profile
Use a user profile to persist information about your users and ensure variation assignments are sticky. For example, if you are working on a backend website, you can create an implementation that reads and saves user profiles from a Redis or memcached store.
Implementing a user profile service is optional and is only necessary if you want to keep variation assignments sticky even when experiment conditions are changed while it is running (e.g. audiences, attributes, variation pausing, traffic distribution). For more information on user profiles and how they used by the SDK, see this Optiverse article.
To use, you must provide an implementation of the UserProfileService that exposes two functions with the following signatures below.
- lookup: Takes a
user_idstring and returns a user profile dict. - save: Takes a
user_profiledict and persists it.
The following is the JSON schema for the user profile map:
{
"title": "UserProfile",
"type": "object",
"properties": {
"user_id": {"type": "string"},
"experiment_bucket_map": {"type": "object",
"patternProperties": {
"^[a-zA-Z0-9]+$": {"type": "object",
"properties": {"variation_id": {"type":"string"}},
"required": ["variation_id"]}
},
}
},
"required": ["user_id", "experiment_bucket_map"]
}
The SDK uses the user profile service you provide to override Optimizely's default bucketing behavior in cases when an experiment assignment has been saved.
When implementing your own UserProfileService we recommend loading the user profiles into the user profile service on initialization and avoiding performing expensive, blocking lookups on the lookup function to minimize the performance impact of incorporating the UserProfileService.
To use, you must provide an implementation of the UserProfileService that exposes two functions, lookup and save, with the following signatures.
- lookup: Takes a string
userIdand returns the corresponding user profile Hash. - save: Takes a Hash user profile and persists it.
The following is the JSON schema for the user profile map:
{
"title": "UserProfile",
"type": "object",
"properties": {
"user_id": {"type": "string"},
"experiment_bucket_map": {"type": "object",
"patternProperties": {
"^[a-zA-Z0-9]+$": {"type": "object",
"properties": {"variation_id": {"type":"string"}},
"required": ["variation_id"]}
},
}
},
"required": ["user_id", "experiment_bucket_map"]
}
To use, you must provide an implementation of the UserProfileService that exposes two functions with the following signatures below.
- lookup: Takes a
userIdstring and returns a user profile map. - save: Takes a user profile map and persists it.
The following is the JSON schema for the user profile map:
{
"title": "UserProfile",
"type": "object",
"properties": {
"user_id": {"type": "string"},
"experiment_bucket_map": {"type": "object",
"patternProperties": {
"^[a-zA-Z0-9]+$": {"type": "object",
"properties": {"variation_id": {"type":"string"}},
"required": ["variation_id"]}
},
}
},
"required": ["user_id", "experiment_bucket_map"]
}
The SDK uses the user profile service you provide to override Optimizely's default bucketing behavior in cases when an experiment assignment has been saved.
When implementing your own UserProfileService we recommend loading the user profiles into the user profile service on initialization and avoiding performing expensive, blocking lookups on the lookup function to minimize the performance impact of incorporating the UserProfileService.
To use, you must provide an implementation of the UserProfileService that exposes two functions with the following signatures below.
- lookup: Takes a
userIdstring and returns a user profile object. - save: Takes a user profile object and persists it.
The following is the JSON schema for the user profile object:
{
"title": "UserProfile",
"type": "object",
"properties": {
"user_id": {"type": "string"},
"experiment_bucket_map": {"type": "object",
"patternProperties": {
"^[a-zA-Z0-9]+$": {"type": "object",
"properties": {"variation_id": {"type":"string"}},
"required": ["variation_id"]}
},
}
},
"required": ["user_id", "experiment_bucket_map"]
}
The SDK uses the user profile service you provide to override Optimizely's default bucketing behavior in cases when an experiment assignment has been saved.
When implementing your own UserProfileService we recommend loading the user profiles into the user profile service on initialization and avoiding performing expensive, blocking lookups on the lookup function to minimize the performance impact of incorporating the UserProfileService.
To use, you must provide an implementation of the UserProfileService that exposes two functions with the following signatures below.
- lookup: Takes a
$userIdstring and returns a user profile map. - save: Takes a
$userProfileMapand persists it.
The following is the JSON schema for the user profile map:
{
"title": "UserProfile",
"type": "object",
"properties": {
"user_id": {"type": "string"},
"experiment_bucket_map": {"type": "object",
"patternProperties": {
"^[a-zA-Z0-9]+$": {"type": "object",
"properties": {"variation_id": {"type":"string"}},
"required": ["variation_id"]}
},
}
},
"required": ["user_id", "experiment_bucket_map"]
}
The SDK uses the user profile service you provide to override Optimizely's default bucketing behavior in cases when an experiment assignment has been saved.
When implementing your own UserProfileService we recommend loading the user profiles into the user profile service on initialization and avoiding performing expensive, blocking lookups on the lookup function to minimize the performance impact of incorporating the UserProfileService.
The UserProfile class can be used for persisting information about your users in a local profile. You can provide your own UserProfile implementation if you’d like to change what user information is persisted or how data is stored on the device. The code at right shows how to include your own user profile implementation when constructing an Optimizely client.
Our default UserProfile implementation, AndroidUserProfile, stores data on which experiments and variations have historically been activated for a user. The Optimizely client uses the supplied user profile to ensure that experiment bucketing is sticky, i.e. the user will always be exposed to the same variation on subsequent sessions in the app, even in the case where the experiment is paused or traffic allocation is changed. We plan to expand our implementation in future to include more information about users, including user IDs and attributes.
The UserProfile interface contains the following methods:
- save: Takes a
userId,experimentId, andvariationIdand saves this information on the device. - lookup: Takes a
userIdandexperimentId, and returns thevariationIdthat was recorded previously if it exists. - remove: Takes a
userIdandexperimentId, and deletes the mapping to the variation if it exists.
For full details, check out our user profile interface here and our default implementation here.
The OPTLYUserProfile class can be used for persisting information about your users in a local profile. You can provide your own OPTLYUserProfile implementation if you’d like to change what user information is persisted or how data is stored on the device. The code at right shows how to include your own user profile implementation when constructing an Optimizely client.
Our default OPTLYUserProfile implementation, OptimizelySDKUserProfile, stores data on which experiments and variations have historically been activated for a user. The Optimizely client uses the supplied user profile to ensure that experiment bucketing is sticky, i.e. the user will always be exposed to the same variation on subsequent sessions in the app, even in the case where the experiment is paused or traffic allocation is changed. We plan to expand our implementation in future to include more information about users, including user IDs and attributes.
The OPTLYUserProfile interface contains the following methods:
- saveUser: Takes a
userId,experimentId, andvariationIdand saves this information on the device. - getVariationForUser: Takes a
userIdandexperimentId, and returns thevariationIdthat was recorded previously if it exists. - removeUser: Takes a
userIdandexperimentId, and deletes the mapping to the variation if it exists.
For full details, check out our user profile interface here and our default implementation here.
The OPTLYUserProfile class can be used for persisting information about your users in a local profile. You can provide your own OPTLYUserProfile implementation if you’d like to change what user information is persisted or how data is stored on the device. The code at right shows how to include your own user profile implementation when constructing an Optimizely client.
Our default OPTLYUserProfile implementation, OptimizelySDKUserProfile, stores data on which experiments and variations have historically been activated for a user. The Optimizely client uses the supplied user profile to ensure that experiment bucketing is sticky, i.e. the user will always be exposed to the same variation on subsequent sessions in the app, even in the case where the experiment is paused or traffic allocation is changed. We plan to expand our implementation in future to include more information about users, including user IDs and attributes.
The OPTLYUserProfile interface contains the following methods:
- saveUser: Takes a
userId,experimentId, andvariationIdand saves this information on the device. - getVariationForUser: Takes a
userIdandexperimentId, and returns thevariationIdthat was recorded previously if it exists. - removeUser: Takes a
userIdandexperimentId, and deletes the mapping to the variation if it exists.
For full details, check out our user profile interface here and our default implementation here.
Example Code
// Event handler is required to make Optimizely EventHandler eventHandler = YourEventHandler.getInstance(); // You can optionally provide a user profile UserProfile userProfile = UserProfile.getInstance(); Optimizely optimizelyClient = Optimizely.builder(datafile, eventHandler) .withUserProfile(userProfile) .build();
Example Code
var optimizely = require('optimizely-client-sdk'); // Sample user profile service implementation var userProfileService = { lookup: function(userId) { // perform user profile lookup }, save: function(userProfileMap) { // persist user profile } }; var datafile; // your project's datafile var optimizelyClient = optimizely.createInstance({ datafile: datafile, userProfileService: userProfileService, });
Example Code
var optimizely = require('optimizely-server-sdk'); // Sample user profile service implementation var userProfileService = { lookup: function(userId) { // perform user profile lookup }, save: function(userProfileMap) { // persist user profile } }; var datafile; // your project's datafile var optimizelyClient = optimizely.createInstance({ datafile: datafile, userProfileService: userProfileService, });
Example Code
CustomUserProfile<OPTLYUserProfile> *customUserProfile = [[CustomUserProfile alloc] init]; // initialize your Manager (settings will propagate to OPTLYClient and Optimizely) OPTLYManager *manager = [OPTLYManager init:^(OPTLYManagerBuilder * _Nullable builder) { builder.projectId = kProjectId; builder.userProfile = userProfile; }];
Example Code
use Optimizely\Logger\DefaultLogger; use Optimizely\UserProfile\UserProfileServiceInterface; use Optimizely\Optimizely; class UserProfileService implements UserProfileServiceInterface { public function lookup($userId) { // perform user profile lookup } public function save($userProfileMap) { // persist user profile } } $datafile = {}; // your project's datafile $optimizelyClient = new Optimizely( $datafile, null, new DefaultLogger(), null, false, new UserProfileService() );
Example Code
from optimizely import user_profile class MyUserProfileService(user_profile.UserProfileService): def lookup(self, user_id): # Retrieve and return user profile def save(self, user_profile): # Save user profile optimizely_client = optimizely.Optimizely(datafile, user_profile_service=MyUserProfileService())
Example Code
# Sample user profile service implementation class UserProfileService def lookup(user_id) # retrieve user profile end def save(user_profile) # save user profile end end optimizely_client = Optimizely::Project.new(datafile, Optimizely::EventDispatcher.new, Optimizely::NoOpLogger.new, nil, false, UserProfileService.new)
Example Code
let customUserProfile: CustomUserProfile? = CustomUserProfile.init() // initialize your Manager (settings will propagate to OPTLYClient and Optimizely) let manager: OPTLYManager? = OPTLYManager.init({ (builder) in builder!.projectId = projectId builder!.userProfile = customUserProfile })
Debugging
You can plug in a logger to assist with debugging. In addition, below are options for debugging with the SDK.
Notification Listeners
You can use notification listeners to be notified when various Optimizely X SDK events occur.
To do this, create a listener instance that extends NotificationListener and override the callback methods you would like to use. Then register the listener instance with the SDK.
NotificationListener is an abstract class with empty callback methods. It is intended to be extended with functionality before use.
The available callback methods are:
public void onEventTracked(String eventKey, String userId, Map<String, String> attributes, Long eventValue, LogEvent logEvent)public void onExperimentActivated(Experiment experiment, String userId, Map<String, String> attributes, Variation varation)
We are working on providing documentation for listeners on iOS and will publish shortly.
We are working on providing documentation for listeners on iOS and will publish shortly.
To do this, create a listener instance that extends NotificationListener and override the callback methods you would like to use. Then register the listener instance with the SDK.
NotificationListener is an abstract class with empty callback methods. It is intended to be extended with functionality before use.
The available callback methods are:
public void onEventTracked(String eventKey, String userId, Map<String, String> attributes, Long eventValue, LogEvent logEvent)public void onExperimentActivated(Experiment experiment, String userId, Map<String, String> attributes, Variation varation)
Example Code
import com.optimizely.ab.notification.NotificationListener; // Create a new listener and override a method NotificationListener listener = new NotificationListener() { @Override public void onExperimentActivated(Experiment experiment, String userId, Map<String, String> attributes, Variation variation) { Log.i(TAG, "Optimizely experiment activated."); } }; // Add a listener Optimizely optimizelyClient = optimizelyManager.getOptimizely(); optimizelyClient.addNotificationListener(listener); // Remove a listener optimizelyClient.removeNotificationListener(listener); // Clear all listeners optimizelyClient.clearNotificationListeners();
Example Code
import com.optimizely.ab.notification.NotificationListener; // Create a new listener and override methods NotificationListener listener = new NotificationListener() { @Override public void onEventTracked(String eventKey, String userId, Map<String, String> attributes, Long eventValue, LogEvent logEvent) { Log.i(TAG, "Optimizely event tracked."); } @Override public void onExperimentActivated(Experiment experiment, String userId, Map<String, String> attributes, Variation variation) { Log.i(TAG, "Optimizely experiment activated."); } }; // Add a listener Optimizely optimizelyClient = optimizelyManager.getOptimizely(); optimizelyClient.addNotificationListener(listener); // Remove a listener optimizelyClient.removeNotificationListener(listener); // Clear all listeners optimizelyClient.clearNotificationListeners();
Integrations
You can add Optimizely experiment information to third-party analytics platforms using Notification Listeners. Measure the impact of your experiments by segmenting your analytics reports using Optimizely experiment and variation keys or IDs.
Below you will find suggested implementations for some common analytics platforms including Amplitude, Google Analytics, Localytics, Mixpanel, and Segment. You can use them as presented or adapt them to meet your specific needs.
Notification Listeners also allow for the flexibility to implement an integration with a platform that is not listed here.
Experiment and Variation Identifiers
Experiment keys are unique within an Optimizely project, and variation keys are unique within an experiment. However, experiment and variation keys are not guaranteed to be universally unique since they are user generated. This may become a problem in your analytics reports if you use the same key in multiple Optimizely projects or rename your keys.
For human friendly strings when absolute uniqueness is not required, use keys. If you need to uniquely identify experiments and variations in a way that will not change when you rename keys, you can use the automatically generated IDs.
Keys can be accessed on Experiment and Variation objects using getKey(), and IDs can be accessed using getId().
Keys and IDs can be accessed on OPTLYExperiment objects using experimentKey and experimentId respectively, and likewise keys and IDs can be accessed on OPTLYVariation objects using variationKey and variationId.
Keys and IDs can be accessed on OPTLYExperiment objects using experimentKey and experimentId respectively, and likewise keys and IDs can be accessed on OPTLYVariation objects using variationKey and variationId.
Keys can be accessed on Experiment and Variation objects using getKey(), and IDs can be accessed using getId().
Amplitude
Read the Amplitude Quick Start Guide if you're just getting started.
Amplitude features two types of properties that can be used to enrich and segment their reports with Optimizely experiment information: Event Properties and User Properties.
In this suggested integration we will incorporate user properties. User properties are meant to describe a user across all events that are logged. So once a user property is set, Amplitude will apply it in their backend to all events that are recorded afterwards until the property is changed.
Event properties are designed to describe a single event, so they must be added to each event that is logged. The Amplitude SDK and backend do not automatically add event properties to future events for us. For this reason, using user properties is simpler than using event properties for this integration.
In the example code, we add an observer to OptimizelyDidActivateExperimentNotification. In the callback, we use Amplitude's AMPIdentify interface to set a new user property which includes the experiment key and the variation key. The user property should immediately propagate to the Amplitude backend and be visible in their dashboard. Create a new segment for each variation to compare their performances against each other in an experiment.
Next in the callback we can optionally log an impression event that signifies that the Optimizely experiment was activated for the current user. We can later use this event or another event you may already be tracking to calculate a conversion rate.
Compare Results
When comparing numbers between Optimizely and Amplitude results, remember to apply a date filter in Amplitude to correspond with the dates your Optimizely experiment was running. User properties will remain set in Amplitude even after your Optimizely experiment has stopped running.
Alternative Solution
An alternative to the above suggestion is to use Amplitude's Behavioral Cohort Analysis which can segment based on the impression event we track in the example code. However this feature requires an Enterprise plan to use.
Read the Amplitude Quick Start Guide if you're just getting started.
Amplitude features two types of properties that can be used to enrich and segment their reports with Optimizely experiment information: Event Properties and User Properties.
In this suggested integration we will incorporate user properties. User properties are meant to describe a user across all events that are logged. So once a user property is set, Amplitude will apply it in their backend to all events that are recorded afterwards until the property is changed.
Event properties are designed to describe a single event, so they must be added to each event that is logged. The Amplitude SDK and backend do not automatically add event properties to future events for us. For this reason, using user properties is simpler than using event properties for this integration.
In the example code, we add an observer to OptimizelyDidActivateExperimentNotification. In the callback, we use Amplitude's AMPIdentify interface to set a new user property which includes the experiment key and the variation key. The user property should immediately propagate to the Amplitude backend and be visible in their dashboard. Create a new segment for each variation to compare their performances against each other in an experiment.
Next in the callback we can optionally log an impression event that signifies that the Optimizely experiment was activated for the current user. We can later use this event or another event you may already be tracking to calculate a conversion rate.
Compare Results
When comparing numbers between Optimizely and Amplitude results, remember to apply a date filter in Amplitude to correspond with the dates your Optimizely experiment was running. User properties will remain set in Amplitude even after your Optimizely experiment has stopped running.
Alternative Solution
An alternative to the above suggestion is to use Amplitude's Behavioral Cohort Analysis which can segment based on the impression event we track in the example code. However this feature requires an Enterprise plan to use.
Read the Amplitude Quick Start Guide if you're just getting started.
Amplitude features two types of properties that can be used to enrich and segment their reports with Optimizely experiment information: Event Properties and User Properties.
In this suggested integration we will incorporate user properties. User properties are meant to describe a user across all events that are logged. So once a user property is set, Amplitude will apply it in their backend to all events that are recorded afterwards until the property is changed.
Event properties are designed to describe a single event, so they must be added to each event that is logged. The Amplitude SDK and backend do not automatically add event properties to future events for us. For this reason, using user properties is simpler than using event properties for this integration.
In the example code, we extend the class NotificationListener and override the onExperimentActivated() callback. In the callback, we use Amplitude's Identify interface to set a new user property which includes the experiment key and the variation key. The user property should immediately propagate to the Amplitude backend and be visible in their dashboard. Create a new segment for each variation to compare their performances against each other in an experiment.
Next in the callback we can optionally log an impression event that signifies that the Optimizely experiment was activated for the current user. We can later use this event or another event you may already be tracking to calculate a conversion rate.
At the end of the example code, we add our listener to the Optimizely SDK.
Compare Results
When comparing numbers between Optimizely and Amplitude results, remember to apply a date filter in Amplitude to correspond with the dates your Optimizely experiment was running. User properties will remain set in Amplitude even after your Optimizely experiment has stopped running.
Alternative Solution
An alternative to the above suggestion is to use Amplitude's Behavioral Cohort Analysis which can segment based on the impression event we track in the example code. However this feature requires an Enterprise plan to use.
Example Code
#import "Amplitude.h" #import "AMPIdentify.h" [[NSNotificationCenter defaultCenter] addObserverForName:OptimizelyDidActivateExperimentNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { // Set "user property" for the user OPTLYExperiment *experiment = note.userInfo[OptimizelyNotificationsUserDictionaryExperimentKey]; OPTLYVariation *variation = note.userInfo[OptimizelyNotificationsUserDictionaryVariationKey]; NSString *propertyKey = [NSString stringWithFormat:@"[Optimizely] %@", experiment.experimentKey]; AMPIdentify *identify = [[AMPIdentify identify] set:propertyKey value:variation.variationKey]; [[Amplitude instance] identify:identify]; // Track impression event (optional) NSString *eventIdentifier = [NSString stringWithFormat:@"[Optimizely] %@ - %@", experiment.experimentKey, variation.variationKey]; [[Amplitude instance] logEvent:eventIdentifier]; }];
Example Code
import Amplitude_iOS let defaultNotificationCenter = NotificationCenter.default defaultNotificationCenter.addObserver( forName: NSNotification.Name("OptimizelyExperimentActivated"), object: nil, queue: nil) { (note) in let userInfo : Dictionary<String, AnyObject>? = note.userInfo as! Dictionary<String, AnyObject>? if let experiment = userInfo?["experiment"] as! OPTLYExperiment? { if let variation = userInfo?["variation"] as! OPTLYVariation? { // Set "user property" for the user let propertyKey : String! = "[Optimizely] " + experiment.experimentKey let identify : AMPIdentify = AMPIdentify() identify.set(propertyKey, value:variation.variationKey as NSObject!) // Track impression event (optional) let eventIdentifier : String = "[Optimizely] " + experiment.experimentKey + " - " + variation.variationKey Amplitude.instance().logEvent(eventIdentifier) } } }
Example Code
import com.amplitude.api.Amplitude; import com.amplitude.api.AmplitudeClient; import com.amplitude.api.Identify; import com.optimizely.ab.config.Experiment; import com.optimizely.ab.config.Variation; import com.optimizely.ab.notification.NotificationListener; NotificationListener listener = new NotificationListener() { @Override public void onExperimentActivated(Experiment experiment, String userId, Map<String, String> attributes, Variation variation) { String experimentKey = experiment.getKey(); String variationKey = variation.getKey(); // Set "user property" for the user Identify identify = new Identify().set( "[Optimizely] " + experimentKey, variationKey); AmplitudeClient amplitudeClient = Amplitude.getInstance(); amplitudeClient.identify(identify); // Track impression event (optional) amplitudeClient.logEvent( "[Optimizely] " + experimentKey + " - " + variationKey); } }; // Add listener OptimizelyClient optimizelyClient = optimizelyManager.getOptimizely(); optimizelyClient.addNotificationListener(listener);
Google Analytics
Read the Google Analytics iOS Setup instructions if you're just getting started.
For our suggested integration with Google Analytics, we will use Events to track Optimizely experiment activations and build Segments for each variation in the experiment. For more details on alternative solutions, see below.
In the example code, we add an observer to OptimizelyDidActivateExperimentNotification. When the callback is called, we create and send an event containing the experiment key and variation key.
The Google Analytics iOS SDK does not support non-interaction events at this time, so your bounce rate may be affected.
The next step is to build segments for each variation of your experiment. In the segment-builder user interface, look under "Advanced" and add a new Condition. Select to filter "Users" by "Event Action" and "Event Label", enter a name for the segment and hit Save.
Finally, build a report of metrics you are interested in.
Compare Results
When comparing numbers between Optimizely and Google Analytics results, remember to apply a date filter in Google Analytics to correspond with the dates your Optimizely experiment was running.
Alternative Solution
Google offers Custom dimensions as another way to "include non-standard data in your reports." Custom dimensions are the replacement for custom variables from older versions of Google Analytics.
Our suggested integration does not use custom dimensions because free Google Analytics accounts are limited to 20 indices and 360 accounts are limited to 200 indices. Indices may also be referred to as slots. Since the value stored by a custom dimension may not exceed 150 bytes, we would need to dedicate an index for each Optimizely experiment. Because Google recommends they not be reused, the number of available indices is essentially the upper limit of how many Optimizely experiments can ever be tracked and segmented by custom dimensions.
Read the Google Analytics iOS Setup instructions if you're just getting started.
For our suggested integration with Google Analytics, we will use Events to track Optimizely experiment activations and build Segments for each variation in the experiment. For more details on alternative solutions, see below.
In the example code, we add an observer to OptimizelyDidActivateExperimentNotification. When the callback is called, we create and send an event containing the experiment key and variation key.
The Google Analytics iOS SDK does not support non-interaction events at this time, so your bounce rate may be affected.
The next step is to build segments for each variation of your experiment. In the segment-builder user interface, look under "Advanced" and add a new Condition. Select to filter "Users" by "Event Action" and "Event Label", enter a name for the segment and hit Save.
Finally, build a report of metrics you are interested in.
Compare Results
When comparing numbers between Optimizely and Google Analytics results, remember to apply a date filter in Google Analytics to correspond with the dates your Optimizely experiment was running.
Alternative Solution
Google offers Custom dimensions as another way to "include non-standard data in your reports." Custom dimensions are the replacement for custom variables from older versions of Google Analytics.
Our suggested integration does not use custom dimensions because free Google Analytics accounts are limited to 20 indices and 360 accounts are limited to 200 indices. Indices may also be referred to as slots. Since the value stored by a custom dimension may not exceed 150 bytes, we would need to dedicate an index for each Optimizely experiment. Because Google recommends they not be reused, the number of available indices is essentially the upper limit of how many Optimizely experiments can ever be tracked and segmented by custom dimensions.
Read the Google Analytics Android Setup instructions if you're just getting started.
For our suggested integration with Google Analytics, we will use Events to track Optimizely experiment activations and build Segments for each variation in the experiment. For more details on alternative solutions, see below.
In the example code, we extend the class NotificationListener and override the onExperimentActivated() callback to track experiment activations. When the callback is called, we create and send an event containing the experiment key and variation key. Since this should not affect the bounce rate, we mark it as a non-interaction event.
Read more about non-interaction events and support for them in the Google Analytics Android SDK.
At the end of the example code, we add our listener to the Optimizely SDK.
The next step is to build segments for each variation of your experiment. In the segment-builder user interface, look under "Advanced" and add a new Condition. Select to filter "Users" by "Event Action" and "Event Label", enter a name for the segment and hit Save.
Finally, build a report of metrics you are interested in.
Compare Results
When comparing numbers between Optimizely and Google Analytics results, remember to apply a date filter in Google Analytics to correspond with the dates your Optimizely experiment was running.
Alternative Solution
Google offers Custom dimensions as another way to "include non-standard data in your reports." Custom dimensions are the replacement for custom variables from older versions of Google Analytics.
Our suggested integration does not use custom dimensions because free Google Analytics accounts are limited to 20 indices and 360 accounts are limited to 200 indices. Indices may also be referred to as slots. Since the value stored by a custom dimension may not exceed 150 bytes, we would need to dedicate an index for each Optimizely experiment. Because Google recommends they not be reused, the number of available indices is essentially the upper limit of how many Optimizely experiments can ever be tracked and segmented by custom dimensions.
Example Code
#import <Google/Analytics.h> [[NSNotificationCenter defaultCenter] addObserverForName:OptimizelyDidActivateExperimentNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { // Google Analytics tracker id<GAITracker> tracker = [GAI sharedInstance].defaultTracker; // Build and send an Event OPTLYExperiment *experiment = note.userInfo[OptimizelyNotificationsUserDictionaryExperimentKey]; OPTLYVariation *variation = note.userInfo[OptimizelyNotificationsUserDictionaryVariationKey]; NSString *action = [NSString stringWithFormat:@"Experiment - %@", experiment.experimentKey]; NSString *label = [NSString stringWithFormat:@"Variation - %@", variation.variationKey]; [tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"Optimizely" action:action label:label value:nil] build]]; }];
Example Code
let defaultNotificationCenter = NotificationCenter.default defaultNotificationCenter.addObserver( forName: NSNotification.Name("OptimizelyExperimentActivated"), object: nil, queue: nil) { (note) in let userInfo : Dictionary<String, AnyObject>? = note.userInfo as! Dictionary<String, AnyObject>? if let experiment = userInfo?["experiment"] as! OPTLYExperiment? { if let variation = userInfo?["variation"] as! OPTLYVariation? { // Google Analytics tracker let tracker : GAITracker? = GAI.sharedInstance().defaultTracker let action : String = "Experiment - " + experiment.experimentKey let label : String = "Variation - " + variation.variationKey // Build and send an Event let builder = GAIDictionaryBuilder.createEvent( withCategory: "Optimizely", action: action, label: label, value: nil).build() tracker?.send(builder as [NSObject : AnyObject]!) } } }
Example Code
import com.google.android.gms.analytics.GoogleAnalytics; import com.google.android.gms.analytics.HitBuilders; import com.google.android.gms.analytics.Tracker; import com.optimizely.ab.config.Experiment; import com.optimizely.ab.config.Variation; import com.optimizely.ab.notification.NotificationListener; NotificationListener listener = new NotificationListener() { @Override public void onExperimentActivated(Experiment experiment, String userId, Map<String, String> attributes, Variation variation) { String experimentKey = experiment.getKey(); String variationKey = variation.getKey(); // Google Analytics tracker SampleApp application = (SampleApp) getApplication(); mTracker = application.getDefaultTracker(); // Build and send a non-interaction Event mTracker.send(new HitBuilders.EventBuilder() .setCategory("Optimizely") .setAction("Experiment - " + experimentKey) .setLabel("Variation - " + variationKey) .setNonInteraction(true) .build()); } }; // Add listener OptimizelyClient optimizelyClient = optimizelyManager.getOptimizely(); optimizelyClient.addNotificationListener(listener);
Localytics
Read the Localytics Getting Started Guide if you're just getting started.
Localytics has a couple of ways that can be used to capture Optimizely experiment information. We will demonstrate the use of Custom Events with Attributes in this suggested integration. For more details on alternative solutions, see below.
This example involves two parts:
- Add an observer for
OptimizelyDidTrackEventNotificationto wrap[Localytics tagEvent] - Add
[optimizely track]to track conversions
Instead of calling [Localytics tagEvent] directly, we will wrap the calls with [optimizely track] so Optimizely bucketing information is included as event attributes.
The example code demonstrates how to add an observer to OptimizelyDidTrackEventNotification. Each time an event is tracked using [optimizely track], we add a mapping of experiment key to variation key to the event attributes and pass it to [Localytics tagEvent].
The last step is to add [optimizely track] to the application logic to track when a conversion event has occured.
Compare Results
When comparing numbers between Optimizely and Localytics results, remember to apply a date filter in Localytics to correspond with the dates your Optimizely experiment was running.
Consistent User Identity
Maintaining a consistent user identity across multiple sessions and devices can help ensure proper reporting. Localytics provides some guidelines for their platform.
We recommend using the same user ID with the following methods:
[optimizely activate][Localytics setCustomerId]
Alternative Solution
Another solution is to set Localytics' Custom Dimensions using an OptimizelyDidActivateExperimentNotification observer. Custom dimensions can be used to segment users without needing to wrap [Localytics tagEvent] but requires configuration in the Localytics dashboard for each Optimizely experiment.
Read the Localytics Getting Started Guide if you're just getting started.
Localytics has a couple of ways that can be used to capture Optimizely experiment information. We will demonstrate the use of Custom Events with Attributes in this suggested integration. For more details on alternative solutions, see below.
This example involves two parts:
- Add an observer for
OptimizelyDidTrackEventNotificationto wrapLocalytics.tagEvent() - Add
optimizely.track()to track conversions
Instead of calling Localytics.tagEvent() directly, we will wrap the calls with optimizely.track() so Optimizely bucketing information is included as event attributes.
The example code demonstrates how to add an observer to OptimizelyDidTrackEventNotification. Each time an event is tracked using optimizely.track(), we add a mapping of experiment key to variation key to the event attributes and pass it to Localytics.tagEvent().
The last step is to add optimizely.track() to the application logic to track when a conversion event has occured.
Compare Results
When comparing numbers between Optimizely and Localytics results, remember to apply a date filter in Localytics to correspond with the dates your Optimizely experiment was running.
Consistent User Identity
Maintaining a consistent user identity across multiple sessions and devices can help ensure proper reporting. Localytics provides some guidelines for their platform.
We recommend using the same user ID with the following methods:
optimizely.activate()Localytics.setCustomerId()
Alternative Solution
Another solution is to set Localytics' Custom Dimensions using an OptimizelyDidActivateExperimentNotification observer. Custom dimensions can be used to segment users without needing to wrap Localytics.tagEvent() but requires configuration in the Localytics dashboard for each Optimizely experiment.
Read the Localytics Getting Started Guide if you're just getting started.
Localytics has a couple of ways that can be used to capture Optimizely experiment information. We will demonstrate the use of Custom Events with Attributes in this suggested integration. For more details on alternative solutions, see below.
This example involves two parts:
- Add a listener for
onEventTrackedto wrapLocalytics.tagEvent() - Add
OptimizelyClient.track()to track conversions
Instead of calling Localytics.tagEvent() directly, we will wrap the calls with OptimizelyClient.track() so Optimizely bucketing information is included as event attributes.
The example code demonstrates how to add an onEventTracked listener by extending the class NotificationListener. Each time an event is tracked using OptimizelyClient.track(), we retrieve a mapping of experiment key to variation key from UserProfile which records bucketing decisions. Then we call Localytics.tagEvent() and include the bucketing map in the attributes.
Next, we add our listener to the Optimizely SDK.
The last step is to add OptimizelyClient.track() to the application logic to track when a conversion event has occured.
Compare Results
When comparing numbers between Optimizely and Localytics results, remember to apply a date filter in Localytics to correspond with the dates your Optimizely experiment was running.
Consistent User Identity
Maintaining a consistent user identity across multiple sessions and devices can help ensure proper reporting. Localytics provides some guidelines for their platform.
We recommend using the same user ID with the following methods:
optimizelyClient.activate()Localytics.setCustomerId()
Alternative Solution
Another solution is to set Localytics' Custom Dimensions using an onExperimentActivated listener. Custom dimensions can be used to segment users without needing to wrap Localytics.tagEvent() but requires configuration in the Localytics dashboard for each Optimizely experiment.
Example Code
@import Localytics; [[NSNotificationCenter defaultCenter] addObserverForName:OptimizelyDidTrackEventNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { // Set event attributes NSMutableDictionary *attributes = [NSMutableDictionary new]; id userDictionary = note.userInfo[OptimizelyNotificationsUserDictionaryAttributesKey]; if (!!userDictionary) { [attributes addEntriesFromDictionary:userDictionary]; } [note.userInfo[OptimizelyNotificationsUserDictionaryExperimentVariationMappingKey] enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { [attributes setValue:((OPTLYExperiment *)key).experimentKey forKey:((OPTLYVariation *) obj).variationKey]; }]; // Tag custom event with attributes NSString *eventIdentifier = [NSString stringWithFormat:@"[Optimizely] %@", note.userInfo[OptimizelyNotificationsUserDictionaryEventNameKey]]; [Localytics tagEvent:eventIdentifier attributes:attributes]; }]; // Track a conversion event for the provided user [optimizely track:eventKey userId:userId];
Example Code
import Localytics let defaultNotificationCenter = NotificationCenter.default defaultNotificationCenter.addObserver( forName: NSNotification.Name("OptimizelyEventTracked"), object: nil, queue: nil) { (note) in let userInfo : Dictionary<String, AnyObject>? = note.userInfo as! Dictionary<String, AnyObject>? let userAttributes : Dictionary<String, AnyObject>? = userInfo?["attributes"] as! Dictionary<String, AnyObject>? let attributes : NSMutableDictionary = [:] attributes.addEntries(from: userAttributes!) // Set event attributes if let userExperimentVariationMapping = userInfo?["ExperimentVariationMapping"] as! Dictionary<String, AnyObject>? { for (key,value) in userExperimentVariationMapping { let variation : OPTLYVariation = value as! OPTLYVariation attributes.setValue(key, forKey:variation.variationKey) } } // Tag custom event with attributes let event : String = userInfo!["eventKey"] as! String let localyticsEventIdentifier : String = "[Optimizely] " + event Localytics.tagEvent(localyticsEventIdentifier) }
Example Code
import com.localytics.android.Localytics; import com.optimizely.ab.bucketing.UserProfile; import com.optimizely.ab.notification.NotificationListener; import java.util.Map; NotificationListener listener = new NotificationListener() { @Override public void onEventTracked(String eventKey, String userId, Map<String, String> attributes, Long eventValue, LogEvent logEvent) { // Make a copy of attributes because it could be immutable attributes = new HashMap<>(attributes); // Retrieve mapping of experiments to variations UserProfile userProfile = optimizelyManager.getUserProfile(); Map<String, Map<String, String>> allRecords = userProfile.getAllRecords(); // Set event attributes if (allRecords.containsKey(userId)) { Map<String, String> userRecords = allRecords.get(userId); for (Map.Entry<String, String> entry : userRecords.entrySet()) { // Mapping of experiment key to variation key attributes.put(entry.getKey(), entry.getValue()); } } // Tag custom event with attributes Localytics.tagEvent("[Optimizely] " + eventKey, attributes); } }; // Add listener OptimizelyClient optimizelyClient = optimizelyManager.getOptimizely(); optimizelyClient.addNotificationListener(listener); // Track a conversion event for the provided user optimizelyClient.track(eventKey, userId);
Mixpanel
Read the Mixpanel Quick Start Guide if you're just getting started.
Mixpanel supports two types of properties that can be used to segment data in their reports: Super Properties and People Properties. Each kind of property captures a slightly different aspect of the events and users they describe and differ in the ways they can be used for reporting. For maximum flexibility in reporting, we will use both super properties and people properties like Mixpanel suggests.
In the example code, we add an observer to OptimizelyDidActivateExperimentNotification. In the callback, we first set the super property and then the people property with Mixpanel.
Next in the callback we can optionally log an impression event that signifies that the Optimizely experiment was activated for the current user. You can later use this event or another event you may already be tracking to calculate a conversion rate.
Compare Results
When comparing numbers between Optimizely and Mixpanel results, remember to apply a date filter in Mixpanel to correspond with the dates your Optimizely experiment was running. People properties and super properties will remain set in Mixpanel even after your Optimizely experiment has stopped running.
Consistent User Identity
Maintaining a consistent user identity across multiple sessions and devices can help ensure proper reporting. Mixpanel provides some guidelines for their platform.
We recommend using the same user ID with the following methods:
[optimizely activate][mixpanel createAlias][mixpanel identify]
Read the Mixpanel Quick Start Guide if you're just getting started.
Mixpanel supports two types of properties that can be used to segment data in their reports: Super Properties and People Properties. Each kind of property captures a slightly different aspect of the events and users they describe and differ in the ways they can be used for reporting. For maximum flexibility in reporting, we will use both super properties and people properties like Mixpanel suggests.
In the example code, we add an observer to OptimizelyDidActivateExperimentNotification. In the callback, we first set the super property and then the people property with Mixpanel.
Next in the callback we can optionally log an impression event that signifies that the Optimizely experiment was activated for the current user. You can later use this event or another event you may already be tracking to calculate a conversion rate.
Compare Results
When comparing numbers between Optimizely and Mixpanel results, remember to apply a date filter in Mixpanel to correspond with the dates your Optimizely experiment was running. People properties and super properties will remain set in Mixpanel even after your Optimizely experiment has stopped running.
Consistent User Identity
Maintaining a consistent user identity across multiple sessions and devices can help ensure proper reporting. Mixpanel provides some guidelines for their platform.
We recommend using the same user ID with the following methods:
optimizely.activate()mixpanel.createAlias()mixpanel.identify()
Read the Mixpanel Quick Start Guide if you're just getting started.
Mixpanel supports two types of properties that can be used to segment data in their reports: Super Properties and People Properties. Each kind of property captures a slightly different aspect of the events and users they describe and differ in the ways they can be used for reporting. For maximum flexibility in reporting, we will use both super properties and people properties like Mixpanel suggests.
In the example code, we extend the class NotificationListener and override the onExperimentActivated() callback. In the callback, we first set the super property and then the people property with Mixpanel.
Next in the callback we can optionally log an impression event that signifies that the Optimizely experiment was activated for the current user. You can later use this event or another event you may already be tracking to calculate a conversion rate.
At the end of the example code, we add our listener to the Optimizely SDK.
Compare Results
When comparing numbers between Optimizely and Mixpanel results, remember to apply a date filter in Mixpanel to correspond with the dates your Optimizely experiment was running. People properties and super properties will remain set in Mixpanel even after your Optimizely experiment has stopped running.
Consistent User Identity
Maintaining a consistent user identity across multiple sessions and devices can help ensure proper reporting. Mixpanel provides some guidelines for their platform.
We recommend using the same user ID with the following methods:
optimizelyClient.activate()mixpanel.alias()mixpanel.identify()
Example Code
#import "Mixpanel/Mixpanel.h" [[NSNotificationCenter defaultCenter] addObserverForName:OptimizelyDidActivateExperimentNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { // Mixpanel instance Mixpanel *mixpanel = [Mixpanel sharedInstance]; // "Super property" will be sent with all future track calls OPTLYExperiment *experiment = note.userInfo[OptimizelyNotificationsUserDictionaryExperimentKey]; OPTLYVariation *variation = note.userInfo[OptimizelyNotificationsUserDictionaryVariationKey]; NSString *propertyKey = [NSString stringWithFormat:@"[Optimizely] %@", experiment.experimentKey]; [mixpanel registerSuperProperties:@{propertyKey: variation.variationKey}]; // Set "People property" for the user [mixpanel.people set:@{propertyKey: variation.variationKey}]; // Track impression event (optional) NSString *eventIdentifier = [NSString stringWithFormat:@"[Optimizely] %@ - %@", experiment.experimentKey, variation.variationKey]; [mixpanel track:eventIdentifier]; }];
Example Code
import Mixpanel let defaultNotificationCenter = NotificationCenter.default defaultNotificationCenter.addObserver( forName: NSNotification.Name("OptimizelyExperimentActivated"), object: nil, queue: nil) { (note) in let userInfo : Dictionary<String, AnyObject>? = note.userInfo as! Dictionary<String, AnyObject>? if let experiment = userInfo?["experiment"] as! OPTLYExperiment? { if let variation = userInfo?["variation"] as! OPTLYVariation? { // Mixpanel instance let mixpanel : MixpanelInstance = Mixpanel.mainInstance() // "Super property" will be sent with all future track calls let propertyKey : String! = "[Optimizely] " + experiment.experimentKey mixpanel.registerSuperProperties([propertyKey: variation.variationKey]) // Set "People property" for the user mixpanel.people.set(property: propertyKey, to: variation.variationKey) // Track impression event (optional) let eventIdentifier : String = "[Optimizely] " + experiment.experimentKey + " - " + variation.variationKey mixpanel.track(event:eventIdentifier) } }
Example Code
import android.util.Log; import com.mixpanel.android.mpmetrics.MixpanelAPI; import com.optimizely.ab.config.Experiment; import com.optimizely.ab.config.Variation; import com.optimizely.ab.notification.NotificationListener; import org.json.JSONException; import org.json.JSONObject; NotificationListener listener = new NotificationListener() { @Override public void onExperimentActivated(Experiment experiment, String userId, Map<String, String> attributes, Variation variation) { String experimentKey = experiment.getKey(); String variationKey = variation.getKey(); // Mixpanel instance String projectToken = YOUR_PROJECT_TOKEN; MixpanelAPI mixpanel = MixpanelAPI.getInstance(this, projectToken); try { // "Super property" will be sent with all future track calls JSONObject props = new JSONObject(); props.put("[Optimizely] " + experimentKey, variationKey); mixpanel.registerSuperProperties(props); } catch (JSONException e) { Log.e("MYAPP", "Unable to add properties to JSONObject", e); } // Set "People property" for the user mixpanel.getPeople().set( "[Optimizely] " + experimentKey, variationKey); // Track impression event (optional) mixpanel.track( "[Optimizely] " + experimentKey + " - " + variationKey); } }; // Add listener OptimizelyClient optimizelyClient = optimizelyManager.getOptimizely(); optimizelyClient.addNotificationListener(listener);
Segment
Please refer to the Optimizely X Fullstack guide for instructions on integrating our JavaScript SDK with your Segment analytics.
Example Code
<html> <head> <script type="text/javascript"> var userId = 'user~1'; // Load the Segment Lib // Initialize Segment analytics.load("YOUR_KEY_HERE"); analytics.page(); // Identify user + add user attributes/traits // These user traits are passed along to Optimizely as user attributes analytics.identify(userId, { age: 24, language: 'yes', }); // Initialize Optimizely and make sure it is appended to the global variable that the Segment integration can use (window.optimizelyClientInstance) var datafile = {}; // YOUR DATAFILE HERE var optimizelyClientInstance = window.optimizelyClient.createInstance({ datafile: datafile }); // Metadata associated with the event // These are passed along to Optimizely as eventTags var eventProperties = { 'category': 'shoes', 'purchasePrice': 42 }; analytics.track("page_loaded", eventProperties); // If you wish to override the userId and user attributes set during the `.identify` call or you don't want to use the `.identify` call you can pass the userId and userAttributes as options to the track call. var options = { 'Optimizely': { 'userId': userId, 'attributes': userAttributes } } analytics.track("page_loaded", eventProperties, options); </script> </head> </html>