Skip to content

Creating feature flags

Feature flags allow you to remotely enable or disable functionality for a selection of users. Create a new feature flag within the Braze dashboard. Provide a name and an ID, a target audience, and a percentage of users for whom to enable to this feature. Then, using that same ID in your app or website’s code, you can conditionally run certain parts of your business logic.

Looking to learn more about what feature flags are and how you can use them in Braze? Check out About feature flags before proceeding.

Prerequisites

To use feature flags, ensure your SDKs are up to date with at least these minimum versions:

Implement feature flags in the dashboard

Create, edit, and archive feature flags from Messaging > Feature Flags. This page displays a list of existing feature flags for this workspace.

A list of previously created feature flags on the Braze dashboard

Access permissions

You must have “Manage Feature Flags” permission to view, create, or edit feature flags. To view the list of available feature flags, you must have the “Access Campaigns, Canvases, Cards, Feature Flags, Segments, Media Library” permission.

Manage Feature Flags permission

Create a new feature flag

To create a new feature flag, click the Create Feature Flag button. Then, define your feature flag’s details, properties, user targeting, and rollout traffic.

A blank feature flag form

Details

Give your new feature flag a Name and ID.

  • The Name field allows you to provide a human-readable title for this feature flag that will be used by marketers and administrators.
  • The ID field will be referenced in your code to determine whether the feature is enabled for a particular user. This must be unique and cannot be modified after it’s created.
  • The Description field is an optional field that allows you to provide additional context around this feature flag.

Choose an ID thoughtfully as it will be used as you develop your feature. Practice good naming conventions to ensure that your code is readable by your colleagues (and your future self).

For example, it’s common to use a naming convention of {verb}_{product}_{feature}, such as enable_rider_new_profile_page to make it clear what enabling the feature flag does.

Properties

Custom properties can be defined as part of your feature flag. These properties will be accessible by your app through the Braze SDK when the feature is enabled. Defining properties is an optional step.

Variables can be strings, boolean values, or numbers. Define both the variable key and default value for each property.

Example properties

For example, if we are defining a feature flag that shows an out-of-stock banner for our ecommerce store, we might set the following properties, which our app will use when displaying the banner:

Property Name Type Value
banner_height number 75
banner_color string blue
banner_text string Widgets are out of stock until July 1.
dismissible boolean false

Targeting

To begin the rollout of a feature flag, you must choose a particular segment of users.

Use the Add Filter dropdown menu to filter users out of your target audience. Add multiple filters to narrow your audience.

Two dropdown menus. The first reads Target Users by Segment. The second reads Additional Filters.

Rollout traffic

Feature flags always start as turned off to allow you to separate the timing of the feature’s release and activation in your users’ experience.

When you are ready to rollout your new feature, specify an audience and then use the Rollout Traffic slider to define the random percentage of your targeted user base to receive the new feature. Set the Rollout Traffic slider to set a percentage between 0% (no users) and 100% (the entire target audience).

A slider labeled Rollout Traffic, spanning between 0 and 100.

Check if the feature flag is enabled within your application

Once you have defined your feature flag, configure your app or site to check whether or not it is enabled for a particular user. When it is enabled, you’ll set some action or reference the feature flag’s variable properties based on your use case. The Braze SDK provides getter methods to pull your feature flag’s status and its properties into your app.

Feature flags are refreshed automatically at session start so that you can display the most up-to-date version of your feature upon launch. The SDK caches these values so they can be used while offline.

Let’s say you were to rolling out a new type of user profile for your app. You might set the ID as expanded_user_profile. Then, you would have your app check to see if it should display this new user profile to a particular user. For example:

1
2
3
4
5
6
const featureFlag = braze.getFeatureFlag("expanded_user_profile");
if (featureFlag.enabled) {
  console.log(`expanded_user_profile is enabled`);
} else {
  console.log(`expanded_user_profile is not enabled`);
}
1
2
3
4
5
6
let featureFlag = braze.featureFlags.featureFlag(id: "expanded_user_profile")
if featureFlag.enabled {
  print("expanded_user_profile is enabled")
} else {
  print("expanded_user_profile is not enabled")
}
1
2
3
4
5
6
FeatureFlag featureFlag = braze.getFeatureFlag("expanded_user_profile");
if (featureFlag.getEnabled()) {
  Log.i(TAG, "expanded_user_profile is enabled");
} else {
  Log.i(TAG, "expanded_user_profile is not enabled");
}
1
2
3
4
5
6
val featureFlag = braze.getFeatureFlag("expanded_user_profile")
if (featureFlag.enabled) {
  Log.i(TAG, "expanded_user_profile is enabled.")
} else {
  Log.i(TAG, "expanded_user_profile is not enabled.")
}
1
2
3
4
5
6
const featureFlag = await Braze.getFeatureFlag("expanded_user_profile");
if (featureFlag.enabled) {
  console.log(`expanded_user_profile is enabled`);
} else {
  console.log(`expanded_user_profile is not enabled`);
}
1
2
3
4
5
6
var featureFlag = Appboy.AppboyBinding.GetFeatureFlag("expanded_user_profile");
if (featureFlag.Enabled) {
  Console.WriteLine("expanded_user_profile is enabled");
} else {
  Console.WriteLine("expanded_user_profile is not enabled");
}
1
2
3
4
5
6
const featureFlag = await BrazePlugin.getFeatureFlag("expanded_user_profile");
if (featureFlag.enabled) {
  console.log(`expanded_user_profile is enabled`);  
} else {
  console.log(`expanded_user_profile is not enabled`);
}
1
2
3
4
5
6
BrazeFeatureFlag featureFlag = await braze.getFeatureFlagByID("expanded_user_profile");
if (featureFlag.enabled) {
  print("expanded_user_profile is enabled");
} else {
  print("expanded_user_profile is not enabled");
}
1
2
3
4
5
6
featureFlag = m.braze.getFeatureFlag("expanded_user_profile")
if featureFlag.enabled
  print "expanded_user_profile is enabled"
else
  print "expanded_user_profile is not enabled"
end if

Logging a feature flag impression

Track a feature flag impression whenever a user has had an opportunity to interact with your new feature, or when they could have interacted if the feature is disabled (in the case of a control group in an A/B test).

Usually, you can put this line of code directly underneath where you reference your feature flag in your app:

1
braze.logFeatureFlagImpression("expanded_user_profile");
1
braze.featureFlags.logImpression(id: "expanded_user_profile")
1
braze.logFeatureFlagImpression("expanded_user_profile");
1
braze.logFeatureFlagImpression("expanded_user_profile")
1
Braze.logFeatureFlagImpression("expanded_user_profile");
1
Appboy.AppboyBinding.LogFeatureFlagImpression("expanded_user_profile");
1
BrazePlugin.logFeatureFlagImpression("expanded_user_profile");
1
braze.logFeatureFlagImpression("expanded_user_profile");
1
m.Braze.logFeatureFlagImpression("expanded_user_profile");

Accessing properties

To access the properties of a feature flag, use one of the following methods depending on the type you defined in the dashboard.

If a feature flag is not enabled, or a property you reference does not exist, these methods will return null.

1
2
3
4
5
6
7
8
// Feature flag instance
const featureFlag = braze.getFeatureFlag("expanded_user_profile");
// String properties
const stringProperty = featureFlag.getStringProperty("color");
// Boolean properties
const booleanProperty = featureFlag.getBooleanProperty("expanded");
// Number properties
const numberProperty = featureFlag.getNumberProperty("height");
1
2
3
4
5
6
7
8
// Feature flag instance
let featureFlag: FeatureFlag = braze.featureFlags.featureFlag(id: "expanded_user_profile")
// String properties
let stringProperty: String? = featureFlag.stringProperty(key: "color")
// Boolean properties
let booleanProperty: Bool? = featureFlag.boolProperty(key: "expanded")
// Number properties
let numberProperty: Double? = featureFlag.numberProperty(key: "height")
1
2
3
4
5
6
7
8
// Feature flag instance
FeatureFlag featureFlag = braze.getFeatureFlag("expanded_user_profile");
// String properties
String stringProperty = featureFlag.getStringProperty("color");
// Boolean properties
Boolean booleanProperty = featureFlag.getBooleanProperty("expanded");
// Number properties
Number numberProperty = featureFlag.getNumberProperty("height");
1
2
3
4
5
6
7
8
// feature flag instance
val featureFlag = braze.getFeatureFlag("expanded_user_profile")
// string properties
val stringProperty = featureFlag.getStringProperty("color")
// boolean properties
val booleanProperty = featureFlag.getBooleanProperty("expanded")
// number properties
val numberProperty = featureFlag.getNumberProperty("height")
1
2
3
4
5
6
// String properties
const stringProperty = await Braze.getFeatureFlagStringProperty("expanded_user_profile", "color");
// Boolean properties
const booleanProperty = await Braze.getFeatureFlagBooleanProperty("expanded_user_profile", "expanded");
// Number properties
const numberProperty = await Braze.getFeatureFlagNumberProperty("expanded_user_profile", "height");
1
2
3
4
5
6
7
8
9
10
// Feature flag instance
var featureFlag = Appboy.AppboyBinding.GetFeatureFlag("expanded_user_profile");
// String properties
var stringProperty = featureFlag.getStringProperty("color");
// Boolean properties
var booleanProperty = featureFlag.getBooleanProperty("expanded");
// Number property as integer
var integerProperty = featureFlag.getIntegerProperty("height");
// Number property as double
var doubleProperty = featureFlag.getDoubleProperty("height");
1
2
3
4
5
6
// String properties
const stringProperty = await BrazePlugin.getFeatureFlagStringProperty("expanded_user_profile", "color");
// Boolean properties
const booleanProperty = await BrazePlugin.getFeatureFlagBooleanProperty("expanded_user_profile", "expanded");
// Number properties
const numberProperty = await BrazePlugin.getFeatureFlagNumberProperty("expanded_user_profile", "height");
1
2
3
4
5
6
7
BrazeFeatureFlag featureFlag = await braze.getFeatureFlagByID("expanded_user_profile");
// String properties
var stringProperty = featureFlag.getStringProperty("color");
// Boolean properties
var booleanProperty = featureFlag.getBooleanProperty("expanded");
// Number properties
var numberProperty = featureFlag.getNumberProperty("height");
1
2
3
4
5
6
' String properties
color = featureFlag.getStringProperty("color")
' Boolean properties
expanded = featureFlag.getBooleanProperty("expanded")
' Number properties
height = featureFlag.getNumberProperty("height")

