Properties, custom properties, and target groups

8 minute read

A target group is a set of users having the same properties or attributes, such as email domain, language, geolocation, device, or software version.

Create an unlimited number of target groups in the CloudBees platform. Each target group configuration can have nested conditions for complex logic and unlimited attributes, allowing you to finely tune a given audience for a feature flag.

Multiple flags can all have the same targeting, and updating a single target group definition applies to all flags that use the target group in their flag configurations.

Set up a target group for internal testing only, for example, and use it across multiple flags to test new features. Once internal testing is complete, modify the target group to roll out the new features to external users.

Create target groups using custom or built-in properties defined in the code. Learn more about defining properties in the SDK references, and using properties and target groups to configure flags.

Create custom properties in either the UI or in code.

To avoid CloudBees platform discrepancies, you cannot delete a custom property. If you require a custom property to be deleted, submit a request to CloudBees Support.

The following property types are available:

  • Boolean

  • Number (some code languages use Double and Int)

  • String

  • SemVer (pre-release and patch labels are not supported.)

  • DateTime

Custom properties in code

Add a custom property to your connected app code for use in target groups, then run the app to display the property options in the UI. For example, the following code within a connected JavaScript app results in the target group options displayed below.

Rox.setCustomStringProperty('company', getCompany()) Rox.setCustomBooleanProperty('isBetaUser', betaAccess()) Rox.setCustomBooleanProperty('isLoggedIn', isLoggedIn()) Rox.setCustomDateTimeProperty('isTargetDate', getTargetDate())
The DateTimeProperty is only available for CloudBees platform.
Target group properties dropdown
Figure 1. Custom properties options are highlighted.

Usage examples: Explicit custom properties

Define explicit custom properties depending on the code language of your connected application:

