Skip to content

Dynamic Content - Segmentation and AB Testing of Remote Configs using Joystick

Schematic

There are four components to keep in mind as we use Joystick Dynamic Content. We will discuss each of these in detail.

  1. The request from your application.
  2. The config that is stored in Joystick.
  3. The Dynamic Content Definition that enables segmentation or ab testing for each config.
  4. The response back to your application.

Declaring Content for Segmentation or AB Testing

The first step is to declare the content you would like to be dynamic for segmentation or AB Test. Declarations are made directly in your config JSON using a simple syntax.

Declaration Syntax

The Joystick Link Key

The Joystick Link Key is what makes our system powerful. You declare using a simple syntax any parameters directly in your content. When we send the JSON out over the API, the Link Keys are all scrubbed and replaced with either the default value, or the dynamic value based on segmentation or AB test matches.

Dynamic Link Key

Item Explanation
Pre-fix The prefix must be four characters "#LK-" (Link Key)
Link Key Name Used to map the response variables into the correct place in the configuration. This should be unique.
Data Type One of the six JSON standard data types: string, number, boolean, object, array.
Default Value The value to use if there is no match to a rule.
Post-fix The end of the string must be the hash symbol "#"

Declaring String, Number or Boolean

{
    "my_string": "#LK-SimpleString:string:Hello!#",
    "my_number": "#LK-SimpleNumber:number:12345#",
    "my_boolean": "#LK-SimpleBoolean:boolean:true#"
}

As soon as you enter the declarations, the editor in Joystick will recognize it, then show you the default values it parsed.

Declarations

Declaring Objects or Arrays

{
    "my_object": "#LK-SimpleObject:object:my_object#",
    "my_array": "#LK-SimpleArray:array:my_array#",
    "_dynamic_values_": {
        "my_object": {},
        "my_array": []
    }   
}

You can make entire objects or arrays dynamic.

  1. Add a special object to the root of your content called _dynamic_values_. This is where the default values for arrays and objects will be picked up from.
  2. The default value should be a key that corresponds to the array or object in _dynamic_values_.

    About _dynamic_values_

    The "_dynamic_values_" object will be removed when sending the response back to you. It will NOT appear when requesting your content over API.

The _dynamic_values_ object will be highlighted in the editor.

Declare Object

The Dynamic Content Definition

When you have identified and declared the components you would like to segment or ab-test in your content, you can now create the Dynamic Content Definition. The Dynamic Content Definition is the rule-set that determines how your content is transformed when the user falls into a segment or ab-test.

Click Add Dynamic Content Definition when you are editing a piece of content to open up the Dynamic Content Definition Editor panel.

Add Dynamic

[
  {
    "expression": "location = 'ch'",
    "name": "offer_title_test",
    "info": "Optional, additional info.",
    "activationDateTime": {
      "start": "2022-01-01T01:01:01Z",
      "end": "2099-12-31T23:34:45Z",
      "overrideAndActivateNow": false
    },
    "groups": [
      {
        "variantName": "variant_a",
        "mod": [
          [ 0, 199 ]
        ],
        "values": {
          "LK-OfferTitle": "Extra Special Offer"
        }
      }
    ]
  }
]
Setup Description
expression REQUIRED An expression, if evaluated true, then our system will attempt to process with either segmentation or ab testing. The ncalc expression engine is used. The expression can use any of the parameters passed in on the "p" dictionary of your request. See below for details.
name REQUIRED A short string to identify this segment or ab-test.
info Optional A short string with any additional information you would like regarding this segment or ab-test. This will NOT be returned to you.
activationDateTime Optional A dictionary that defines the date time range over which this segment is active. You can use this to schedule live-ops events.
activationDateTime.start activationDateTime.end If activationDateTime is used, either start or end is required. Optional to have both define. Start and end time, defined in ISO8601 format. Example: 2022-01-01T01:01:01Z. You may set just a start (event has no ending), just an end (event starts immediately), or both.
activationDateTime.overrideAndActivateNow Optional Useful for QA testing. If you have an event with a start date in the future, but you want the effects of the segment to activate right now for QA testing without having to change the start date-time, you can set this to true.
overlayType Optional If you want to override the entire configuration for this segment or ab-test, set this to FullConfig. If not set, then the default is Values, which means you have to declare which specific values in your config to replace, and the values in the values or groups[].values will be used.
fullConfig Optional If overlayType is set to FullConfig, then this object will be used to override the entire configuration for this segment or ab-test. It must be an object (specifically in Typescript: Record<string, unknown>).
groups Optional. See Usage Notes. An array of ab-test or treatment groups.
groups[].variantName Required if groups used. A string that is unique and allows you to identify a variant of the AB test.
groups[].mod Required if groups used. An array of arrays with two integers (0-999 inclusive) that represent the mod range over which to show this variation. See below for details.
groups[].values Required if groups used. A dictionary of key-value pairs, where the keys match the Link Keys declared in the configuration content. The values here will be used as replacements if the expression matches (evaluates as true) AND the user falls into one of the mod ranges defined.
groups[].fullConfig Optional If overlayType is set to FullConfig, and ab test groups are defined, then this object will be used to override the entire configuration for an ab test group. It must be an object (specifically in Typescript: Record<string, unknown>).
values A dictionary of key-value pairs, where the keys match the Link Keys declared in the configuration content. The values here will be used as replacements if the expression matches (evaluates as true).