You can also get a list of all enabled feature flags:

1
2
3
4
const features = getAllFeatureFlags();
for(const feature of features) {
  console.log(`Feature: ${feature.id}`, feature.enabled);
}
1
2
3
4
let features = braze.featureFlags.featureFlags
for let feature in features {
  print("Feature: \(feature.id)", feature.enabled)
}
1
2
3
4
List<FeatureFlag> features = braze.getAllFeatureFlags();
for (FeatureFlag feature: features) {
  Log.i(TAG, "Feature: ", feature.getId(), feature.getEnabled());
}
1
2
3
4
val featureFlags = braze.getAllFeatureFlags()
featureFlags.forEach { feature ->
  Log.i(TAG, "Feature: ${feature.id} ${feature.enabled}")
}
1
2
3
4
const features = await Braze.getAllFeatureFlags();
for(const feature of features) {
  console.log(`Feature: ${feature.id}`, feature.enabled);
}
1
2
3
4
List<FeatureFlag> features = Appboy.AppboyBinding.GetAllFeatureFlags();
foreach (FeatureFlag feature in features) {
  Console.WriteLine("Feature: {0} - enabled: {1}", feature.ID, feature.Enabled);
}
1
2
3
4
const features = await BrazePlugin.getAllFeatureFlags();
for(const feature of features) {
  console.log(`Feature: ${feature.id}`, feature.enabled);
}
1
2
3
4
List<BrazeFeatureFlag> featureFlags = await braze.getAllFeatureFlags();
featureFlags.forEach((feature) {
  print("Feature: ${feature.id} ${feature.enabled}");
});
1
2
3
4
features = m.braze.getAllFeatureFlags()
for each feature in features
      print "Feature: " + feature.id + " enabled: " + feature.enabled.toStr()
end for

Refresh feature flags

You can refresh the current user’s feature flags mid-session to pull the latest values from Braze.

1
2
3
4
5
braze.refreshFeatureFlags(() => {
  console.log(`Feature flags have been refreshed.`);
}, () => {
  console.log(`Failed to refresh feature flags.`);
});
1
2
3
4
5
6
7
8
braze.featureFlags.requestRefresh { result in
  switch result {
  case .success(let features):
    print("Feature flags have been refreshed:", features)
  case .failure(let error):
    print("Failed to refresh feature flags:", error)
  }
}
1
braze.refreshFeatureFlags();
1
braze.refreshFeatureFlags()
1
Braze.refreshFeatureFlags();
1
Appboy.AppboyBinding.RefreshFeatureFlags();
1
BrazePlugin.refreshFeatureFlags();
1
braze.refreshFeatureFlags();
1
m.Braze.refreshFeatureFlags()

Listen for changes

You can configure the Braze SDK to listen and update your app when the SDK refreshes any feature flags.

This is useful if you want to update your app if a user is no longer eligible for a feature. For example, setting some state in your app based on whether or not a feature is enabled, or one of its property values.

