Skip to main content

Optimizely

Optimizing Experiences with Optimizely: Custom Audience Criteria for Mobile Visitors

Istock 1785617988

In today’s mobile-first world, delivering personalized experiences to visitors using mobile devices is crucial for maximizing engagement and conversions. Optimizely’s powerful experimentation and personalization platform allows you to define custom audience criteria to target mobile users effectively.

By leveraging Optimizely’s audience segmentation, you can create tailored experiences based on factors such as device type, operating system, screen size, and user behavior. Whether you want to optimize mobile UX, test different layouts, or personalize content for Android vs. iOS users, understanding how to define mobile-specific audience criteria can help you drive better results.

In this blog, we’ll explore how to set up simple custom audience criteria for mobile visitors in Optimizely, the key benefits of mobile targeting, and the best practices to enhance user experiences across devices. Let’s dive in!

This solution is based on Example – Create audience criteria, which you can find in the Optimizely documentation.

Create the settings and criterion classes

First, we need to create two classes in our solution:

Class VisitorDeviceTypeCriterionSettings needs to inherit CriterionModelBase class, and we need only one property (settings) to determine if the visitor is using a desktop or a mobile device.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
publicbool IsMobile {get; set; }
public bool IsMobile { get; set; }
public bool IsMobile { get; set; }

The abstract CriterionModelBase class requires you to implement the Copy() method. Because you are not using complex reference types, you can implement it by returning a shallow copy as shown (see Create custom audience criteria):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
publicoverride ICriterionModel Copy()
{
returnbase.ShallowCopy();
}
public override ICriterionModel Copy() { return base.ShallowCopy(); }
public override ICriterionModel Copy()
{
    return base.ShallowCopy();
}