Usage Notes

  • If overlayType is set to FullConfig:
    • One of fullConfig or groups is defined, but not both together. values cannot be used in this case.
    • The fullConfig parameter or the groups[].fullConfig object will be used to override the entire configuration for a matching segment or ab-test.
  • If overlayType is not set, or set to Values:
    • One of values or groups is defined, but not both together. fullConfig cannot be used in this case.
    • The values parameter or the groups[].values object will be used to replace individual values declared in the configuration for a matching segment or ab-test.

Expression

The expression parameter makes Joystick Dynamic Content extremely versatile and flexible. On the p object in your request, you can send along different parameters, which can then be used in the expression.

  • When the expression evaluates to true then the content the content replacement of the group it is in will be processed.
  • You can have multiple groups that all evaluate to true. Content replacement is processed in priority from top to bottom.

Please Note

The expression, when fully resolved, should evaluate to true or false. Any errors mean the group will be ignored.

or, ||
and, &&
!, not, ~ (bitwise not)
=, ==, !=, <>
<, <=, >, >=
+, -, *, /, %
& (bitwise and), | (bitwise or), ^ (bitwise xor), << (left shift), >> (right shift)

Expression Example

{
    "u": "USER ID OR GUID",
    "p": {
        "myString": "hello",
        "myStringArray": ["one", "two", "three"],
        "myNumber": 10,
        "myNumberArray": [1, 2, 3] 
    }
}

Given that the body sent with the config is the same as above, here are some examples to show you how expressions work.

"expression": "myString == 'hello'" // true
"expression": "(myNumber * 2) == 10 " // false
"expression": "myString == 'hello' and myNumber >= 10" // true (Use logical operators to combine statements)
"expression": "StringIn('one', myStringArray) and myNumber < 20" // true (Check if 'one' is a member of myStringArray)
"expression": "NumberIn(6, myNumberArray)" // false
"expression": "in(myString, 'hello', 'goodbye')" // true (Is the first argument in hard-coded list)
"expression": "if(myNumber > 20, true, myString == 'goodbye')" // false (Using an if statement)

Controlling AB Tests and Staged Rollout (Using the Mod Parameter)

Joystick has a powerful AB test system that gives you precise control over how you run your tests, partial audience exposures, and rollouts.

  • Create test groups as small as 0.1% of your audience. (You can use segmentation to further refine this.)
  • Expose only a specific percentage or segment of your audience to a new feature or content.
  • Users are assigned to stable, consistent groups over time so you can do long-term testing, or control for not over-testing a particular group.
  • You can run multiple non-overlapping tests simultaneously.
  • You can perform staged rollouts, gradually increasing the percentage of users exposed to a new feature or content.

Core Concepts

  1. Stable User Assignment: Based on the "u" parameter sent in the request body, Joystick generates a stable integer between 0 and 999 for each user (using MurMur and modulo 1000). This number remains consistent for the same user id sent.
  2. Audience Segmentation: You can define "bands" of users by specifying ranges of these integers. Each increment of 1 represents 0.1% of your audience.
  3. Flexible Grouping: By defining different ranges, or multiple ranges, you can control how much of your audience is exposed to a specific variation.

What are Mod Ranges?

Please view our Understanding Mod Groups in Joystick page for a detailed explanation of how the mod number and mod ranges work.

Key Points

  • The same user id pass as "u" will always fall into the same mod range, ensuring consistency across tests.
  • Mod ranges are defined as arrays of integer pairs: [[start, end], [start2, end2]]. Start and end values are inclusive.
  • Values must be between 0 and 999.
  • Multiple ranges can be defined for the same variant.
  • Ensure that mod ranges for different variants of the same test do not overlap to avoid cross-contamination of results.
  • When expanding a rollout, always include the previous range to avoid removing users who were already exposed.
