Properties and custom properties

8 minute read

Create properties using Custom properties in code or Built-in targeting properties properties defined in the code. All property configurations are scoped to the application where they are defined. They are not shared across applications.

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. This is the value set in the SDK’s roxOptions.version during setup.

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

Learn more about defining properties in the SDK references, and using properties to configure flags.

Create custom properties in either the UI or in code.

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 or directly within feature flag configurations, then run the app to display the property options in the UI.

Custom properties are not limited to target groups; they can also be used individually inside a feature flag configuration. This allows for more granular flag targeting without requiring a predefined target group. For example, you can apply a custom property directly within flag rules to evaluate specific conditions based on user attributes.

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('targetDate', getTargetDate())
The setCustomDateTimeProperty method is available in JavaScript-based SDKs when using CloudBees platform. Refer to your SDK documentation for DateTime property support in other languages.
Target group properties dropdown
Figure 1. Custom properties options.

You can define custom properties using two approaches: explicit or implicit.

  • Explicit custom properties are defined directly in your code by calling specific SDK methods for each property, giving you full control over property names, types, and values. This approach is recommended when you have a known set of properties that remain relatively stable.

  • Implicit custom properties use a dynamic property rule handler that resolves properties at runtime from a context object, without pre-defining them in code. This approach is useful when you need flexibility to add new properties without code changes, or when properties are provided dynamically from external systems or user sessions.

Usage examples: Explicit custom properties

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

Android (Java/Kotlin)
C
C++
Go
Java
JavaScript
JavaScript SSR
.NET/C#
Node.js
Objective-C
PHP
Python
React Native
Ruby
Swift
public class Example { public void initCustomProperties(User user, Database database) { 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: CheckIfPlaying() = default; Rox::DynamicValue *operator()(Rox::Context *context) override { return rox_context_get(context, "playing"); } }; CheckIfPlaying checkIfPlaying; Rox::SetCustomComputedProperty<bool>("isPlayingProperty", &checkIfPlaying); class UserEmail : public Rox::CustomPropertyGeneratorInterface { public: UserEmail() = default; Rox::DynamicValue *operator()(Rox::Context *context) override { return rox_context_get(context, "email"); } }; UserEmail userEmail; Rox::SetCustomComputedProperty<const char *>("emailProperty", &userEmail);
rox.SetCustomStringProperty("serviceName", serviceName) rox.SetCustomComputedBooleanProperty("isPaying", func(context context.Context) bool { value, _ := context.Get("is_paying_user").(bool) return value })
public class Example { public void initCustomProperties() { // simple String property Rox.setCustomStringProperty("serviceName", serviceName); // simple Boolean computed property 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 string 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('stringProp', 'something') # simple Boolean property Rox.set_custom_boolean_property('boolProp', True) # simple integer property Rox.set_custom_integer_property('intProp', 100) # simple double/float property Rox.set_custom_double_property('doubleProp', 3.14) # simple semver property Rox.set_custom_semver_property('smvrProp', '3.11.0') # computed string property Rox.set_custom_computed_string_property('userEmail', lambda context: context.get('email')) # computed Boolean property Rox.set_custom_computed_boolean_property('isPlayingProperty', lambda context: context.get('isPlaying'))
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 |prop_name, 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
public class Example { public void setup() { RoxOptions options = new RoxOptions.Builder() .withDynamicPropertyRule((propName, context) -> { if (context == null) { return null; } Object accountObj = context.get("account"); if (accountObj instanceof Map) { Map<String, Object> account = (Map<String, Object>) accountObj; if (account.containsKey(propName)) { return account.get(propName); } } return context.get(propName); }) .build(); Rox.setup("<YOUR-SDK-KEY>", options); } }
RoxDynamicValue *dynamic_rule(char *propName, void *target, RoxContext *context) { RoxDynamicValue *account = rox_context_get(context, "account"); if (account != NULL) { RoxMap *account_map = rox_dynamic_value_get_map(account); if (account_map != NULL) { RoxDynamicValue *prop = rox_map_get(account_map, propName); if (prop != NULL) { return prop; } } } 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 DynamicPropertyRule() {} public: DynamicValue *Invoke(const char *propName, Context *context) override { RoxDynamicValue *account = rox_context_get(context, "account"); if (account != NULL) { RoxMap *account_map = rox_dynamic_value_get_map(account); if (account_map != NULL) { RoxDynamicValue *prop = rox_map_get(account_map, propName); if (prop != NULL) { return prop; } } } return rox_context_get(context, propName); } }; int main(int argc, char **argv) { 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 { return nil } if account := args.Context.Get("account"); account != nil { if accountMap, ok := account.(map[string]interface{}); ok { if val, exists := accountMap[args.PropName]; exists { return val } } } return args.Context.Get(args.PropName) }, }) <-rox.Setup("<YOUR-SDK-KEY>", options)
import java.util.Map; public class Example { public void setup() { RoxOptions options = new RoxOptions.Builder() .withDynamicPropertyRule(new DynamicPropertyRule() { @Override public Object invoke(String propName, Object context) { if (!(context instanceof Map)) { return null; } Map<String, Object> ctx = (Map<String, Object>) context; Object accountObj = ctx.get("account"); if (accountObj instanceof Map) { Map<String, Object> account = (Map<String, Object>) accountObj; if (account.containsKey(propName)) { return account.get(propName); } } return ctx.get(propName); } }) .build(); Rox.setup("<YOUR-SDK-KEY>", options); } }
const options = { dynamicPropertyRuleHandler: (propName, context) => { if (!context) return null; const account = context.account; if (account && typeof account === 'object' && propName in account) { return account[propName]; } return context[propName] ?? null; } }; Rox.setup("<YOUR-SDK-KEY>", options);
const options = { dynamicPropertyRuleHandler: (propName, context) => { if (!context) return null; const account = context.account; if (account && typeof account === 'object' && propName in account) { return account[propName]; } return context[propName] ?? null; } }; Rox.setup("<YOUR-SDK-KEY>", options);
const options = { dynamicPropertyRuleHandler: (propName, context) => { if (!context) return null; const account = context.account; if (account && typeof account === 'object' && propName in account) { return account[propName]; } return context[propName] ?? null; } }; Rox.setup("<YOUR-SDK-KEY>", options);
ROXOptions *options = [[ROXOptions alloc] init]; options.dynamicPropertiesRule = ^NSObject *(NSString *propName, ROXDynamicPropertyContext context) { NSDictionary *ctx = (NSDictionary *)context(nil); if (!ctx) { return nil; } NSDictionary *account = ctx[@"account"]; if ([account isKindOfClass:[NSDictionary class]]) { return account[propName]; } return ctx[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) { $account = $ctx->get('account'); if ($account && is_array($account)) { if (isset($account[$propName])) { return $account[$propName]; } } return $ctx->get($propName); }) ); Rox::setup("<YOUR-SDK-KEY>", $options);
from rox.server import Rox, RoxOptions def dynamic_property_rule_handler(prop_name, context): """ Custom handler for dynamic properties. First checks if property exists in account object, then falls back to context root. """ if context is None: return None # Check if property exists in account object account = context.get("account") if account and isinstance(account, dict) and prop_name in account: return account[prop_name] # Fall back to context root return context.get(prop_name) # Configure Rox with dynamic property handler options = RoxOptions(dynamic_property_rule_handler=dynamic_property_rule_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| return nil unless context account = context['account'] if account && account.is_a?(Hash) && account.key?(prop_name) return account[prop_name] end context[prop_name] 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! { guard let ctx = context(nil) as? [String: Any] else { return nil } if let account = ctx["account"] as? [String: Any], let value = account[propName] as? NSObject { return value } return ctx[propName] as? NSObject } 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), call Rox.unfreeze() to re-evaluate feature flags with the updated property values. This ensures flags are recalculated based on the current property state.

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 the Search field.

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.

