Featurevisor

Migrating the Elixir SDK for Featurevisor v4

Upgrade Elixir applications to SDK 2.0.0 for Featurevisor v4 support.

Follow the shared migration order: prepare canonical prerequisite datafiles, upgrade consumers, then upgrade the project CLI. Before publishing new runtime fields, follow Adopt new syntax safely.

Convert Groups before upgrading

This SDK silently ignores legacy feature ranges, with no compatibility handling or warning. Convert Groups to flat list requirements while still on the previous compatible SDK and v3 CLI, then publish all datafiles, caches and bundled copies before upgrading. Unconverted files can change cohorts or enable features without a variation. Old cohorts are not guaranteed. Follow Groups removed.

Minimal migration path

Most Elixir application code does not change. The following APIs were already available in the previous SDK line and keep the same role:

  • Featurevisor.create_featurevisor/1 and Featurevisor.set_datafile/3
  • Featurevisor.enabled?/4, Featurevisor.get_variation/4, and feature variable getters
  • Featurevisor.get_global_variable/4 and its typed getters
  • the corresponding detailed evaluate_* functions
  • set_sticky_features/3, set_sticky_variables/3, and child instances
  • get_feature_evaluations/4 and get_global_variable_evaluations/4
  • module lifecycle, diagnostics, and the existing split sticky events

Elixir already used canonical sticky APIs and separate feature and global variable evaluation helpers in the previous SDK line. Do not replace them with JavaScript compatibility names that never existed in this SDK.

If an application already uses before_evaluation and after_evaluation and does not inspect renamed evaluation struct fields, its source migration can be limited to updating the packages and testing its datafiles.

See the Elixir SDK guide for the complete current API.

Breaking changes

Root condition reason Breaking

An unmet root gate reports conditions_unmet for feature flags and global variables, replacing the v3 required reason for feature flags and required_features_unmet for global variables. This applies to attribute, segment, feature and variable leaves in root conditions. Update callbacks, analytics and test expectations. Variation and feature variable evaluations retain their disabled reasons. See the shared migration guidance.

Before
unmet_requirement = evaluation.reason == :required
After
unmet_requirement = evaluation.reason == :conditions_unmet

Datafile update result Breaking

Featurevisor.set_datafile/3 now returns true for accepted updates, including unchanged content, and false for rejected updates or closed instances. Replace matches on :ok and {:error, reason} with boolean handling. Rejection details remain available through diagnostics, and the previous valid datafile is retained.

Before
case Featurevisor.set_datafile(f, datafile) do
:ok -> IO.puts("Datafile accepted")
{:error, reason} -> IO.inspect(reason, label: "Datafile rejected")
end
After
if Featurevisor.set_datafile(f, datafile) do
IO.puts("Datafile accepted")
else
IO.puts("Datafile rejected. Check diagnostics.")
end

Use if to handle a rejected update without failing a pattern match. Merge remains the default. The acceptance result does not report whether any evaluated value changed.

Module callbacks Breaking

The deprecated feature only before and after fields are removed from Featurevisor.Module. before_evaluation and after_evaluation were already available in the previous SDK line, so applications can adopt them before the breaking upgrade.

Before migration
module = %Featurevisor.Module{
name: "feature_context",
before: fn options ->
%{options | context: Map.put(options.context, "country", "nl")}
end,
bucket_value: fn options -> options.bucket_value end,
after: fn evaluation, _options -> evaluation end
}
f = Featurevisor.create_featurevisor(%{
datafile: datafile,
modules: [module]
})
After migration
module = %Featurevisor.Module{
name: "feature_context",
before_evaluation: fn options ->
if Map.has_key?(options, :feature_key) do
%{options | context: Map.put(options.context, "country", "nl")}
else
options
end
end,
bucket_value: fn options -> options.bucket_value end,
after_evaluation: fn evaluation, _options -> evaluation end
}
f = Featurevisor.create_featurevisor(%{
datafile: datafile,
modules: [module]
})

The canonical callbacks cover feature and global variable evaluations, including prerequisite evaluations. If callback logic is feature specific, check Map.get(options, :feature_key) before transforming the options or result. setup, bucket_key, bucket_value, and close are unchanged.

If modules currently define both callback families, preserve their intended ordering while consolidating them. On the previous SDK line, all legacy before callbacks run before all before_evaluation callbacks, while all after_evaluation callbacks run before all legacy after callbacks. After migration, the SDK runs only canonical callbacks in module registration order.

Detailed evaluation fields Breaking

Rename the legacy sticky metadata field when application code inspects %Featurevisor.Evaluation{}:

Before migration
evaluation = Featurevisor.evaluate_flag(f, "checkout", context)
sticky_feature = evaluation.sticky
requirements = evaluation.required_features
After migration
evaluation = Featurevisor.evaluate_flag(f, "checkout", context)
sticky_feature = evaluation.sticky_feature
requirements = evaluation.conditions

conditions now holds the failed root gate expression. Use evaluation.reason == :sticky to identify a sticky result. sticky_feature is optional metadata and is not present for every sticky result.

Accumulating global variable evaluations add variable_override_paths, listing every applied authored path in execution order. Existing singular override fields still describe the final match. A failed variable prerequisite exposes conditions and the reason :conditions_unmet.

Number getter Breaking

Variables that hold numbers have the type number, so the typed getter is now get_variable_number/5. It reads number variables and the double variables of datafiles built before v4. get_variable_double/5 and get_global_variable_double/4 were removed. Rename calls to get_variable_number/5 and get_global_variable_number/4, which return the same values.

Before
threshold = Featurevisor.get_global_variable_double(f, "freeShippingThreshold", context)
After
threshold = Featurevisor.get_global_variable_number(f, "freeShippingThreshold", context)

Language differences

Datafile input remains straightforward

Elixir maps and lists retain the distinction between JSON objects and arrays. Both existing initialization forms remain valid:

datafile = File.read!("datafile.json")
f = Featurevisor.create_featurevisor(%{datafile: datafile})
datafile = "datafile.json" |> File.read!() |> Jason.decode!()
f = Featurevisor.create_featurevisor(%{datafile: datafile})

No alternative reader or wrapper type is required for v4. Keep generated datafile keys as strings when passing decoded maps.

OpenFeature

The Elixir repository publishes featurevisor and the separate featurevisor_openfeature Hex package together. Upgrade both packages to 2.0.0. If FeaturevisorOpenFeature.Provider receives an existing f, that instance must use the matching base SDK.

The provider constructor, key mapping, and resolver usage remain unchanged. The updated provider maps :conditions_unmet to OpenFeature's disabled reason. Keeping provider and core releases aligned also preserves datafile update notifications.

See Elixir OpenFeature usage and the shared OpenFeature provider guide.

Migration checklist

Required for the migration

  • Prepare and deploy canonical conditions datafiles before changing the runtime.
  • Replace module before and after fields with before_evaluation and after_evaluation.
  • Rename evaluation.sticky to evaluation.sticky_feature where used.
  • Rename get_variable_double and get_global_variable_double calls to their _number forms.
  • Upgrade featurevisor and featurevisor_openfeature together when applicable.
  • Test feature, variation, feature variable, global variable, sticky, child, and module behaviour.
  • Upgrade the project CLI last, review mutation order and date and regex comparisons, and keep a compatible rollback datafile.

Only if adopting optional features

Follow Adopt new syntax safely before publishing new datafile syntax. Upgrade every CLI runner before adding checks or scenarios.