Disclaimer: This website requires Please enable JavaScript in your browser settings for the best experience.

The availability of features may depend on your plan type. Contact your Customer Success Manager if you have any questions.

Dev guideRecipesAPI ReferenceChangelog
Dev guideAPI ReferenceRecipesChangelogUser GuideGitHubDev CommunityOptimizely AcademySubmit a ticketLog In
Dev guide

Implement a user profile service for the JavaScript SDK v6

How to configure a custom user profile service for the Optimizely Feature Experimentation JavaScript (Browser) SDK.

❗️

Warning

This content covers the Feature Experimentation JavaScript (Browser) SDK v6 features currently in pre-production testing and is subject to change before release

For the latest released version, see JavaScript (Browser) SDK.

Use a user profile service 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.

In the JavaScript (Browser) SDK, there is no default implementation. 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 (for example, audiences, attributes, variation pausing, and traffic distribution). Otherwise, the JavaScript (Browser) SDK is stateless and relies on deterministic bucketing to return consistent assignments.

If the user profile service does not bucket a user as you expect, then check whether other flags are overriding the bucketing. For information, see How bucketing works.

Minimum SDK version

v6.0.0+

Implement a service

To implement a custom user profilesService, refer to the following code samples. The service needs to expose the following two functions with the following signatures:

  • lookup – Takes a user ID string and returns a user profile matching the following schema.
  • save – Takes a user profile and persists it.

If you want to use the user profile service purely for tracking purposes and not sticky bucketing, you can implement only the save method (always return nil from lookup).

The interface of the user profile service resembles the following:



import { createInstance, createPollingProjectConfigManager } from "@optimizely/optimizely-sdk"

const SDK_KEY="YOUR_SDK_KEY";

const pollingConfigManager = createPollingProjectConfigManager({
  sdkKey: SDK_KEY,
});

// Sample user profile service implementation
const userProfileService = {
  lookup: userId => {
    // Perform user profile lookup
  },
  save: userProfileMap => {
    // Persist user profile
  },
};

const optimizely = createInstance({
  projectConfigManager: pollingConfigManager,
  userProfileService,
});

The following example shows a client-side implementation of a user profile service using localStorage:


import { createInstance, createPollingProjectConfigManager } from "@optimizely/optimizely-sdk"

const SDK_KEY="YOUR_SDK_KEY";

const pollingConfigManager = createPollingProjectConfigManager({
  sdkKey: SDK_KEY,
});

const userProfileService = {
  // Adapter that provides helpers to read and write from localStorage
  UPS_LS_PREFIX: 'optimizely-ups-data',
  localStorageAdapter: {
    read: function(key) {
      const data = JSON.parse(localStorage.getItem(key) || '{}');
      return data;
    },
    write: function(key, data) {
      localStorage.setItem(key, JSON.stringify(data));
    },
  },
  getUserKey: function (userId) {
    return `${this.UPS_LS_PREFIX}-${userId}`;
  },
  // Perform user profile lookup
  lookup: function(userId) {
    return this.localStorageAdapter.read(this.getUserKey(userId));
  },
  // Persist user profile
  save: function(userProfileMap) {
    const userKey = this.getUserKey(userProfileMap.user_id);
    this.localStorageAdapter.write(userKey, userProfileMap);
  },
};

// example usage
const optimizely = createInstance({
  projectConfigManager: pollingConfigManager,
  userProfileService,
});

The following code example shows the JSON schema of the user profile object. In the example, ^[a-zA-Z0-9]+$ is the experiment ID.

{
  "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 JavaScript (Browser) SDK uses the user profile service you provide to override Optimizely Feature Experimentation's default bucketing behavior in cases when an experiment assignment has been saved.

The experiment_bucket_map overrides the default bucketing behavior and defines an alternate experiment variation for a given user. Each key in the experient_bucket_map object corresponds to an experiment override. The experiment ID is the key and the value is an object with a variation_id property that specifies the desired variation. If there is not an entry for an experiment, then the default bucketing behavior persists.

When implementing your own user profile service, you should load 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 service.

When implementing in a multi-server or stateless environment, you should use this interface with a backend like Cassandra or Redis. You can decide how long you want to keep your sticky bucketing around by configuring these services.