Delete a custom property

A custom property cannot be deleted while it is still in use. You must first remove all references to it, such as conditions or configurations that use the property. Once all references are removed, the property can be safely deleted.

To delete a custom property:

  1. Select Feature management  Custom properties.

  2. Locate the custom property to be deleted.

  3. Select Vertical ellipsis next to the property.

  4. Select Delete.

If the property is in use

When the Delete (custom property) dialogue appears and shows the property is still in use, do the following:

  1. Review the environments where the property is being used.

  2. Select the Edit flag configuration link to open the configuration interface.

  3. Review how the property is applied in feature flag rules.

  4. Note that the Delete option is disabled until all references are removed.

    View the Delete (custom property) dialogue
    Flag is scoped to an application
    Figure 2. Delete (custom property) dialogue
  5. Remove references by doing one of the following:

    • To remove a full configuration, including all conditions within the configuration, select Delete next to the configuration.

      Delete a configuration
      Figure 3. Delete a configuration
    • To remove individual conditions, select Trash can next to the conditions to be deleted.

      Delete a single condition
      Figure 4. Delete a single condition
  6. Select Save configuration to confirm changes.

  7. Repeat for all environments and configurations where the property is used.

  8. After removing all references, return to the custom properties list and attempt deletion again.

If the property has no references

When the Safely delete (custom property) dialogue appears, all references have been removed:

  1. Review the confirmation message.

  2. Select Delete to permanently remove the custom property from CloudBees platform.

    Flag is shared among environments
    Figure 5. Safely delete custom property dialogue

Once deleted, the custom property cannot be restored.

For configuring user segmentation with target groups, refer to Target groups.