Skip to main content

Co-enrolling Features

A feature which allows co-enrollment allows a client to be enrolled in any number of experiments/rollouts for that feature.

Features supporting co-enrollment

  • Messaging (Fenix, Firefox iOS, Focus for Android, Focus for iOS)
  • Firefox Desktop — any feature with allowCoenrollment: true in FeatureManifest.yaml (e.g., prefFlips)

How to define a co-enrolling feature

A feature can be marked as allowing co-enrollment with a boolean flag in its feature definition in a Feature Manifest.

Mobile (FML): In your .fml.yaml file:

features:
messaging:
description: |
The in-app messaging system.
allow-coenrollment: true
variables:
messages:
description: A growable collection of messages
type: Map<String, MessageData>
default: {}

Desktop: In FeatureManifest.yaml:

prefFlips:
description: Pref flips for incident response
allowCoenrollment: true
variables:
prefs:
type: json

Once your feature is defined as a co-enrolling feature, a client can be enrolled in any number of experiments/rollouts for that feature.

Recording exposure

In order to record exposure for a co-enrolled feature, the string {experiment} needs to exist in the feature config somewhere. In the case of messaging, this would be in the message object:

objects:
MessageData:
...
fields:
experiment:
type: Option<String>
description: The slug of the experiment that this message came from.
default: null

This string is then replaced with the experiment slug at enrollment time.

The feature code that the developer is writing needs to get the experiment slug from the feature config and record the exposure with recordExperimentExposure(experimentSlug). In Kotlin, this would look like:

val slug = message.data.experiment ?: return message

messagingFeature.recordExperimentExposure(slug)

Desktop

On Desktop, co-enrolling features use the {slug} option on recordExposureEvent():

NimbusFeatures.myFeature.recordExposureEvent({ slug: "experiment-slug" });

Standard per-feature methods like getVariable() and getAllVariables() throw for co-enrolling features. Instead, use these APIs:

  • getAllEnrollments() — returns { meta: { slug, branch, isRollout }, value: object }[] with both metadata and resolved feature values for each enrollment.
  • getAllEnrollmentMetadata() — returns { slug, branch, isRollout }[] (metadata only).

See the Desktop Feature API reference for full details.

Other things to note about co-enrollment

  • Experiment feature values still take precedence over rollout feature values
  • Enrollment/unenrollment is calculated independently for each experiment/rollout regardless of whether they target the same feature or not
  • Enrollment/unenrollment telemetry is still sent for each experiment/rollout at the time each enrolls or unenrolls