The entire class will look something like this:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
using EPiServer.Data.Dynamic;
using EPiServer.Personalization.VisitorGroups;
namespace AlloyTest.Personalization.Criteria
{
[EPiServerDataStore(AutomaticallyRemapStore = true)]
publicclass VisitorDeviceTypeCriterionSettings : CriterionModelBase
{
publicbool IsMobile {get; set; }
publicoverride ICriterionModel Copy()
{
// if this class has reference types that require deep copying, then
// that implementation belongs here. Otherwise, you can just rely on
// shallow copy from the base class
returnbase.ShallowCopy();
}
}
}
using EPiServer.Data.Dynamic; using EPiServer.Personalization.VisitorGroups; namespace AlloyTest.Personalization.Criteria { [EPiServerDataStore(AutomaticallyRemapStore = true)] public class VisitorDeviceTypeCriterionSettings : CriterionModelBase { public bool IsMobile { get; set; } public override ICriterionModel Copy() { // if this class has reference types that require deep copying, then // that implementation belongs here. Otherwise, you can just rely on // shallow copy from the base class return base.ShallowCopy(); } } }
using EPiServer.Data.Dynamic;
using EPiServer.Personalization.VisitorGroups;

namespace AlloyTest.Personalization.Criteria
{
    [EPiServerDataStore(AutomaticallyRemapStore = true)]
    public class VisitorDeviceTypeCriterionSettings : CriterionModelBase
    {
        public bool IsMobile { get; set; }

        public override ICriterionModel Copy()
        {
            // if this class has reference types that require deep copying, then
            // that implementation belongs here. Otherwise, you can just rely on
            // shallow copy from the base class
            return base.ShallowCopy();
        }
    }
}

Now, we need to implement the criterion class VisitorDeviceTypeCriterion and inherit the abstract CriterionBase class with the settings class as the type parameter:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
publicclass VisitorDeviceTypeCriterion : CriterionBase<VisitorDeviceTypeCriterionSettings>
public class VisitorDeviceTypeCriterion : CriterionBase<VisitorDeviceTypeCriterionSettings>
public class VisitorDeviceTypeCriterion : CriterionBase<VisitorDeviceTypeCriterionSettings>

Add a VisitorGroupCriterion attribute to set the category, name, and description of the criterion (for more available VisitorGroupCriterion properties, see Create custom audience criteria:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[VisitorGroupCriterion(
Category = "MyCustom",
DisplayName = "Device Type",
Description = "Criterion that matches type of the user's device"
)]
[VisitorGroupCriterion( Category = "MyCustom", DisplayName = "Device Type", Description = "Criterion that matches type of the user's device" )]
[VisitorGroupCriterion(
    Category = "MyCustom",
    DisplayName = "Device Type",
    Description = "Criterion that matches type of the user's device"
)]

The abstract CriterionBase class requires you to implement an IsMatch() method that determines whether the current user matches this audience criterion. In this case, we need to determine from which device the visitor is accessing our site. Because Optimizely doesn’t provide this out of the box, we need to figure out that part.

One of the solutions is to use information from the request header, from the User-Agent field and analyze it to determine the OS and device type. We can do that by writing our match method:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
publicvirtualboolMatchBrowserType(string userAgent)
{
var os =
newRegex(
@"(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino",
RegexOptions.IgnoreCase | RegexOptions.Multiline);
var device =
newRegex(
@"1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-",
RegexOptions.IgnoreCase | RegexOptions.Multiline);
var deviceInfo = string.Empty;
if(os.IsMatch(userAgent))
{
deviceInfo = os.Match(userAgent).Groups[0].Value;
}
if(device.IsMatch(userAgent.Substring(0, 4)))
{
deviceInfo += device.Match(userAgent).Groups[0].Value;
}
if(!string.IsNullOrEmpty(deviceInfo))
{
returntrue;
}
returnfalse;
}
public virtual bool MatchBrowserType(string userAgent) { var os = new Regex( @"(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino", RegexOptions.IgnoreCase | RegexOptions.Multiline); var device = new Regex( @"1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-", RegexOptions.IgnoreCase | RegexOptions.Multiline); var deviceInfo = string.Empty; if (os.IsMatch(userAgent)) { deviceInfo = os.Match(userAgent).Groups[0].Value; } if (device.IsMatch(userAgent.Substring(0, 4))) { deviceInfo += device.Match(userAgent).Groups[0].Value; } if (!string.IsNullOrEmpty(deviceInfo)) { return true; } return false; }
public virtual bool MatchBrowserType(string userAgent)
{
    var os =
        new Regex(
            @"(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino",
            RegexOptions.IgnoreCase | RegexOptions.Multiline);
    var device =
        new Regex(
            @"1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-",
            RegexOptions.IgnoreCase | RegexOptions.Multiline);
    var deviceInfo = string.Empty;

    if (os.IsMatch(userAgent))
    {
        deviceInfo = os.Match(userAgent).Groups[0].Value;
    }

    if (device.IsMatch(userAgent.Substring(0, 4)))
    {
        deviceInfo += device.Match(userAgent).Groups[0].Value;
    }

    if (!string.IsNullOrEmpty(deviceInfo))
    {
        return true;
    }

    return false;
}

Now, we can go back and implement the IsMatch() method that is required by CriterionBase abstract class.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
publicoverrideboolIsMatch(IPrincipal principal, HttpContext httpContext)
{
returnMatchBrowserType(httpContext.Request.Headers["User-Agent"].ToString());
}
public override bool IsMatch(IPrincipal principal, HttpContext httpContext) { return MatchBrowserType(httpContext.Request.Headers["User-Agent"].ToString()); }
public override bool IsMatch(IPrincipal principal, HttpContext httpContext)
{
    return MatchBrowserType(httpContext.Request.Headers["User-Agent"].ToString());
}

 

Test the criterion

In the CMS we need to create a new audience criterion. When you click on the ‘Add Criteria’ button, there will be ‘MyCustom’ criteria group with our criteria:

When you select the ‘Device Type’ criteria, you will see something like this:

We can easily add a label for the checkbox by using Optimizely’s translation functionality. Create a new XML file VisitorGroupCriterion.xml and place it in your translations folder where your translation files are, like this:

Put this into the file that you created:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<languages>
<languagename="English"id="en-us">
<visitorgroups>
<criteria>
<ismobile>
<key>Is Mobile Device (Use this setting to show content only on Mobile)</key>
</ismobile>
</criteria>
</visitorgroups>
</language>
</languages>
<?xml version="1.0" encoding="utf-8" standalone="yes"?> <languages> <language name="English" id="en-us"> <visitorgroups> <criteria> <ismobile> <key>Is Mobile Device (Use this setting to show content only on Mobile)</key> </ismobile> </criteria> </visitorgroups> </language> </languages>
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<languages>
  <language name="English" id="en-us">
    <visitorgroups>
      <criteria>
        <ismobile>
          <key>Is Mobile Device (Use this setting to show content only on Mobile)</key>
        </ismobile>
      </criteria>
    </visitorgroups>
  </language>
</languages>

 

There is one more thing to do. In VisitorDeviceTypeCriterionSettings.cs, decorate the IsMobile property with the translation definition. Add this attribute:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[CriterionPropertyEditor(LabelTranslationKey = "/visitorgroups/criteria/ismobile/key")]
[CriterionPropertyEditor(LabelTranslationKey = "/visitorgroups/criteria/ismobile/key")]
[CriterionPropertyEditor(LabelTranslationKey = "/visitorgroups/criteria/ismobile/key")]

It should look like this:

Now, in the editor view, we have a label for the checkbox.

 

Personalize the content by setting the content for this visitor group.

Desktop view:

 

Mobile view:

You can see that there is content that is only visible if you access the site with a mobile device.

 

And that’s it!

Thoughts on “Optimizing Experiences with Optimizely: Custom Audience Criteria for Mobile Visitors”

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Nenad Nicevski

Nenad is a Certified Optimizely (former EPiServer) Content Cloud Developer. He is a full-stack developer focused on ASP.NET CMS technologies, mainly ASP.NET Core and ASP.NET with MVC. Nenad is no stranger to WordPress, Drupal 7/8, and Angular JS. In his free time, he loves to play basketball and guitar and make fun of himself to amuse his daughter!

More from this Author

Follow Us