Summary
For boolean flags, the LaunchDarkly importer maps LD's on toggle directly to Flagsmith's enabled state and ignores the flag's actual default served value (the fallthrough variation when on, or the offVariation when off).
LaunchDarkly separates "is the flag on" from "what does it serve", so a flag can be on: true and still serve false via its fallthrough (a common way to stage a flag: on, defaulting to false, with specific segments/targets receiving true). Flagsmith boolean flags have no such separation — enabled is the served value — so these flags import with an inverted state and evaluate to the opposite of what they do in LaunchDarkly.
Severity
Correctness. Imported boolean flags can evaluate to the wrong value. The mismatch is silent: nothing is written to error_messages and the import still reports success/incomplete. Any project that uses the "flag on, default off, enable via targeting" pattern can have a large fraction of its boolean flags affected.
Behaviour
For a boolean flag, the importer does:
# api/integrations/launch_darkly/services.py (_create_boolean_feature_states_with_segments_identities)
feature_state, _ = FeatureState.objects.update_or_create(
feature=feature,
feature_segment=None,
environment=environment,
defaults={"enabled": ld_flag_config["on"]}, # <-- uses on/off toggle, not the served variation
)
FeatureStateValue.objects.update_or_create(feature_state=feature_state) # empty value
It never reads fallthrough or offVariation, so the served variation is discarded for boolean flags. (The string/multivariate factories do resolve the served variation via _summary — only the boolean path is affected.)
Steps to reproduce
- In LaunchDarkly, create a boolean flag with variations
[true, false].
- Turn the flag on, and set the default rule (fallthrough) to serve the
false variation.
- Import the project into Flagsmith.
- Observe the imported flag: environment-default
enabled is true, so it serves true, while the same flag serves false in LaunchDarkly.
The reverse also occurs: a flag that is off with an offVariation of true imports as enabled: false.
Expected behaviour
For a boolean flag the environment-default enabled should be the resolved default served value, not the raw toggle:
on: true -> enabled = value_of(fallthrough.variation)
on: false -> enabled = value_of(offVariation)
Suggested fix
- In
_create_boolean_feature_states_with_segments_identities, resolve the default served variation (fallthrough when on, otherwise offVariation) and set enabled to that variation's boolean value, instead of ld_flag_config["on"].
- Decide handling for boolean flags whose default is a percentage rollout between true/false rather than a fixed variation. The boolean path currently ignores rollout entirely and flattens to the
on state; it should either map the split or log a skip rather than silently dropping it.
- Because the mismatch is otherwise silent, consider surfacing a per-flag note when a boolean flag's resolved served value differs from its
on state, so already-completed (and now incorrect) imports are discoverable.
Summary
For boolean flags, the LaunchDarkly importer maps LD's
ontoggle directly to Flagsmith'senabledstate and ignores the flag's actual default served value (thefallthroughvariation when on, or theoffVariationwhen off).LaunchDarkly separates "is the flag on" from "what does it serve", so a flag can be
on: trueand still servefalsevia its fallthrough (a common way to stage a flag: on, defaulting to false, with specific segments/targets receiving true). Flagsmith boolean flags have no such separation —enabledis the served value — so these flags import with an inverted state and evaluate to the opposite of what they do in LaunchDarkly.Severity
Correctness. Imported boolean flags can evaluate to the wrong value. The mismatch is silent: nothing is written to
error_messagesand the import still reports success/incomplete. Any project that uses the "flag on, default off, enable via targeting" pattern can have a large fraction of its boolean flags affected.Behaviour
For a boolean flag, the importer does:
It never reads
fallthroughoroffVariation, so the served variation is discarded for boolean flags. (The string/multivariate factories do resolve the served variation via_summary— only the boolean path is affected.)Steps to reproduce
[true, false].falsevariation.enabledistrue, so it servestrue, while the same flag servesfalsein LaunchDarkly.The reverse also occurs: a flag that is off with an
offVariationoftrueimports asenabled: false.Expected behaviour
For a boolean flag the environment-default
enabledshould be the resolved default served value, not the raw toggle:on: true->enabled = value_of(fallthrough.variation)on: false->enabled = value_of(offVariation)Suggested fix
_create_boolean_feature_states_with_segments_identities, resolve the default served variation (fallthrough whenon, otherwiseoffVariation) and setenabledto that variation's boolean value, instead ofld_flag_config["on"].onstate; it should either map the split or log a skip rather than silently dropping it.onstate, so already-completed (and now incorrect) imports are discoverable.