> ## Documentation Index
> Fetch the complete documentation index at: https://mixpanel-edb78807-amymadden-ffmigration.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Implement Feature Flags (React Native)

## Overview

This developer guide will assist you in configuring your React Native application for Feature Flags using the [Mixpanel React Native SDK](/docs/tracking-methods/sdks/react-native). Feature Flags allow you to control the rollout of your features, conduct A/B testing, and manage application behavior without deploying new code.

Feature Flags in the React Native SDK work across iOS, Android, and React Native Web platforms.

For complete React Native SDK documentation, see the [React Native SDK guide](/docs/tracking-methods/sdks/react-native).

## Prerequisites

Before implementing Feature Flags, ensure:

* You are on an Enterprise subscription plan and have the latest version of the SDK installed (minimum supported version is [`v3.5.0`](https://github.com/mixpanel/mixpanel-react-native/releases)). If not, install with `npm install mixpanel-react-native`.
* You have your Project Token from your [Mixpanel Project Settings](/docs/orgs-and-projects/managing-projects#find-your-project-tokens).
* For iOS native apps, run `cd ios && pod install` after installing the SDK.

<Note>
  **Data Residency:** Use the server URL that matches your Mixpanel project's data residency region:

  * **US** (default): `https://api.mixpanel.com`
  * **EU**: `https://api-eu.mixpanel.com`
  * **India**: `https://api-in.mixpanel.com`
</Note>

## Execution Modes

The React Native SDK operates in two modes that affect how feature flags are evaluated:

| Mode           | Platforms        | How It Works                                                     |
| -------------- | ---------------- | ---------------------------------------------------------------- |
| **Native**     | iOS, Android     | Bridges to the native Swift and Android SDKs for flag evaluation |
| **JavaScript** | React Native Web | Uses a pure JavaScript implementation (aligned with mixpanel-js) |

The mode is determined by the `useNative` parameter in the `Mixpanel` constructor:

```javascript theme={"system"}
import { Mixpanel } from 'mixpanel-react-native';

// Native mode (default for bare React Native apps)
const mixpanel = new Mixpanel("YOUR_PROJECT_TOKEN", true);

// JavaScript mode (for React Native Web)
const mixpanel = new Mixpanel("YOUR_PROJECT_TOKEN", true, false);
```

## Flag Initialization

Initializing the SDK with feature flags enabled requires passing `featureFlagsOptions` with `enabled: true` to the `init()` method. This enables making an outbound request to Mixpanel servers with the current user context.

The server will assign the user context to a variant for each feature flag according to how they are configured in the Mixpanel UX.

The response will include an assigned variant for each flag that the user context is in a rollout group for. If a flag is not returned, it most likely signifies that the user was excluded by filtering rules or the rollout percentage for the flag.

**Example Usage**

```javascript theme={"system"}
import { Mixpanel } from 'mixpanel-react-native';

// For native apps (iOS/Android)
const mixpanel = new Mixpanel("YOUR_PROJECT_TOKEN", true);

// For React Native Web, use JavaScript mode:
// const mixpanel = new Mixpanel("YOUR_PROJECT_TOKEN", true, false);

await mixpanel.init(
  false,                       // optOutTrackingDefault
  {},                          // superProperties
  "https://api.mixpanel.com",  // serverURL (see Data Residency above)
  true,                        // useGzipCompression
  { enabled: true }            // featureFlagsOptions
);
```

If your flag is configured with a Variant Assignment Key other than `distinct_id` or `device_id` for any of the feature flags in your project, then the call to initialize feature flags must include those keys.

For example, for a Variant Assignment Key `company_id`, you would set up the SDK as follows:

```javascript theme={"system"}
await mixpanel.init(
  false,
  {},
  undefined,
  undefined,
  {
    enabled: true,
    context: {
      company_id: "X",
    },
  }
);
```

If you are using Runtime Targeting in any of the feature flags in your project, then any properties that you use in targeting should be included in a `custom_properties` node within the context:

```javascript theme={"system"}
await mixpanel.init(
  false,
  {},
  undefined,
  undefined,
  {
    enabled: true,
    context: {
      company_id: "X",
      custom_properties: {
        platform: "react-native",
      },
    },
  }
);
```

## Flag Persistence

By default (`networkOnly`), the SDK fetches fresh flag assignments and waits for the network response before variant calls complete. You can enable persistence to cache assignments so they are available immediately on subsequent launches. In JavaScript mode, persistence uses the same AsyncStorage adapter as the rest of the SDK. In native mode, persistence is handled by the underlying iOS and Android SDKs.

Configure a `persistence.variantLookupPolicy` on `featureFlagsOptions`:

**`networkFirst`**. The SDK waits for the network on every launch. If the network call fails, the persisted value is returned as a fallback. Async getter calls block until the fetch completes (or the fallback is resolved).

```javascript theme={"system"}
await mixpanel.init(
  false,
  {},
  undefined,
  undefined,
  {
    enabled: true,
    persistence: {
      variantLookupPolicy: "networkFirst",
    },
  }
);

// Waits for the network. Falls back to the persisted value if the network is unavailable.
const value = await mixpanel.flags.getVariantValue("my-feature-flag", "control");
```

**`persistenceUntilNetworkSuccess`**. Persisted variants are returned immediately on launch. Each getter call while serving persisted data triggers a background network fetch (deduplicated, only one fetch runs at a time). If the fetch fails, persisted data continues to be served and the next getter call retries the fetch. Once a fetch succeeds, the in-memory state updates and subsequent calls no longer trigger background fetches.

```javascript theme={"system"}
await mixpanel.init(
  false,
  {},
  undefined,
  undefined,
  {
    enabled: true,
    persistence: {
      variantLookupPolicy: "persistenceUntilNetworkSuccess",
    },
  }
);

// Returns persisted variants immediately. Background fetches retry until a
// network success replaces the persisted state.
const value = await mixpanel.flags.getVariantValue("my-feature-flag", "control");
```

To customize the TTL (default is 24 hours), pass `persistenceTtlMs` in milliseconds:

```javascript theme={"system"}
await mixpanel.init(
  false,
  {},
  undefined,
  undefined,
  {
    enabled: true,
    persistence: {
      variantLookupPolicy: "networkFirst",
      persistenceTtlMs: 12 * 60 * 60 * 1000, // 12 hours
    },
  }
);
```

### Variant source

Every variant returned by `getVariant` / `getVariantSync` carries a `variant_source` field indicating where the value came from: `"network"`, `"persistence"`, or `"fallback"`. Variants served from persistence also include `persisted_at_in_ms`, the epoch timestamp in milliseconds when the variant set was persisted.

```javascript theme={"system"}
const fallback = { key: "control", value: "control" };
const variant = await mixpanel.flags.getVariant("my-feature-flag", fallback);

console.log(variant.value);
console.log(variant.variant_source); // "network" | "persistence" | "fallback"

if (variant.variant_source === "persistence") {
  console.log(`Persisted at: ${new Date(variant.persisted_at_in_ms).toISOString()}`);
}
```

### `$experiment_started` properties

When a variant is served, the `$experiment_started` exposure event includes:

* `$variant_source`. Either `"network"` or `"persistence"`.
* `$persisted_at_in_ms`. Epoch milliseconds when the variant set was persisted (persistence only).

<Note>
  Persistence is scoped to the current `distinct_id`. Calling `identify()` with a new ID or calling `reset()` clears persisted flag state and triggers a fresh fetch.
</Note>

## Flag Reload

Following initialization, you can reload feature flag assignments in several ways:

### Reload via identify

After a user logs in or out of your application and you call `identify`, a feature flag reload will be triggered.

```javascript theme={"system"}
const updatedDistinctId = "user-123";
await mixpanel.identify(updatedDistinctId);
```

### Manual Reload

You can manually reload flags at any time:

```javascript theme={"system"}
await mixpanel.flags.loadFlags();
```

### Update Context

`updateContext` is available in both native and JavaScript modes. It updates the feature flags context after initialization and triggers a reload with the new values.

```javascript theme={"system"}
// Default is merge with the existing context.
await mixpanel.flags.updateContext({
  company_id: "Y",
  custom_properties: {
    platform: "mobile",
  },
});
```

In JavaScript mode, pass `{ replace: true }` to replace the context entirely instead of merging.

```javascript theme={"system"}
await mixpanel.flags.updateContext(
  { company_id: "Z" },
  { replace: true }
);
```

<Note>
  In **native mode**, the SDK always merges the new context with the existing context. The `{ replace: true }` option is accepted for API parity but has no effect on native.
</Note>

## Flag Evaluation

The SDK provides both asynchronous (Promise-based) and synchronous methods for evaluating flags.

### Async Evaluation (Recommended)

Async methods return Promises that resolve once flags are ready. This is the recommended approach as it handles the case where flags may not yet be loaded.

**Experiment Flags: Get Variant Value**

```javascript theme={"system"}
// Fetch the variant value once flags are ready and track an exposure event
// if the user context is in a rollout group for the feature flag.
const fallback = "control"; // used if the user doesn't match any rollout rules
const variantValue = await mixpanel.flags.getVariantValue("my-feature-flag", fallback);

if (variantValue === "variant_a") {
  showExperienceForVariantA();
} else if (variantValue === "variant_b") {
  showExperienceForVariantB();
} else {
  showDefaultExperience();
}
```

**Feature Gates: Check if Flag is Enabled/Disabled**

```javascript theme={"system"}
const isEnabled = await mixpanel.flags.isEnabled("my-boolean-flag", false);

if (isEnabled) {
  showNewFeature();
} else {
  showOldFeature();
}
```

**Get Full Variant Object**

Use `getVariant` to retrieve the full variant object, which includes metadata like `experiment_id`:

```javascript theme={"system"}
const variant = await mixpanel.flags.getVariant("my-feature-flag", {
  key: "control",
  value: "standard",
});

console.log(`Variant: ${variant.key}, Value: ${variant.value}`);
if (variant.experiment_id) {
  console.log(`Part of experiment: ${variant.experiment_id}`);
}
```

### Sync Evaluation

Synchronous methods return values immediately without waiting. Use these when you need to evaluate flags in render paths or other synchronous contexts.

<Warning>
  Sync methods return the fallback value if flags have not finished loading. Always check `areFlagsReady()` before relying on sync results.
</Warning>

```javascript theme={"system"}
if (mixpanel.flags.areFlagsReady()) {
  const variantValue = mixpanel.flags.getVariantValueSync("my-feature-flag", "control");
  const isEnabled = mixpanel.flags.isEnabledSync("my-boolean-flag", false);

  // Full variant object
  const variant = mixpanel.flags.getVariantSync("my-feature-flag", {
    key: "control",
    value: "standard",
  });
}
```

### Get All Flag Assignments

You can retrieve all feature flag assignments at once. These methods do **not** trigger `$experiment_started` exposure events.

**Synchronous**

`getAllVariantsSync()` returns a JavaScript `Map` keyed by feature name. If flags have not been loaded yet, it returns an empty `Map`.

```javascript theme={"system"}
const allFlags = mixpanel.flags.getAllVariantsSync();

for (const [flagName, variant] of allFlags) {
  console.log(`${flagName}: ${variant.value}`);
}
```

**Asynchronous**

`getAllVariants` returns a Promise resolving to a `Map`. If flags have not been loaded yet, it will attempt to fetch them before returning. On failure, it resolves to an empty `Map` rather than rejecting.

```javascript theme={"system"}
const flags = await mixpanel.flags.getAllVariants();
flags.forEach((variant, flagName) => {
  console.log(`${flagName}: ${variant.value}`);
});
```

## Exposure Events

Calling `isEnabled`, `isEnabledSync`, `getVariantValue`, `getVariantValueSync`, `getVariant`, or `getVariantSync` triggers tracking an exposure event, `$experiment_started`, to your Mixpanel project if the user context is in a rollout group for the feature flag. The exposure event is deduplicated per feature name for the lifetime of the SDK instance.

`getAllVariants` and `getAllVariantsSync` do not fire exposure events.

## Method Name Aliases

The React Native SDK supports both camelCase and snake\_case method names, aligned with mixpanel-js conventions:

| camelCase               | snake\_case                |
| ----------------------- | -------------------------- |
| `areFlagsReady()`       | `are_flags_ready()`        |
| `getVariant()`          | `get_variant()`            |
| `getVariantSync()`      | `get_variant_sync()`       |
| `getVariantValue()`     | `get_variant_value()`      |
| `getVariantValueSync()` | `get_variant_value_sync()` |
| `isEnabled()`           | `is_enabled()`             |
| `isEnabledSync()`       | `is_enabled_sync()`        |
| `getAllVariants()`      | `get_all_variants()`       |
| `getAllVariantsSync()`  | `get_all_variants_sync()`  |
| `loadFlags()`           | `load_flags()`             |
| `updateContext()`       | `update_context()`         |

## Frequently Asked Questions

### What if I'm not receiving any flags on SDK initialization?

1. **Check your project token**:

* Ensure you're using the correct project token from your [Mixpanel project settings](/docs/orgs-and-projects/managing-projects#find-your-project-tokens).

2. **Review flag configuration**:

* Make sure your feature flag is enabled.
* Check the flag's rollout percentage. User contexts that are not assigned to the rollout percentage will not receive flags.
* If you are using a targeting cohort, verify on the Mixpanel 'Users' page that the user's `distinct_id` is a member of that cohort.

3. **Review SDK parameters**:

* Ensure `featureFlagsOptions` with `enabled: true` is passed to `init()`.
* If using a custom Variant Assignment Key, ensure it is included in the `context` object.
* If using Runtime Targeting, ensure all properties used in targeting are included in the `custom_properties` object within `context`.

4. **Check flags readiness**: Use `areFlagsReady()` to check if flags have been loaded before using sync evaluation methods.
5. **Enable debug logging**: Call `mixpanel.setLoggingEnabled(true)` to see detailed information about flag requests and responses.

### Which mode should I use?

* **Bare React Native apps** (iOS/Android): Use **native mode** (`new Mixpanel(token, true)`) for best performance, as it delegates to the optimized native SDKs.
* **React Native Web**: Use **JavaScript mode** (`new Mixpanel(token, true, false)`).