"mod": [[0, 0]]     // 0.1% of users (mod value 0 only)
"mod": [[0, 99]]    // 10% of users (mod values 0-99)
"mod": [[499, 999]]   // 50% of users (mod values 499-999)
"mod": [[200, 299], [800, 899]] // 20% total: two separate 10% ranges

Simple AB Testing Example

  • Base Config Content
    {
        // OfferTitle and Price are declared as a dynamic content parameter
        "SpecialPricingOffer": {
            "OfferTitle": "#LK-OfferTitle:string:Special Offer#",
            "PercentBonus": "10",
            "Price": "#LK-Price:number:4.99#",
            "BackgroundColor": "LightGreen",
            "ButtonColor": "LightSalmon",
            "ButtonActionText": "Buy Now"
        }
    }
    
  • Dynamic Content Definition
    [
        {
            "expression": "location = 'ch'",
            "name": "offer_title_test",
            "groups": [
                {
                    "variantName": "variant_a",
                    "mod": [[0, 199]], // 20% of the audience
                    "values": {
                        "LK-OfferTitle": "Extra Special Offer",
                        "LK-Price": 3.99
                    }
                }
            ]
        }
    ]
    

Example Breakdown

This example will create a 20% audience test for user who match the expression location = 'ch'.

  • Base Config: This is the base configuration content. It contains a declaration for dynamic content on the OfferTitle parameter.
  • Dynamic Content Definition: This defines a segment based on the location parameter. If the location is 'ch', then the user will first match the segment. If the user does not match the expression, they will see the default value "Special Offer".
  • AB Test Groups: The groups array contains one group with a mod range of [0, 199], which means 20% of the users who match the expression will see the variant. The rest will see the default value.

Simultaneous AB Testing Example

  • Base Config Content
    {
        // OfferTitle and Price are declared as a dynamic content parameter
        "SpecialPricingOffer": {
            "OfferTitle": "#LK-OfferTitle:string:Special Offer#",
            "PercentBonus": "10",
            "Price": "#LK-Price:number:4.99#",
            "BackgroundColor": "LightGreen",
            "ButtonColor": "LightSalmon",
            "ButtonActionText": "Buy Now"
        }
    }
    
  • Dynamic Content Definition
    [
        {
            // With an expression of 1 = 1, the segment be all users
            "expression": "1 = 1",
            "name": "test_1",
            "overlayType": "FullConfig",
            "groups": [
                {
                    "mod": [[100, 199]], // This mod range will be 10% for Test 1
                    "variantName": "test_a-variant_a",
                    "fullConfig": {
                        "SpecialPricingOffer": {
                            "OfferTitle": "Title for Test 1, Variant A",
                            "PercentBonus": "20",
                            "Price": 4.99,
                            "BackgroundColor": "LightBlue",
                            "ButtonColor": "LightBlue",
                            "ButtonActionText": "Try Now"
                        }
                    }
                }
            ]
        },
        {
            "expression": "1 = 1",
            "name": "test_2",
            "groups": [
                {
                    // Test 2 Variant A
                    "variantName": "test_2-variant_a",
                    "mod": [[700, 749], [800, 849]], // 10% total, across two 5% ranges.
                    "values": {
                        "LK-Price": 5.99
                    }
                },
                {
                    // Test 2 Variant B
                    "variantName": "test_2-variant_a",
                    "mod": [[900, 999]], // 10% for Test 2, variant B
                    "values": {
                        "LK-Price": 6.99
                    }
                }
            ]
        }
    ]
    

Example Breakdown

In this example, there are two non-overlapping AB tests running.

  • The expression 1 = 1 is used to match all users, meaning both tests will apply to the entire audience.
  • Test 1
    • Has a mod range of [100,199], so applies to 10% of the audience.
    • Uses a full config overlay, and tests for a change in many parameters.
  • Test 2
    • Applies to 20% of the audience, split into two variants.
    • Uses a values overlay, and tests for a change only in the Price parameter.
    • Test 2 Variant A has a mod range of [700, 749], [800, 849], which means a total of 10% of the audience will see this variant with a price of 5.99.
    • Test 2 Variant B has a mod range of [900, 999], which means another 10% of the audience will see this variant with a price of 6.99.

Performing a Staged Rollout

