Desktop Feature API (JS and C++)
This guide will help you use the Nimbus Feature API in Desktop Firefox to run experiments, set values remotely, and manage user preferences. If you are new to using Nimbus, here's a video overview of how the code interacts to control your feature. The video until 5 minutes 44 seconds focuses on getting started. Starting at 5:58 it switches to considerations when controlling preferences.
If you are familiar with Normandy and are trying to migrate a feature, you may want to check out the Migration Guide for Pref Experiments.
About the Feature API
Can I use this?
For the JS implementation you can import ExperimentAPI.sys.mjs
in the parent process or a privileged child process. We do support First run experiments on Windows, holdbacks, and rollouts.
For the C++ implementation you can import #include "mozilla/browser/NimbusFeatures.h"
and we support early startup experiments and holdbacks.
If you have a usecase that isn't supported, please reach out in #ask-experimenter on Slack.
What is a feature?
In the Nimbus ecosystem, a feature
is an area of code instrumented for experiments and remote configuration. It can be as small as a single function or as complex as a whole about: page. Some examples:
aboutwelcome
, The about:welcome page in Desktopnewtab
, The about:newtab page in Desktop
In your code, you will use the Nimbus SDK to access variables associated with those features. e.g.
- JavaScript
- C++
const { screens, skipFocus } = NimbusFeatures.aboutwelcome.getAllVariables();
NimbusFeatures::GetBool("aboutwelcome"_ns, "enabled"_ns, false);
Configuration sources
This section is relevant only for the JS API.
The Nimbus Feature API will return the correct configuration for a feature given a few different inputs in this order:
- Experiment value: First, we check if a Nimbus experiment is activated that changes the feature.
- Remotely-configured value: If no experiment is set, we check if there is a remotely-defined value. This is a mechanism that allows us to roll-out changes quickly between releases.
- Local default: Finally, we will return the current value of preferences in the manifest, if they are defined in firefox.js.
Registering a new feature
To register a new feature, you will need to choose an identifier and add it to the manifest in FeatureManifest.yaml: After adding the feature a build step is required to update the appropriate header file.
# In FeatureManifest.yaml
# Our feature name
aboutwelcome:
description: The about:welcome page
# Include this if you need synchronous access / very early access at startup
# or if you are registering this to use for platform experiments.
isEarlyStartup: true
variables:
# Additional (optional) values that we can control
# The name of these variables is up to you
enabled:
type: boolean
fallbackPref: browser.aboutwelcome.enabled
skipFocus:
type: boolean
// In firefox.js
pref("browser.aboutwelcome.enabled", true);
Importing the Feature API
Import the NimbusFeatures
module:
- JavaScript
- C++
const lazy = {}
ChromeUtils.defineESModuleGetters(lazy, {
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
});
#include "mozilla/browser/NimbusFeatures.h"
API Reference Guide
getVariable()
getVariable(variableName: string): FeatureValue
Returns the value of a single feature variable.
- JavaScript
- C++
// Warning: **This function will throw in Nightly and CI build** if you do not define `variableName` in the Nimbus manifest.
const foo = NimbusFeatures.myFeature.getVariable("foo");
// notAVariable is not defined in the manifest, so this will throw in CI
const baz = NimbusFeatures.myFeature.getVariable("notAVariable");
// feature name and variable name to retrieve the value
NimbusFeatures::GetInt("aboutwelcome"_ns, "skipFocus"_ns, false);
getAllVariables()
getAllVariables({ defaultValues }): FeatureValue
(JS Only)
Returns the value of all variables for a feature. Note that variables will be merged between sources.
If options.defaultValues
is defined, it will be preferred before default branch fallback values but after experiment, remote, and user-set preference values.
- JavaScript
- C++
const { foo, bar } = NimbusFeatures.myFeature.getAllVariables({
defaultValues: { foo: true, bar: false },
});
// There is no equivalent for this in C++. Instead you can use
// NimbusFeatures::GetInt("featurename"_ns, "variablename"_ns, 0)
// NimbusFeatures::GetBool("featurename"_ns, "variablename"_ns, false)
// To access a single variable value at a time
recordExposureEvent()
Use this to send an exposure event. By default this will send one exposure event per function call, but you can add an options object of {once: true}
to only send it once per session.
Note that you should add an exposureDescription
to the manifest describing when/how this event is sent.
- JavaScript
- C++
NimbusFeatures.myFeature.recordExposureEvent();
// Only sends once per session, even if this function is called multiple times
NimbusFeatures.myFeature.recordExposureEvent({ once: true });
// aOnce=true specifies that we only want to send this event once per browsing
// session
NimbusFeatures::RecordExposureEvent("featurename"_ns, true);
ready()
ready(): Promise
(JS Only)
Wait for the remote experiment and defaults stores to be synced before checking values.
- JavaScript
- C++
await NimbusFeatures.myFeature.ready();
const { foo } = NimbusFeatures.myFeature.getAllVariables();
// This is not needed by the C++ API and has no equivalent, instead by
// registering your feature as `isEarlyStartup` you can ensure that `GetBool`
// and `GetInt` will return the experiment value at startup
onUpdate()
Listen for changes, include to remote defaults or pref values.
- JavaScript
- C++
NimbusFeatures.myFeature.onUpdate((event, reason) => {
/**
* `reason` is a string that can be used to identify the source
* of the update event.
* This list of reasons:
* 1. `feature-experiment-loaded` or `feature-rollout-loaded` this
* is triggered when the Nimbus feature has finished loading
* (when .ready() resolves). It is not relevant for isEarlyStartup=true features
* 2. `experiment-updated` or `rollout-updated` client recipe for this
* feature was changed (activated or deactivated)
* 3. `pref-updated` the value of the fallback pref for the feature
* variable was changed
*/
const newValue = NimbusFeatures.myFeature.getAllVariables();
updateUI(newValue);
});
NimbusFeatures::OnUpdate("aboutwelcome"_ns, "skipFocus"_ns,
[](const char*, void*){}, void*);
off()
Stop listening for changes.
- JavaScript
- C++
NimbusFeatures.myFeature.onUpdate(aListener);
// Later
NimbusFeatures.myFeature.offUpdate(aListener);
// Provide feature name and value to watch for changes
NimbusFeatures::OnUpdate("aboutwelcome"_ns, "skipFocus"_ns,
[](const char*, void*){}, void*);
// Later
NimbusFeatures::OffUpdate("aboutwelcome"_ns, "skipFocus"_ns,
[](const char*, void*){}, void*);
Experiment Metadata
If you need to know whether an experiment is active or get access to the experiment or branch identifier (for example, to report in utm_params
), you can use ExperimentAPI.getExperimentMetaData
:
const lazy = {}
ChromeUtils.defineESModuleGetters(lazy, {
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
});
const data = lazy.ExperimentAPI.getExperimentMetaData({ featureId: "myFeature" });
// If there is no experiment, data will be null.
const slug = data?.slug;
const branchSlug = data?.branch?.slug;
if (experimentSlug && branchSlug) {
sendSomeTelemetry(
`The experiment identifier is ${slug} and the branch identifier is ${branchSlug}`,
);
}