FeatureFlags
Types
ActivationContext
interface
ActivationContext {
userId:
number?
--
The ID of the user
groups:
{
[
string
]
:
true
}
?
--
A set of groups
systemStates:
{
[
string
]
:
true
}
?
--
A set of system states
abSegments:
{
[
string
]
:
true
}
?
--
A set of AB segments
}
The ActivationContext for a feature' activation.
Represents user ID, groups, system states, and AB segments that inform feature activation. These parameters allow features to operate under different rules based on their specific context.
For instance, features may activate for specific user groups, AB segments, or under certain system states.
Default behavior is applied if a context parameter is not provided.
local userContext = {
userId = 12345, -- Replace with actual user ID
groups = { betaTesters = true }, -- User is in the 'betaTesters' group
systemStates = { lowLoad = true }, -- System is currently under low load
abSegments = { testA = true }, -- User is in the 'testA' AB segment
}
if isActive("ourFeature", userContext) then
-- Our feature is active for this particular context
end
ActivationConfig
interface
ActivationConfig {
default:
boolean?
--
Default activation status if the feature doesn't exist
allowRetire:
boolean?
--
Flag to allow retirement of the feature; if not set and the flag is retired, this notifies in the console
warnExists:
boolean?
--
Flag to warn rather than error when a feature doesn't exist
warnRetire:
boolean?
--
Flag to warn rather than error when a feature is retired
}
The configuration parameters for a feature's activation.
This determines the default state and warning behavior of a feature. This can assist with development of features behind flags, such as throwing errors when flags are being used that are no longer meant to be used.
if
isActive("newUI", userContext, {
default = true, -- Assume the feature is active if not found (do not notify)
allowRetire = false, -- Notify if the flag has been retired
warnRetire = false, -- Error if the flag is retired
})
then
-- Load the new user interface
else
-- Load the old user interface
end
InitializationOptions
interface
InitializationOptions {
synchronizationInterval?:
number
--
The interval in seconds to synchronize flags
}
Options to initialize the serialization process with.
RuleSet
interface
RuleSet {
activation?:
(
(
context?:
{
[
unknown
]
:
unknown
}
,
ruleSet?:
{
[
unknown
]
:
unknown
}
)
→
boolean
)
--
A custom activation function to evaluate
allowedUsers?:
{
[
number
]
:
true
}
--
A set of user IDs that must match
forbiddenUsers?:
{
[
number
]
:
true
}
--
A set of user IDs that may not match
allowedGroups?:
{
[
string
]
:
true
}
--
A set of groups that must match
forbiddenGroups?:
{
[
string
]
:
true
}
--
A set of groups that may not much
allowedSystemStates?:
{
[
string
]
:
true
}
--
A set of system states that must match
forbiddenSystemStates?:
{
[
string
]
:
true
}
--
A set of system states that may not match
abSegments?:
{
[
string
]
:
number
}
--
A map of AB testing segments
}
A rule set to determine if a feature should be active for a given context.
When a rule set is evaluated against a context, all parts of the rule set must be true for this rule set to pass. If no rule sets match then this rule set will not apply. See isActive for a more detailed explanation.
AB testing segments are provided as a map of segment names to segment proportions. All testing segment proportions are added together when selecting a segment to determine the total proportion rather than using a percentage. This enables more specific and configurable group proportions than strict percentages may allow.
create("abTestFlag", {
active: true,
ruleSets: {
{ -- Our AB testing rule set
abSegments = {
-- Our total proportion is 8, so we're selecting out of 8 total.
segment1 = 5, -- This segment accounts for 5/8ths of the population.
segment2 = 2, -- This segment accounts for 2/8ths, or 1/4th.
segment3 = 1, -- This segment accounts for 1/8th.
},
},
},
})
Most rules accept a boolean value for each member. This creates a set of members, but also allows for these to be defined as predicates. Predicates are functions that accept some semantic context and return a boolean value. This allows for more complex contexts to be provided without needing to create specialized logic for each case.
local userId = 1234567890
isActive("predicateFlag", {
userId = userId,
groups = {
beta = isBetaMember(userId),
campaign = isCampaignMember(userId),
criticalGeoRegion = isCriticalGeoRegion(userId),
},
})
tip
Prefer using predicates over activation functions. Predicates are easier to use and understand. The predicate logic is clear to the developer placing a feature behind a flag, and the predicate logic is clear to the developer configuring the flag.
Activation functions are less clear and can introduce magic behavior. While the activation function may be clear to the developer configuring the flag, it may not be clear to the developer placing a feature behind a flag, and may even be confusing. Activation functions should be used sparingly and only when predicates are insufficient.
FlagConfig
interface
FlagConfig {
active:
boolean
--
Whether the flag is active
retired:
boolean
--
Whether this flag is retired
}
The configuration of a flag.
Rule sets will be evaluated one at a time to determine if an activation context should have this feature active. See isActive for a more detailed explanation.
info
The active property controls whether this flag can be active for anyone. If this is false, no one will have this feature active. If a flag should be active in some circumstances, use rule sets.
PartialFlagConfig
interface
PartialFlagConfig {
active?:
boolean
--
Whether the flag is active
retired?:
boolean
--
Whether this flag is retired
}
A partial flag configuration.
This is used to update a flag. Any properties that are nil will not be updated.
ChangeRecord
interface
ChangeRecord {
}
A record of how a flag has changed.
UpdateOptions
interface
UpdateOptions {
serialize:
boolean
--
Whether this change should be serialized
}
Options for updating a flag.
These are options for how a flag should be updated. Here you can specify whether this change should be serialized. The default is false.
PartialUpdateOptions
interface
PartialUpdateOptions {
serialize?:
boolean
--
Whether this change should be serialized
}
Partial update options.
This is used to configure a flag update. Any properties that are nil will be given default values.
Properties
Changed
FeatureFlags.Changed:
Event
The flag changed event.
This fires every time a flag changes. It provides the name, a ChangeRecord, and UpdateOptions.
Changed:Connect(function(name: string, record: ChangeRecord, options: UpdateOptions)
print(string.format("Flag '%s' changed.", name))
print("Old flag:", record.old)
print("New flag:", record.new)
if options.serialize then
print("This change will be serialized.")
end
end)
Functions
isActive
FeatureFlags.
isActive
(
name:
string
,
--
The name of the feature flag to check
) →
boolean
--
Whether the feature should be active
Checks if a feature flag is active based on provided context and configuration.
The isActive
function evaluates whether a feature should be active based on
the provided user context and configuration. It normalizes these inputs, using
default values for any missing context or configuration properties.
if isActive("uiUpdate", {
userId = 1000,
groups = { beta = true },
abSegments = { newInventory = true },
}) then
-- The user with this context should have this feature active.
end
The feature flag's existence and retirement status are then checked:
-
If the feature doesn't exist, the behavior depends on the
warnExists
anddefault
configuration properties.- If
warnExists
is true, a warning is logged. - If
default
is provided, thedefault
value is returned. -
If neither a warning is logged nor a
default
value is provided, an error is thrown. - If nothing else causes the function to terminate a default value of false is returned.
if isActive("missingFlag", activationContext, { default = true, warnExists = true, }) then -- If the flag no longer exists we still execute this code. -- A warning is logged rather than an error. else -- The flag exists, but is set to false. end
- If
-
If the feature is retired, the behavior depends on the
allowRetire
andwarnRetire
configuration properties.- If
allowRetire
is false, an error is thrown. -
If
allowRetire
is true butwarnRetire
is true as well, a warning is logged.
if isActive("oldFlag", activationContext, { allowRetire = true, warnRetire = true, }) then -- A retired flag can still be checked, but a warning is logged. end
- If
The flag's active status is then checked. If the flag isn't active, then false is returned immediately.
info
An inactive flag indicates it should not be active for anyone or under any circumstances. To activate a feature conditionally rule sets should be used.
If the flag is active, each rule set in the feature flag's configuration is evaluated using the provided context:
-
If a rule set evaluates to true, the feature is active.
-
If no rule set evaluates to true, but at least one rule set was evaluated (i.e., it matched the context even though it evaluated to false), the feature is not active.
-
If no rule set matches the context (i.e., none of the rule sets were evaluated), this means that no rules apply to disable a feature for the provided context. The feature is considered active.
caution
This is sometimes unintuitive for users unfamiliar with it.
Consider the case where a rule set to activate a feature for a specific user ID is configured but the feature is being activated without a user context, such as on the server. In such a case, the user restriction should not be considered matching and the feature should be considered active if no other rules apply.
init
FeatureFlags.
init
(
) →
Connection
--
A connection that can be used to disconnect the serialization process
Initializes the serialization process.
Begins synchronizing flags with the data store. This will listen for local changes to flags that are marked for serialization and update the data store accordingly. Local changes are listened for continuously, but only sent to the data store in specified intervals to help avoid rate limits.
info
This should be started as soon as possible in a game's lifecycle to ensure that flags are synchronized as soon as possible. Any features that depend on serialized flags will be unavailable until this is started and synchronized.
init({
store = "MyFeatureFlagStore",
synchronizationInterval = 300, -- 5 minutes
})
The initial synchronization happens immediately as this is called. This ensures that flags are available as soon as possible. Later synchronizations will happen in the specified interval.
Flag changes can easily be marked for serialization by updating them while
passing the serialize
option of UpdateOptions. This will mark the flag as
needing serialization and will be synchronized with the data store during the
next synchronization interval.
-- This flag will not be serialized
update("localFlag", {
-- Desired flag changes
})
-- Mark this flag for serialization
update("serializedFlag", {
-- Desired flag changes
}, {
serialize = true,
})
This is a convenience function that takes care of gathering flag changes and
synchronizing them with a data store. If you need more control over the
serialization process, you can create your own bespoke serialization process
instead using the Changed event and the serialize
option of
UpdateOptions.
create
FeatureFlags.
create
(
name:
string
,
--
The name to use for the flag
options:
PartialUpdateOptions?
) →
(
)
Creates a new flag with the provided name and configuration.
note
This only needs to be used when introducing new flags, such as when you want to introduce a new flag for a feature ahead of time.
This is typically done through some configuration interface, such as the built in one.
tip
If updating a flag that already exists, use the update
function instead.
Errors
Type | Description |
---|---|
"Flag '%s' already exists." | Thrown when a flag with this name already exists. |
exists
FeatureFlags.
exists
(
name:
string
) →
boolean
Checks if a flag with this name currently exists.
read
Reads the data of this flag.
This is primarily useful to display or manipulate flag information.
caution
This shouldn't be used to determine flag activation. Use the isActive
function instead.
While this contains all flag information, it doesn't include any complex
activation evaluation logic that isActive
does.
Errors
Type | Description |
---|---|
"Flag '%s' doesn't exist." | Thrown when a flag with this name doesn't exist. |
update
Updates the configuration of this flag.
Errors
Type | Description |
---|---|
"Flag '%s' doesn't exist." | Thrown when a flag with this name doesn't exist. |
retire
FeatureFlags.
retire
(
) →
(
)
Sets the retired status of a flag.
If a retired status isn't provided it defaults to true, assuming you intend to retire a flag.
Errors
Type | Description |
---|---|
"Flag '%s' doesn't exist." | Thrown when a flag with this name doesn't exist. |
destroy
Removes a flag entirely.
Errors
Type | Description |
---|---|
"Flag '%s' doesn't exist." | Thrown when a flag with this name doesn't exist. |
reset
FeatureFlags.
reset
(
notify?:
boolean
--
Whether to notify listeners of this change
) →
(
)
Resets all registered flags.
After this operation, there will be no registered flags and flags will need to be registered again before they can be used. This is primarily used to re-initialize all feature flags.
Notification will inform all listeners about the removal of all flags. Performing no notification is faster, but may break features currently listening. The default is not to notify, assuming that features listening to flags have already been handled or are not currently listening.
get
FeatureFlags.
get
(
name:
string
--
The name of the flag
) →
Promise
<
Flag
>
--
A Promise of the Flag requested
Gets a flag asynchronously.
get("newActivity"):andThen(function(flag: Flag)
-- The flag is available to use.
if flag.isActive() then
-- The flag is active.
end
end)