Android
C
C++
Go
Java
JavaScript
JavaScript SSR
.NET/C#
Node.js
Objective-C
PHP
Python
React Native
Ruby
Swift
Rox.setCustomStringProperty("userName", user.getName()); Rox.setCustomBooleanProperty("isPayingUser", user.isPayingUser()); Rox.setCustomComputedStringProperty("userNameComputed", new CustomPropertyGenerator<String>() { @Override public String generateProperty() { return database.getUserName(); } });
rox_set_custom_integer_property("intProp", 100); rox_set_custom_string_property("stringProp", "something"); rox_set_custom_boolean_property("boolProp", true); rox_set_custom_double_property("doubleProp", 3.14); rox_set_custom_semver_property("smvrProp", "3.11.0"); RoxDynamicValue *check_if_playing(void *target, RoxContext *context) { return rox_dynamic_value_create_boolean(rox_context_get(context, "playing")); } rox_set_custom_computed_boolean_property("isPlayingProperty", NULL, &check_if_playing); RoxDynamicValue *user_email(void *target, RoxContext *context) { return rox_dynamic_value_create_string_copy(rox_context_get(context, "email")); } rox_set_custom_computed_string_property("emailProperty", NULL, &user_email);
Rox::SetCustomProperty<int>("intProp", 100); Rox::SetCustomProperty<const char *>("stringProp", "something"); Rox::SetCustomProperty<bool>("boolProp", true); Rox::SetCustomProperty<double>("doubleProp", 3.14); Rox::SetCustomSemverProperty("smvrProp", "3.11.0"); class CheckIfPlaying : public Rox::CustomPropertyGeneratorInterface { public: explicit CheckIfPlaying() {} Rox::DynamicValue *operator()(Rox::Context *context) override { return rox_context_get(context, "playing"); } }; CheckIfPlaying checkIfPlaying = CheckIfPlaying(); Rox::SetCustomComputedProperty<bool>("isPlayingProperty", &CheckIfPlaying); class UserEmail : public Rox::CustomPropertyGeneratorInterface { public: explicit UserEmail() {} Rox::DynamicValue *operator()(Rox::Context *context) override { return rox_context_get(context, "email"); } }; UserEmail userEmail = UserEmail(); Rox::SetCustomComputedProperty<const char *>("emailProperty", &CheckIfPlaying);
rox.SetCustomStringProperty("serviceName", serviceName) rox.SetCustomComputedBooleanProperty("isPaying", func(context context.Context) bool { value, _ := context.Get("is_paying_user").(bool) return value })
Rox.setCustomStringProperty("serviceName", serviceName); Rox.setCustomComputedBooleanProperty("isPayingUser", new CustomPropertyGeneratorWithContext() { @Override public Boolean generateProperty(Context context) { return context.user.isPayingUser(); } });
Rox.setCustomNumberProperty("numberProperty", 100) Rox.setCustomStringProperty("serviceName", serviceName) Rox.setCustomBooleanProperty("booleanProperty", true) Rox.setCustomBooleanProperty("isPaying", () => Account.sharedInstance().paying) Rox.setCustomBooleanProperty("isPayingUser", context => context.user.isPayingUser()) Rox.setCustomStringProperty("email", () => Account.sharedInstance().email())
Rox.setCustomNumberProperty("numberProperty", 100) Rox.setCustomStringProperty("serviceName", serviceName) Rox.setCustomBooleanProperty("booleanProperty", true) Rox.setCustomBooleanProperty("isPaying", () => Account.sharedInstance().paying) Rox.setCustomBooleanProperty("isPayingUser", context => context.user.isPayingUser()) Rox.setCustomStringProperty("email", () => Account.sharedInstance().email())
Rox.SetCustomStringProperty("serviceName", serviceName); Rox.SetCustomComputedBooleanProperty("isPayingUser", (context) => { return database.user.isPayingUser(); });
Rox.setCustomNumberProperty("numberProperty", 100) Rox.setCustomStringProperty("serviceName", serviceName) Rox.setCustomBooleanProperty("booleanProperty", true) Rox.setCustomBooleanProperty("isPaying", () => Account.sharedInstance().paying) Rox.setCustomBooleanProperty("isPayingUser", context => context.user.isPayingUser()) Rox.setCustomStringProperty("email", () => Account.sharedInstance().email())
[ROXCore setCustomIntProperty:100 forKey:@"intProp"]; [ROXCore setCustomStringProperty:@"something" forKey:@"stringProp"]; [ROXCore setCustomBooleanProperty:YES forKey:@"boolProp"]; [ROXCore setCustomComputedBooleanProperty:^Bool *{ return [[Account sharedInstance] isPaying]; } forKey:@"payingUser"]; [ROXCore setCustomComputedIntProperty:^int *{ return [[Account sharedInstance] age]; } forKey:@"age"]; [ROXCore setCustomComputedStringProperty:^NSString *{ return [[Account sharedInstance] email]; } forKey:@"email"];
# simple string property Rox::setCustomStringProperty("stringProp", "something"); # simple Boolean property Rox::setCustomBooleanProperty("boolProp", true); # simple integer property Rox::setCustomIntegerProperty("intProp", 100); # simple double/float property Rox::setCustomDoubleProperty("doubleProp", 3.14); # simple semver property Rox::setCustomSemverProperty("smvrProp", "3.11.0"); # computed dtring property Rox::setCustomComputedStringProperty("userEmail", function (ContextInterface $context) { return $context->get("email"); }); # computed Boolean property Rox::setCustomComputedBooleanProperty("isPlayingProperty", function (ContextInterface $context) { return (bool)$context->get("isPlaying"); });
# simple string property Rox.set_custom_string_property('SomeKeyName', 'SomeValue') # simple Boolean computed property Rox.set_custom_boolean_property('isPaying', lambda context : context['is_paying_user'] )
Rox.setCustomNumberProperty("numberProperty", 100) Rox.setCustomStringProperty("serviceName", serviceName) Rox.setCustomBooleanProperty("booleanProperty", true) Rox.setCustomBooleanProperty("isPaying", () => Account.sharedInstance().paying) Rox.setCustomBooleanProperty("isPayingUser", context => context.user.isPayingUser()) Rox.setCustomStringProperty("email", () => Account.sharedInstance().email())
Rox::Server::RoxServer.set_custom_string_property("serviceName", serviceName) # simple Boolean computed property Rox::Server::RoxServer.set_custom_boolean_property('isPaying') do |context| context['is_paying_user'] == true end
ROX.setCustomProperty(key:"intProp", value: 100) ROX.setCustomProperty(key:"stringProp", value: "something") ROX.setCustomProperty(key:"boolProp", value: true) ROX.setCustomProperty(key: "isPaying") { () -> Bool in return Account.sharedInstance().paying } ROX.setCustomProperty(key: "email") { () -> String in return Account.sharedInstance().email }

