Handle user IDs
Information on user IDs in Optimizely Feature Experimentation.
Optimizely Feature Experimentation identifies visitors with user IDs and determines which variation they see in an experiment (A/B test or multi-armed bandit) or a flag delivery (targeted delivery). Feature Experimentation lets you define user IDs based on your testing strategy. For example, you can use a first-party cookie, a device ID for anonymous users, or a stored identifier for logged-in users.
Consistent user IDs prevent users from being potentially re-bucketed into different variations, ensuring reliable experiment results. If your tests span multiple devices or applications, you can use a UUID to maintain a consistent experience for each user. Alternatively, you can use bucketing IDs to provide a consistent experience across different identifiers.
Tips for user IDs
- Ensure user IDs are unique – Feature Experimentation buckets users based on the user IDs you provide, so ensure these IDs are unique within your testing population.
- Anonymize user IDs – Optimizely processes user IDs exactly as provided. Anonymize any personally identifiable information (PII), such as email addresses, according to your company's policies.
- Use one namespace per project – Feature Experimentation assumes a single namespace for user IDs per project. Mixing different ID conventions within a project (for example, anonymous visitor IDs and UUIDs) can affect mutual exclusivity between tests.
- Use logged-out or logged-in IDs – Optimizely does not alias logged-out IDs with logged-in IDs. Persist logged-out IDs for the duration of a test if you need to track users across authentication states. See the Handle multiple user IDs section.
- iOS 14 ID for advertisers and disclosure updates – Feature Experimentation SDKs do not access user data or identifiers that you do not explicitly pass in through instrumented API calls. As a result, you should not depend on SDK changes planned to accommodate Apple's changes to its privacy disclosure and opt-in policy related to their ID for Advertisers (IDFA). You should review Apple's full requirements and guidance, other identifier options, and your data use carefully to determine the correct path forward for your application and usage.
Create a user context example
Feature Experimentation SDKs manage user IDs through user contexts. When you create a user context, you define a user ID and optional attributes for audience targeting. The SDKs consistently assign a user to the same variation as long as the traffic distribution is not modified. See Why you should not change a running experiment.
The following code samples demonstrate creating a user context using Feature Experimentation SDKs. Every Feature Experimentation SDK handles creating a user context similarly. Scroll right on the list of SDKs in the code sample box to see additional SDKs.
// Initalize the Android SDK
// Build a manager
val optimizelyManager = OptimizelyManager.builder()
// Provide the sdkKey of your desired environment here
.withSDKKey("YOUR_SDK_KEY")
.build(context)
// Instantiate a client synchronously with a bundled datafile
// Copy datafile JSON from URL accessible in App>Settings
val datafile = "<REPLACE_WITH_YOUR_DATAFILE>"
val optimizelyClient = optimizelyManager.initialize(context, datafile)
// Option 1 – Create a user, then set attributes.
var user: OptimizelyUserContext?
user = optimizelyClient.createUserContext("YOUR_USER_ID")
user.setAttribute("is_logged_in", false)
user.setAttribute("app_version", "1.3.2")
// Option 2 – Pass attributes when creating the user.
val attributes: MutableMap<String, Any> = HashMap()
attributes["is_logged_in"] = false
attributes["app_version"] = "1.3.2"
user = optimizelyClient.createUserContext("YOUR_USER_ID", attributes)
// Initalize the Android SDK
// Build a manager
OptimizelyManager optimizelyManager = OptimizelyManager.builder()
// Provide the sdkKey of your desired environment here
.withSDKKey("YOUR_SDK_KEY")
.build(context);
// Instantiate a client synchronously with a bundled datafile
// copy datafile JSON from URL accessible in app>settings
String datafile = "REPLACE_WITH_YOUR_DATAFILE";
OptimizelyClient optimizelyClient = optimizelyManager.initialize(context, datafile);
// Option 1 – Create a user, then set attributes.
OptimizelyUserContext user;
user = optimizelyClient.createUserContext("YOUR_USER_ID");
user.setAttribute("is_logged_in", false);
user.setAttribute("app_version", "1.3.2");
// Option 2 – Pass attributes when creating the user.
Map<String, Object> attributes = new HashMap<>();
attributes.put("is_logged_in", false);
attributes.put("app_version", "1.3.2");
user = optimizelyClient.createUserContext("YOUR_USER_ID", attributes);
// Import Optimizely SDK
using OptimizelySDK;
// Instantiate an Optimizely client
// Provide the sdkKey of your desired environment here
var optimizely = OptimizelyFactory.NewDefaultInstance("YOUR_SDK_KEY");
// Option 1 – Create a user, then set attributes.
var user = optimizely.CreateUserContext("YOUR_USER_ID");
user.SetAttribute("is_logged_in", false);
user.SetAttribute("app_version", "1.3.2");
// Option 2 – Pass attributes when creating the user.
var attributes = new UserAttributes
{
{ "is_logged_in", false },
{ "app_version", "1.3.2" }
};
var user = optimizely.CreateUserContext("YOUR_USER_ID", attributes);
// Initialize OptimizelyClient
// Provide the sdkKey of your desired environment here
var flutterSDK = OptimizelyFlutterSdk("YOUR_SDK_KEY");
var response = await flutterSDK.initializeClient();
// Option 1 – Create a user, then set attributes.
var user = await flutterSDK.createUserContext("YOUR_USER_ID");
var attributes = <String, dynamic>{};
attributes["is_logged_in"] = false;
attributes["app_version"] = "1.3.2";
user!.setAttributes(attributes);
// Option 2 – Pass attributes when creating the user.
var attributes = <String, dynamic>{};
attributes["is_logged_in"] = false;
attributes["app_version"] = "1.3.2";
var optimizelyUserContext = await optimizelyClient.createUserContext(userId: "YOUR_USER_ID", attributes: attributes);
import "github.com/optimizely/go-sdk/pkg/client" // for v2: "github.com/optimizely/go-sdk/v2/pkg/client"
// Instantiate an Optimizely client
// Provide the sdkKey of your desired environment here
optimizelyFactory := client.OptimizelyFactory{SDKKey: "YOUR_SDK_KEY"}
client, err := optimizelyFactory.StaticClient()
if err != nil {
// handle error
}
// Option 1 – Create a user, then set attributes.
user := client.CreateUserContext("YOUR_USER_ID", nil)
user.SetAttribute("is_logged_in", false)
user.SetAttribute("app_version", "1.3.2")
// Option 2 – Pass attributes when creating the user.
attributes := map[string]interface{}{
"device": "iPhone",
"lifetime": 24738388,
"is_logged_in": true,
"app_version": "4.3.0-beta",
}
user = client.CreateUserContext("YOUR_USER_ID", attributes)
import com.optimizely.ab.Optimizely;
import com.optimizely.ab.OptimizelyFactory;
// Instantiate an Optimizely client
public class App {
public static void main(String[] args) {
// Provide the sdkKey of your desired environment here
String sdkKey = "YOUR_SDK_KEY";
Optimizely optimizely = OptimizelyFactory.newDefaultInstance(sdkKey);
}
}
// Option 1 – Create a user, then set attributes.
OptimizelyUserContext user = optimizely.createUserContext("YOUR_USER_ID");
user.setAttribute("is_logged_in", false);
user.setAttribute("app_version", "1.3.2");
// Option 2 – Pass attributes when creating the user.
Map<String, Object> attributes = new HashMap<>();
attributes.put("is_logged_in", false);
attributes.put("app_version", "1.3.2");
OptimizelyUserContext user = optimizely.createUserContext("YOUR_USER_ID", attributes);
import { createInstance } from '@optimizely/optimizely-sdk';
const optimizely = createInstance({
sdkKey: 'YOUR_SDK_KEY' // Provide the sdkKey of your desired environment here
});
// Option 1 - Create a user, then set attributes.
const user = optimizely.createUserContext('YOUR_USER_ID');
user.setAttribute('is_logged_in', false);
user.setAttribute('app_version', '1.3.2');
// Option 2 – Pass attributes when creating the user.
const attributes = {
is_logged_in: false,
app_version: '1.3.2'
}
const user = optimizely.createUserContext('YOUR_USER_ID', attributes);
const { createInstance } = require('@optimizely/optimizely-sdk');
const optimizely = createInstance({
sdkKey: '<YOUR_SDK_KEY>' // Provide the sdkKey of your desired environment here
});
// Option 1 – Create a user, then set attributes.
const user = optimizely.createUserContext('YOUR_USER_ID');
user.setAttribute('is_logged_in', false);
user.setAttribute('app_version', '1.3.2');
// Option 2 – Pass attributes when creating the user.
const attributes = {
is_logged_in: false,
app_version: '1.3.2'
}
const user = optimizely.createUserContext('YOUR_USER_ID', attributes);
use Optimizely\Optimizely;
// Instantiate an Optimizely client
// Provide the sdkKey of your desired environment here
$sdkKey = "YOUR_SDK_KEY";
$optimizelyClient = OptimizelyFactory::createDefaultInstance($sdkKey);
// Option 1 – Create a user, then set attributes.
$user = $optimizely->createUserContext('YOUR_USER_ID');
$user->setAttribute('is_logged_in', false);
$user->setAttribute('app_version', '1.3.2');
// Option 2 – Pass attributes when creating the user.
$attributes = array(
'device'=>'iPhone',
'lifetime'=> 24738388,
'is_logged_in'=> true,
'app_version' => '4.3.0-beta'
);
$user = $optimizely->createUserContext('YOUR_USER_ID', $attributes);
from optimizely import optimizely
# Provide the sdkKey of your desired environment here
optimizely_client = optimizely.Optimizely('YOUR_SDK_KEY')
# Option 1 – Create a user, then set attributes.
user = optimizely_client.create_user_context("YOUR_USER_ID")
user.set_attribute("is_logged_in", False)
user.set_attribute("app_version", "1.3.2")
# Option 2 – Pass attributes when creating the user.
attributes = {
"device": "iPhone",
"lifetime": 24738388,
"is_logged_in": True,
"app_version": "4.3.0-beta",
}
user = optimizely_client.create_user_context("YOUR_USER_ID", attributes)
user_attributes = user.get_user_attributes()
mport { OptimizelyProvider, createInstance } from '@optimizely/react-sdk';
const optimizely = createInstance({
sdkKey: 'YOUR_SDK_KEY',
});
// Using a function component
export default function App() {
return (
<OptimizelyProvider
optimizely={optimizely}
user={{id: 'YOUR_USER_ID'}}>
<App />
</OptimizelyProvider>
);
}
// Or a class component
class App extends React.Component {
render() {
return (
<OptimizelyProvider
optimizely={optimizely}
user={{id: 'YOUR_USER_ID'}}>
<App />
</OptimizelyProvider>
);
}
}
import { OptimizelyProvider, createInstance } from '@optimizely/react-sdk';
const optimizely = createInstance({
sdkKey: 'YOUR_SDK_KEY',
});
// using a function component
export default function App() {
return (
<OptimizelyProvider
optimizely={optimizely}
user={{id: 'YOUR_USER_ID'}}>
<App />
</OptimizelyProvider>
);
}
// or a class component
class App extends React.Component {
render() {
return (
<OptimizelyProvider
optimizely={optimizely}
user={{id: 'YOUR_USER_ID'}}>
<App />
</OptimizelyProvider>
);
}
}
require 'optimizely/optimizely_factory'
# Initialize an Optimizely client
# Provide the sdkKey of your desired environment here
optimizely_client = Optimizely::OptimizelyFactory.default_instance('YOUR_SDK_KEY')
# Option 1 - Create a user, then set attributes.
user = optimizely.create_user_context('YOUR_USER_ID')
user.set_attribute('is_logged_in', false)
user.set_attribute('app_version', '1.3.2')
# Option 2 – Pass attributes when creating the user.
attributes = {
'is_logged_in' => false,
'app_version' => '1.3.2'
}
optimizely.create_user_context('YOUR_USER_ID', attributes)
// Provide the sdkKey of your desired environment here
let optimizely = OptimizelyClient(sdkKey: "YOUR_SDK_KEY")
optimizely.start {
// Option 1 – Create a user, then set attributes.
let user = optimizely.createUserContext(userId: "YOUR_USER_ID")
user.setAttribute(key: "is_logged_in", value: false)
user.setAttribute(key: "app_version", value: "1.3.2")
// Option 2 - Pass attributes when creating the user.
let attributes: [String: Any] = [
"device": "iPhone",
"lifetime": 24738388,
"is_logged_in": true,
"app_version": "4.3.0-beta",
]
let user = optimizely.createUserContext(userId: "YOUR_USER_ID", attributes: attributes)
}
Using this approach ensures that experiment decisions and event tracking remain consistent for the user.
For information, see the SDK's user context documentation.
- Android
- C#
- Flutter
- Go
- Java
- JavaScript (Browser)
- JavaScript (Node)
- PHP
- Python
- React
- React Native
- Ruby
- Swift
Handle multiple user IDs
If a user interacts with an experiment or targeted delivery while logged out and then logs in, Optimizely does not merge their logged-out identity with their logged-in identity. You should persist the logged-out ID and continue using it after login instead of generating a new one if you need to track a user's journey across login states (for example, seeing the same experiment variation before and after login).
Risks to changing a logged-out ID
If you change the logged-out ID (for example, when they log in) the following can occur:
- Inconsistent user experience – Users may see different variations before and after logging in.
- Corrupted experiment data – Optimizely may attribute conversion events to different IDs, inflating visitor counts and skewing results.
- Billing inefficiencies – If you are on the Monthly Active User (MAU)-based pricing model, unnecessary user ID changes can increase your MAUs. See the users and invoices section.
Best practices for multiple user IDs
- Do not change user IDs in an existing user context, as it may alter variation assignments.
- Maintain separate user context instances for anonymous and logged-in users.
- Only use the logged-in user context where necessary, specifically in omnichannel experiments.
- Ensure event tracking is performed separately for each user context to maintain accurate results.
Recommended approach for handling multiple user IDs
To maintain experiment consistency, create separate user context instances for anonymous and logged-in users. Never modify the user ID in an existing user context. The following code sample demonstrates how to do this:
// To test on both anonymous journeys and logged in journeys you must use two user contexts.
// Do not change the user ID on the user context, this could change the variation the visitor sees and lead to innacurate results.
// Create instance.
const client = createInstance({
sdkKey: 'YOUR_SDK_KEY', // Provide the sdkKey of your desired environment here
});
// Use the optimizely visitor ID cookie here, for example, 424241b3-be1b-412a-8287-1d55a1cd9081.
// The function is not fully developed here, it is used for example only.
const anonymousUserId = getExperimentsUserId();
// Use the logged in user ID here, for example, 123456.
// The function is not fully developed here, it is used for example only.
const customerId = getCustomerId();
// Create two separate user contexts with a logged_in attribute.
// Never swap the ID in the user context!
const anonymousUser = client.createUserContext(anonymousUserId, {logged_in: false});
const loggedInUser = client.createUserContext(customerId, {logged_in: true});
// Run experiments by default on the anonymousUser.
const sortingDecision = anonymousUser.decide('sorting_experiment');
const bannerDecision = anonymousUser.decide('banner_experiment');
// Only for omnichannel experiment use the loggedInUser, this costs an extra MAU if you are on an MAU model so document this internally.
const omnichannelDecision = loggedInUser.decide('omnichannel_experiment')
const pricingDecision = loggedInUser.decide('pricing_experiment')
// You must fire events for each context for the events to be correctly recorded.
loggedInUser.trackEvent('purchase');
anonymousUser.trackEvent('purchase');
# To test on both anonymous journeys and logged-in journeys, you must use two user contexts.
# Do not change the user ID on the user context, as this could change the variation the visitor sees and lead to inaccurate results.
from optimizely import optimizely
# Create instance.
client = optimizely.Optimizely(sdk_key='YOUR_SDK_KEY') # Provide the sdkKey of your desired environment here.
# Use the optimizely visitor ID cookie here, for example, 424241b3-be1b-412a-8287-1d55a1cd9081.
# The function is not fully developed here; it is used for example only.
anonymous_user_id = get_experiments_user_id()
# Use the logged-in user ID here.
# For example, 123456.
customer_id = get_customer_id()
# Create two separate user contexts with a logged_in attribute
# Never swap the ID in the user context!
anonymous_user = client.create_user_context(anonymous_user_id, {'logged_in': False})
logged_in_user = client.create_user_context(customer_id, {'logged_in': True})
# Run experiments by default on the anonymous_user.
sorting_decision = anonymous_user.decide('sorting_experiment')
banner_decision = anonymous_user.decide('banner_experiment')
# Only for omnichannel experiments, use the logged_in_user.
# This costs an extra MAU if you are on an MAU model, so document this internally.
omnichannel_decision = logged_in_user.decide('omnichannel_experiment')
pricing_decision = logged_in_user.decide('pricing_experiment')
# You must fire events for each context for the events to be correctly recorded.
logged_in_user.track_event('purchase')
anonymous_user.track_event('purchase')
Users and invoices
Starting in September 2020, Optimizely Feature Experimentation offers monthly active user (MAU) pricing as an alternative to impressions pricing. Optimizely counts an MAU each time one of the following occurs for an unique user ID:
- When you call a Decide method – A decision event (impression) is triggered.
- When you call a Track Event method – A conversion event is triggered.
The MAU count includes unique anonymous IDs. If you use both Optimizely Web Experimentation and Optimizely Feature Experimentation, you can override anonymous Optimizely Web Experimentation user IDs with known Feature Experimentation user IDs to avoid overcounting. See Bring your own ID.
Updated 9 days ago