To gradually increase audience exposure from 5% to 20%. Note, you can use this with start and end dates to control the rollout timing automatically.

  1. Start with a 5% rollout:

    "mod": [[0, 49]] // 5% of the audience.
    
  2. Expand to 10%:

    "mod": [[0, 99]] // 10% of the audience.
    
  3. Finally, expand to 20%:

    "mod": [[0, 199]] // 20% of the audience.
    

Dynamic Content: Extended Example and Explanation

This example demonstrates the functionality and flexibility of Joystick Dynamic Content. Both the Dynamic Content Definition and Content are shown.

[
  {
    "expression": "location = 'ch'",
    "name": "offer_title_test",
    "info": "Optional, additional info.",
    "groups": [
      {
        "variantName": "variant_a",
        "mod": [
          [
            0,
            199
          ]
        ],
        "values": {
          "LK-OfferTitle": "Extra Special Offer"
        }
      },
      {
        "variantName": "variant_b",
        "mod": [
          [
            500,
            599
          ],
          [
            700,
            899
          ]
        ],
        "values": {
          "LK-OfferTitle": "A Very Good Offer!"
        }
      }
    ]
  },
  {
    "expression": "days_in_app >= 7",
    "name": "improved_offer",
    "info": "Optional, additional info.",
    "values": {
      "LK-Price": 2.99,
      "LK-BundleItems": { "coins": 100, "gear": ["hammer", "tent", "portable-stove"] }
    }    
  }
]
{
  "SpecialPricingOffer":
  {
    "OfferTitle": "#LK-OfferTitle:string:Special Offer#",
    "Price": "4.99",
    "BundleItems": "#LK-BundleItems:object:bundle_items#"
  },
  "_dynamic_values_":
  {
    "bundle_items":
    {
      "coins": 50,
      "gear": ["backpack"]
    }
  }
}

There are two segments defined in the Dynamic Content Definition (two objects in the main array).

  1. "expression": "location = 'ch'"
    • This one is named "offer_title_test", and will match if the 'location' parameter passed in on the request is the string 'ch'.
    • There are two ab-test variants.
    • variant_a is exposed to 20% (mod range 0 to 199 inclusive) of the audience, and the title "Extra Special Offer" will be shown.
    • variant_a is exposed to 30% (mod range 500 to 599 and 700 to 899) of the audience, and the title "A Very Good Offer!" will be shown.
  2. "expression": "days_in_app >= 7"
    • This one is named "improved_offer", and will match if the 'days_in_app' parameter passed in on the request is a number equal or greater than 7.
    • There is no ab-test for this segment. When there is no ab-test, the "values" object can be placed at the same level as "expression".
    • There are two parameters to be replaced if this segment is matched.
      • LK-Price will be replaced with 2.99
      • LK-BundleItems will be replaced with a bigger bundle.
    • Notice how the groups parameter is not required if you have the values parameter.

Complete Example

Below is a complete example showing what each component looks like.

// This is the config that is stored in Joystick. It has a dynamic content declaration.
{
    "SpecialPricingOffer": {
        "OfferTitle": "#LK-OfferTitle:string:Special Offer#",
        "PercentBonus": "10",
        "Price": "4.99",
        "BackgroundColor": "LightGreen",
        "ButtonColor": "LightSalmon",
        "ButtonActionText": "Buy Now"
    }
}
// This is the dynamic content Definition that pairs with the Config Content.
[
  {
    "expression": "location = 'ch'",
    "name": "offer_title_test",
    "info": "Optional, additional info.",
    "groups": [
      {
        "variantName": "variant_a",
        "mod": [
          [
            0,
            199
          ]
        ],
        "values": {
          "LK-OfferTitle": "Extra Special Offer"
        }
      },
      {
        "variantName": "variant_b",
        "mod": [
          [
            200,
            499
          ]
        ],
        "values": {
          "LK-OfferTitle": "Offer Just For You!"
        }
      }
    ]
  }
]
// This is the body of the request to the Joystick REST API.
{
  "u": "d",
  "p":
  {
    "location": "ch"
  }
}
// This is the response back over REST API
{
  "data":
  {
    "SpecialPricingOffer":
    {
      "OfferTitle": "Extra Special Offer",
      "PercentBonus": "10",
      "Price": "4.99",
      "BackgroundColor": "LightGreen",
      "ButtonColor": "LightSalmon",
      "ButtonActionText": "Buy Now"
    }
  },
  "hash": "e35b02af",
  "meta":
  {
    "uid": 655955059,
    "mod": 59,
    "seg":
    [
      {
        "n": "offer_title_test", // Name of the segment
        "v": "variant_a" // Variant name
      }
    ]
  }
}