Usage examples: Implicit custom properties

A dynamic custom property rule handler is called when an explicit custom property definition does not exist in the platform.

If you do not define this rule handler, the default function is activated, which tries to extract the property value from the context by its name.

A generic implementation of that handler is described by the following:

(propName, context) => context ? context[propName] : undefined

Create this handler for implicit custom properties, depending on the code language of your connected application:

Android
C
C++
Go
Java
JavaScript
JavaScript SSR
Node.js
Objective-C
PHP
Python
React Native
Ruby
Swift
options := server.NewRoxOptions(server.RoxOptionsBuilder{ DynamicPropertyRuleHandler: func(args model.DynamicPropertyRuleHandlerArgs) interface{} { if args.Context != nil { if account := args.Context.Get("account"); account != nil { return account.(map[string]interface{})[args.PropName] } } return nil }, }) <-rox.Setup(<YOUR-SDK-KEY>, options)
RoxDynamicValue *dynamic_rule(char *propName, void *target, RoxContext *context) { return rox_context_get(context, propName); } RoxOptions *options = rox_options_create(); rox_options_set_dynamic_properties_rule(options, NULL, &dynamic_rule);
class DynamicPropertyRule : public Rox::DynamicPropertiesRuleInterface { public: explicit DynamicPropertiesRuleInterface() {} public: DynamicValue *Invoke(const char *propName, Context *context) override { return rox_context_get(context, propName); } }; int main(int argc, char **argv) { DynamicPropertyRule dynamicPropertyRule = DynamicPropertyRule(); Rox::Options *options = Rox::OptionsBuilder() .SetDynamicPropertiesRule(&dynamicPropertyRule) .Build(); Rox::Setup(<YOUR-SDK-KEY>, options); Rox::Shutdown(); }
options := server.NewRoxOptions(server.RoxOptionsBuilder{ DynamicPropertyRuleHandler: func(args model.DynamicPropertyRuleHandlerArgs) interface{} { if args.Context != nil { if account := args.Context.Get("account"); account != nil { return account.(map[string]interface{})[args.PropName] } } return nil }, }) <-rox.Setup(<YOUR-SDK-KEY>, options)
RoxOptions options = new RoxOptions.Builder() .withDynamicPropertyRule(new DynamicPropertyRule() { @Override public Object invoke(String propName, Context context) { return (context != null) ? ((Map<String, Object>)context.get("account")).get(propName) : null; } }) .build(); Rox.setup(<YOUR-SDK-KEY>, options);
const options = { dynamicPropertyRuleHandler: (propName, context) => getLoggedInUser().properties[propName] }; Rox.setup(<YOUR-SDK-KEY>, options);
const options = { dynamicPropertyRuleHandler: (propName, context) => getLoggedInUser().properties[propName] }; Rox.setup(<YOUR-SDK-KEY>, options);
const options = { dynamicPropertyRuleHandler: (propName, context) => { if (propName.startsWith('account.')) { const accountProp = propName.split(".")[0]; return context['account'].properties[accountProp]); } return null; } } Rox.setup(<YOUR-SDK-KEY>, options);
ROXOptions *options = [[ROXOptions alloc] init]; options.dynamicPropertiesRule = ^NSObject *(NSString *propName, ROXDynamicPropertyContext context) { return context(propName); }; [ROXCore setupWithKey:<YOUR-SDK-KEY> options:options];
use Rox\Core\Context\ContextInterface; use Rox\Server\Rox; use Rox\Server\RoxOptions; use Rox\Server\RoxOptionsBuilder; $options = new RoxOptions((new RoxOptionsBuilder()) ->setDynamicPropertiesRule(function ($propName, ContextInterface $ctx) { return $ctx->get($propName); })); Rox::setup(<YOUR-SDK-KEY>, $options);
def handler(prop_name, context): if prop_name.startswith('account.'): account_prop = prop_name.split('.')[0] return context['account'].properties[account_prop] return None options = RoxOptions(dynamic_property_rule_handler=handler) Rox.setup(<YOUR-SDK-KEY>, options)
const options = { dynamicPropertyRuleHandler: (propName, context) => getLoggedInUser().properties[propName] }; Rox.setup(<YOUR-SDK-KEY>, options);
require 'rox/server/rox_server' dynamic_property_rule_handler = proc do |prop_name, context| context ? context.get[prop_name] : nil end options = Rox::Server::RoxOptions.new( dynamic_property_rule_handler: dynamic_property_rule_handler ) Rox::Server::RoxServer.setup("<YOUR-SDK-KEY>", options).join
func dynamicPropertiesRule(propName:String, context:ROXDynamicPropertyContext) -> NSObject! { return context(propName) } let options = RoxOptions(); options.dynamicPropertiesRule = dynamicPropertiesRule ROX.setup(withKey: <YOUR-SDK-KEY>, options: options);
If you are using a client-side SDK, and the value of a custom property changes while the app is running, such as after a user signs in, you may need to use Rox.unfreeze().

