contribute/feature-toggles.md
This guide helps you to add your feature behind a feature flag, code that lets you enable or disable a feature without redeploying Grafana.
Exhaustive documentation on OpenFeature can be found at OpenFeature.dev
toggles_gen for the backend, grafana-data for the frontend, and docs. To run the test, run make gen-feature-toggles.Once your feature toggle is defined, you can then wrap your feature around a check if the feature flag is enabled on that Grafana instance.
Examples:
Use the OpenFeature client for all new backend feature flags.
commands module before initialization of other modules.import "github.com/open-feature/go-sdk/openfeature"
client := openfeature.NewDefaultClient()
if client.Boolean(ctx, MyTestFlag, false, openfeature.TransactionContext(ctx)) {
...
}
import (
"github.com/open-feature/go-sdk/openfeature"
"github.com/open-feature/go-sdk/openfeature/testing"
)
var (
// Since openfeature relies on global state,
// TestProvider should be a global instance shared between tests
// Under the hood it uses a custom _goroutine local_ storage to manage state on a per-test basis
provider = testing.NewTestProvider()
)
func TestMain(m *testing.M) {
fmt.Println("Setting up test environment...")
if err := openfeature.SetProvider(provider); err != nil {
...
}
exitCode := m.Run()
os.Exit(exitCode)
}
func TestFoo(t *testing.T) {
t.Parallel()
testFlags := map[string]memprovider.InMemoryFlag{
...
}
provider.UsingFlags(t, testFlags)
...
}
Use the OpenFeature React hooks for all new feature flags. The React hooks automatically stay up to date with the latest flag values and integrate seamlessly with React components.
For React components, use the useBooleanFlagValue hook from @openfeature/react-sdk:
import { useBooleanFlagValue } from '@openfeature/react-sdk';
function MyComponent() {
// The hook returns the current value and automatically updates when the flag changes
const isNewPreferencesEnabled = useBooleanFlagValue('newPreferences', false);
if (isNewPreferencesEnabled) {
return <NewPreferencesUI />;
}
return <LegacyPreferencesUI />;
}
Flag values may change over the lifetime of the session, so do not store the result elsewhere in a way it will not react to changes in the flag value.
If using non-boolean flags (a unique feature of the new feature flag system), explore the other exports from @openfeature/react-sdk to see how to use them.
For advanced, non-React contexts (utilities, class methods, callbacks), you can use the OpenFeature client directly.
However, because this is seperate from the React render loop there are important caveats you must be aware of:
getBooleanValue() just at the top-level of a module. You must wait until app.ts has initialised until you call a flag otherwise you will only get the default valueIt is strongly preferred to use the React hooks instead of getting the client.
import { getFeatureFlagClient } from '@grafana/runtime/internal';
// GOOD - The feature toggle should be called after app initialisation
function doThing() {
if (getFeatureFlagClient().getBooleanValue('newPreferences', false)) {
// do new things
}
}
// BAD - Don't do this. The feature toggle must wait until app initialisation
const isEnabled = getFeatureFlagClient().getBooleanValue('newPreferences', false);
function doThing() {
if (isEnabled) {
// do new things
}
}
// BAD - Don't do this. The feature toggle will not change in response to updates
class FooSrv {
constructor() {
this.isEnabled = getFeatureFlagClient().getBooleanValue('newPreferences', false);
}
doThing() {
if (this.isEnabled) {
// do new things
}
}
}
Add the feature toggle to the feature_toggle section in your custom.ini, for example:
[feature_toggles]
localeFormatPreference=true