1
2
3
4
5
6
// Register an event listener
const subscriptionId = braze.subscribeToFeatureFlagsUpdates((features) => {
  console.log(`Features were updated`, features);
});
// Unregister this event listener
braze.removeSubscription(subscriptionId);
1
2
3
4
5
6
7
// Create the feature flags subscription
// - You must keep a strong reference to the subscription to keep it active
let subscription = braze.featureFlags.subscribeToUpdates { features in
  print("Feature flags were updated:", features)
}
// Cancel the subscription
subscription.cancel()
1
2
3
4
5
6
braze.subscribeToFeatureFlagsUpdates(event -> {
  Log.i(TAG, "Feature flags were updated.");
  for (FeatureFlag feature: event.getFeatureFlags()) {
    Log.i(TAG, "Feature: ", feature.getId(), feature.getEnabled());
  }
});
1
2
3
4
5
6
braze.subscribeToFeatureFlagsUpdates() { event ->
  Log.i(TAG, "Feature flags were updated.")
  event.featureFlags.forEach { feature ->
    Log.i(TAG, "Feature: ${feature.id}")
  }
}
1
2
3
4
// Register an event listener
Braze.addListener(braze.Events.FEATURE_FLAGS_UPDATED, (featureFlags) => {
  console.log(`featureFlagUpdates`, JSON.stringify(featureFlags));
});

To listen for changes, set the values for Game Object Name and Callback Method Name under Braze Configuration > Feature Flags to the corresponding values in your application.

1
2
3
4
// Register an event listener
BrazePlugin.subscribeToFeatureFlagUpdates((featureFlags) => {
    console.log(`featureFlagUpdates`, JSON.stringify(featureFlags));
});

In the Dart code in your app, use the following sample code:

1
2
3
4
5
6
7
8
9
// Create stream subscription
StreamSubscription featureFlagsStreamSubscription;

featureFlagsStreamSubscription = braze.subscribeToFeatureFlags((featureFlags) {
  print("Feature flags were updated");
});

// Cancel stream subscription
featureFlagsStreamSubscription.cancel();

Then, make these changes in the iOS native layer as well. Note that there are no additional steps needed on the Android layer.

  1. Implement featureFlags.subscribeToUpdates to subscribe to feature flag updates as described in the subscribeToUpdates documentation.

  2. Your featureFlags.subscribeToUpdates callback implementation must call BrazePlugin.processFeatureFlags(featureFlags).

For an example, see AppDelegate.swift in our sample app.

1
2
' Define a function called `onFeatureFlagChanges` to be called when feature flags are refreshed
m.BrazeTask.ObserveField("BrazeFeatureFlags", "onFeatureFlagChanges")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { useEffect, useState } from "react";
import {
  FeatureFlag,
  getFeatureFlag,
  removeSubscription,
  subscribeToFeatureFlagsUpdates,
} from "@braze/web-sdk";

export const useFeatureFlag = (id: string): FeatureFlag => {
  const [featureFlag, setFeatureFlag] = useState<FeatureFlag>(
    getFeatureFlag(id)
  );

  useEffect(() => {
    const listener = subscribeToFeatureFlagsUpdates(() => {
      setFeatureFlag(getFeatureFlag(id));
    });
    return () => {
      removeSubscription(listener);
    };
  }, [id]);

  return featureFlag;
};

Segmenting with feature flags

Braze automatically keeps track of which users are currently eligible for or participating in a feature flag. You can create a segment or target messaging using the Feature Flag filter. For more information about filtering on segments, see Creating a segment.

Best practices

Don’t combine rollouts with Canvases or experiments

To avoid users being enabled and disabled by different entry points, you should either set the rollouts slider to a value greater than zero OR enable the feature flag in a Canvas or experiment. As a best practice, if you plan to use a feature flag in a Canvas or experiment, keep the rollout percentage at zero.

Naming conventions

  • Consider following a pattern such as {product}.{feature}.{action}.
    • For example, in a ride sharing app your feature ID may be driver.profile.show_animation_v3
  • This also helps when searching for a specific product area or team’s feature flags.
  • Make sure that the default state for a feature flag is disabled in your app.
    • For example, it is an anti-pattern if you have a flag named disable_feature_xyz. There may be exceptions, but try to avoid confusing a feature’s “enabled” status with the actual enabled behavior (disabling feature XYZ).

Planning ahead

Always play it safe. When considering new features that may require an off switch, it’s better to release new code with a feature flag and not need it than it is to realize a new app update is required.

Be descriptive

Add a description to your feature flag. While this is an optional field in Braze, it can help answer questions others may have when browsing available feature flags.

  • Contact details for who is responsible for the enablement and behavior of this flag
  • When this flag should be disable
  • Links to documentation or notes about the new feature this flag controls
  • Any dependencies or notes on how to use the feature

Clean up old feature flags

We’re all guilty of leaving features on at 100% rollout for longer than necessary.

To help keep your code (and Braze dashboard) clean, remove permanent feature flags from your code base after all users have upgraded and you no longer need the option to disable the feature. This helps reduce the complexity of your development environment, but also keeps your list of feature flags tidy.

HOW HELPFUL WAS THIS PAGE?
New Stuff!