Built-in targeting properties

The following table lists built-in targeting, in the format of rox.<attribute name>:

Table 1. Built-in target group attributes
Attribute name Description Data type

rox.app_release

Application release version.

SemVer

rox.distinct_id

Universally unique identifier (UUID).

String

rox.language

ISO 639 two-letter language code. For example, "en" for English, "es" for Spanish, and "zh" for Chinese.

String

rox.now

Current time.

DateTime

rox.platform

Code language or framework name. For example, "Python" or "Gradle".

String

rox.screen_height

Screen height in pixels.

Number

rox.screen_width

Screen width in pixels.

Number

Use regular expressions

Regular expression (regEx) matching is an Operator option in target group creation.

Use regular expressions
Figure 2. An example of using regEx matching in the target group UI.

The underlying code on the SDK is:

Go
Java
JavaScript
JavaScript SSR
.NET/C#
Node.js
PHP
Python
React Native
Ruby
import ( "regexp" ) boolean regexMatch(String str, String pattern){ matched, _ := regexp.MatchString(pattern, str) return matched; }
boolean regexMatch(String str, String pattern){ int options = 0; Pattern r = Pattern.compile(pattern, options); Matcher m = r.matcher(str); return m.find() }
function regexMatch(str, pattern) { var regex = new RegExp(pattern, ""); var match = regex.exec(str); return match ? true : false; }
function regexMatch(str, pattern) { var regex = new RegExp(pattern, ""); var match = regex.exec(str); return match ? true : false; }
bool regexMatch(String str, String pattern){ RegexOptions options = 0; Match match = Regex.Match(str, pattern, options); return match.Success; }
function regexMatch(str, pattern) { var regex = new RegExp(pattern, ""); var match = regex.exec(str); return match ? true : false; }
preg_match(pattern, src);
import re # In the above example # str is the email property value # pattern is "@cloudbees\.com$" re.search(pattern, src, 0)
function regexMatch(str, pattern) { var regex = new RegExp(pattern, ""); var match = regex.exec(str); return match ? true : false; }
# In the above example # str is the email property value # pattern is "@cloudbees\.com$" matched = !Regexp.new(pattern, 0).match(src).nil?

The following provides more information:

expect(parser.evaluateExpression('match("111", "222"')).toEqual(false); expect(parser.evaluateExpression('match("22222", ".*")')).toEqual(true); expect(parser.evaluateExpression('match("22222", "^2*$")')).toEqual(true); expect(parser.evaluateExpression('match("[email protected]", ".*(com|ca)")')).toEqual(true); expect(parser.evaluateExpression('match("[email protected]", ".*car\\.com$")')).toEqual(true); expect(parser.evaluateExpression('match("US", ".*IL|US")')).toEqual(true); expect(parser.evaluateExpression('match("US", "IL|US")')).toEqual(true); expect(parser.evaluateExpression('match("US", "(IL|US)")')).toEqual(true);

