Custom properties

7 minute read

You can create your own custom properties that can be used in Target groups or in the Configuration tab as criteria for feature releases.

If you want to open a feature only for internal employees, for example, you can identify internal employees by their email domain. You can define a custom property called email and then use it when deploying the feature.

Custom properties can be created in either the CloudBees Feature Management UI or your source code. The CloudBees Feature Management custom properties reported in your source code are listed in App settings. You can also add new custom properties in the UI. If you add a custom property to your code and run/compile the application, it will automatically be available in CloudBees Feature Management. Custom properties can be sorted and filtered in the UI, and any custom property can be marked as hidden. CloudBees Feature Management suggests hiding custom properties that you are not using.

To avoid CloudBees Feature Management discrepancies, the user interface does not allow custom properties to be deleted. If you are not using a custom property, you can mark it as hidden. A hidden custom property is still available for targeting if it has already been added as a condition to a Target group or to the flag’s Configuration , but you cannot use a hidden custom property to add new conditions.

If you require a custom property to be deleted, submit a request to CloudBees Support.

Adding a custom property

You must add any custom property you create in the CloudBees Feature Management UI to your source code to use the custom property.

To add a custom property:

  1. From the CloudBees Feature Management Home page, select App settings  Custom properties.

  2. If all of your custom properties are hidden, select Create a new custom property. If you have custom properties displayed, select Add new custom property.

  3. Enter the custom property name, select the Type from the dropdown, and enter an optional description.

  4. Select Create.

Your custom property is displayed in the custom properties listing and is marked as visible.

Modifying a custom property

You can modify any custom property defined in your source code from within CloudBees Feature Management. You can change the type of a custom property from Number to String, for example, but you cannot change the custom property name.

To update a custom property in an app:

  1. From the CloudBees Feature Management Home page, select App settings  Custom properties.

  2. Select a custom property, and then update the Type and/or Description.

  3. Select Save Changes.

Your custom property is updated accordingly.

Hiding a custom property

You can control the visibility of any custom property in the list.

To hide or show a custom property from the list:

  1. From the CloudBees Feature Management Home page, select App settings  Custom properties.

  2. From the dropdown for the custom property, select Hidden or Visible.

Your custom property is displayed accordingly.

Filtering custom properties by visibility

To display both hidden and visible custom properties, select Show all. Deselect Show all to display only visible custom properties.

Custom properties example with both hidden and visible custom properties displayed
Figure 1. Custom properties example with both hidden and visible custom properties displayed

Sorting app custom properties

You can sort custom properties by name, type, or description. You cannot sort on visibility.

To sort the list of custom properties:

  1. From the CloudBees Feature Management Home page, select App settings  Custom properties.

  2. Select the column heading that you want to sort. Select the column heading again to change the sort order from ascending to descending, or vice versa.

The list of custom properties is sorted accordingly. An arrow to the right of the column heading indicates how the properties are sorted.

Server-side and some client-side SDKs allow you to use context to set a custom property value. For more information, refer to Flag context.

Explicit custom property - setCustomProperty

An explicit custom property is a custom property which is defined entirely within your code by using the CloudBees Feature Management API. The SDK supplies a function for every type of property. The available property types are as follows:

  • Boolean

  • Int

  • Double

  • String

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

The type Number is used in some platforms instead of Double and Int.

Once the above code is running you will be able to use the custom property from within the CloudBees Feature Management dashboard.

The following are examples of how to define explicit custom properties in the code on different platforms:

Swift
Objective-C
Android
React Native
JavaScript
Node.js
JavaScript SSR
JVM
.NET
Python
Go
Ruby
PHP
C
C++
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 }
[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"];
Rox.setCustomStringProperty("userName", user.getName()); Rox.setCustomBooleanProperty("isPayingUser", user.isPayingUser()); Rox.setCustomComputedStringProperty("userNameComputed", new CustomPropertyGenerator<String>() { @Override public String generateProperty() { return database.getUserName(); } });
Rox.setCustomNumberProperty("numberProperty", 100) Rox.setCustomStringProperty("stringProperty", "something") Rox.setCustomBooleanProperty("booleanProperty", true) Rox.setCustomBooleanProperty("isPaying", () => Account.sharedInstance().paying) Rox.setCustomStringProperty("email", () => Account.sharedInstance().email())
Rox.setCustomNumberProperty("numberProperty", 100) Rox.setCustomStringProperty("stringProperty", "something") 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.setCustomBooleanProperty("isPayingUser", context => context.user.isPayingUser())
import {Rox} from 'rox-ssr'; Rox.setCustomStringProperty("serviceName", serviceName) Rox.setCustomBooleanProperty("isPayingUser", context => context.user.isPayingUser())
Rox.setCustomStringProperty("serviceName", serviceName); Rox.setCustomComputedBooleanProperty("isPayingUser", new CustomPropertyGeneratorWithContext() { @Override public Boolean generateProperty(Context context) { return context.user.isPayingUser(); } });
Rox.SetCustomStringProperty("serviceName", serviceName); Rox.SetCustomComputedBooleanProperty("isPayingUser", (context) => { return database.user.isPayingUser(); });
# 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.SetCustomStringProperty("serviceName", serviceName) rox.SetCustomComputedBooleanProperty("isPaying", func(context context.Context) bool { value, _ := context.Get("is_paying_user").(bool) return value })
# simple String property 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
# 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"); });
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);

Implicit custom property - DynamicPropertiesRule

The value of that custom property is defined by the DynamicPropertiesRule in the code.

The Dynamic Custom Property Rule handler is called when an explicit custom property definition does not exist on the client side.

If you do not set the setDynamicCustomPropertyRule it will then activate the default function which tries to extract the property value from the context by its name.

A generic implementation of that handler can be described by the following snippet:

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

The following examples are code snippets to create this rule on different platforms:

  • SDKs 4.x

JavaScript
JavaScript SSR
C#
Node.js
PHP
C
C++
Rox.setDynamicCustomPropertyRule((propName, context) => getLoggedInUser().properties[propName]);
import {Rox} from 'rox-ssr'; Rox.setDynamicCustomPropertyRule((propName, context) => getLoggedInUser().properties[propName]);
RoxOptions options = new RoxOptions(new RoxOptions.RoxOptionsBuilder{ DynamicPropertiesRule = (property, context) => { //return context == null ? null : context.Get(property); <- default behavior // in case the property we use on Dashboard is a property on the user object in the context return context.Get("userData")[property]); } }); await Rox.Setup(appKey, options);
Rox.setDynamicCustomPropertyRule((propName, context) => { if (propName.startsWith('account.')) { const accountProp = propName.split(".")[0]; return context['account'].properties[accountProp]); } return null; })
$roxOptionsBuilder = (new RoxOptionsBuilder()) ->setDynamicPropertiesRule(function ($propName, ContextInterface $ctx) { return ($ctx->get("user"))->{$propName}; }); Rox::setup(ROLLOUT_KEY, new RoxOptions($roxOptionsBuilder));
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(DEFAULT_API_KEY, options); Rox::Shutdown(); }
  • SDKs 5.0 or later

JavaScript
JavaScript SSR
Node.js
Python
Android
JDK
C
C++
Go
Objective C
Swift
PHP
Ruby
const options = { dynamicPropertyRuleHandler: (propName, context) => getLoggedInUser().properties[propName] }; Rox.setup(environmentKey, options);
import {Rox} from 'rox-ssr'; const options = { dynamicPropertyRuleHandler: (propName, context) => getLoggedInUser().properties[propName] }; Rox.setup(environmentKey, options);
const options = { dynamicPropertyRuleHandler: (propName, context) => { if (propName.startsWith('account.')) { const accountProp = propName.split(".")[0]; return context['account'].properties[accountProp]); } return null; } } Rox.setup(environmentKey, 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(environment_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(this, 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(<ROLLOUT_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(DEFAULT_API_KEY, options); Rox::Shutdown(); }
options := // NEED TO PUT IN A SAMPLE DYNAMIC PROPERTY RULE HERE rox.Setup(<ROLLOUT_KEY>, options)
ROXOptions *options = [[ROXOptions alloc] init]; options.dynamicPropertiesRule = ^NSObject *(NSString *propName, ROXDynamicPropertyContext context) { return context(propName); }; [ROXCore setupWithKey:DEFAULT_API_KEY options:options];
func dynamicPropertiesRule(propName:String, context:ROXDynamicPropertyContext) -> NSObject! { return context(propName) } let options = RoxOptions(); options.dynamicPropertiesRule = dynamicPropertiesRule ROX.setup(withKey: DEFAULT_API_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(DEFAULT_API_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("<ROLLOUT_KEY>", options).join