Manage custom properties in the UI

Create your own custom properties in the UI that can be used in target groups or as criteria for feature releases.

Access custom properties

To access custom properties, select Feature management  Custom properties. Search for a specific custom property by entering all or part of a property name into Search.

Select Sort icon or Sort icon next to a heading on the custom property list to sort ascending or descending, respectively, on that heading. Alternatively, sort by selecting a heading from the Sort by options.

Create a custom property

To create a custom property in the UI:

  1. Select Feature management  Custom properties.

  2. Select Create custom property.

  3. Enter a Name.

  4. (Optional) Enter a Description.

  5. Select a data type from the options.

  6. Select Save.

The custom property is created accordingly.

Update a custom property

You cannot update the custom property Name or Data type, but you can update the optional Description.

To update a custom property description:

  1. Select Feature management  Custom properties.

  2. Select Vertical ellipsis next to the property you want to update.

  3. Select Edit property, and update or delete the description.

  4. Select Update.

The custom property is updated accordingly.

Manage target groups in the UI

Create, update, and delete configured target groups for an organization/sub-organization.

Access target groups

You can search in and sort the list of target groups.

To access target groups:

  1. Select Feature management  Target groups.

  2. (Optional) Search for a specific target group, by entering all or part of a target group name into Search.

  3. (Optional) Select an option in Sort by to sort the target group list by either ASCENDING or DESCENDING alphanumerical sort order.

The target groups are displayed according to your search or sort criteria.

Create a target group

Use custom properties or built-in attributes (ROX PROPERTIES) to define user segments. You can also create targeting dependent on other target groups.

You can only create nested statements based on target group options as long as there are no circular dependencies on other target groups.
  1. Select Feature management  Target groups.

  2. Select Create target group.

  3. Enter a Name, an optional Description, and select one of the following conditions:

  4. (Optional) Select the Property condition.

    1. Select a Property name from the options.

    2. Select an Operator from the options (including regular expression matching).

    3. Enter a Value, if the property type is not a Boolean.

      Select your keyboard Enter/Return key after entering each value to add multiple values when the equals one of operator is selected.
  5. (Optional) Select the Target group condition.

    1. Select a Matches …​ logical operator option.

    2. Select a Target group from the options.

  6. (Optional) Select Add condition to add more conditions.

    1. Select one of the following logical operator options:

      • Matches all the following (AND operator)

      • Matches any of the following (OR operator)

        All subsequent conditions use the same logical operator.
    2. Configure either a Property or a Target group condition.

  7. Select Save when all conditions are added.

Your target group is created, and listed in Target groups.

Select the next to a condition to remove it. However, you must have at least one condition.
Example target group
Figure 3. An example target group created with multiple conditions.

Update a target group

  1. Select Feature management  Target groups.

  2. Select Vertical ellipsis next to the target group you want to delete.

  3. Select Edit, and then make the desired updates.

  4. Select Save.

The target group is updated accordingly.

Delete a target group

Delete any unused target group.

Before you can delete a target group, you must first remove all references to it in all flag configurations.

A deleted target group is completely removed from the CloudBees platform, including all environments, and deletion is irreversible.

To delete a target group:

  1. Select Feature management  Target groups.

  2. Select Vertical ellipsis next to the target group you want to delete.

  3. Select Delete.

  4. Remove all references to the target group in all flag configurations, if you have not already done so.

    1. Select the Down icon next to each displayed environment.

    2. For each flag in each environment, select Edit configuration.

      Remove target group
      Figure 4. Select Edit configuration to remove the target group reference.
    3. Remove the target group reference.

    4. Return to the target group you want to delete and select Delete.

  5. Select Delete.

The selected target group is deleted and removed from the target group list.