Skip to main content

Storage

The React Native SDK supports pluggable storage adapters for persistent caching of feature flags. This enables offline support and faster app startup.

Why Use Storage?​

Storage adapters provide several benefits:

  • Offline support: Feature flags are available even without network
  • Faster startup: Flags load instantly from cache before API refresh
  • Reduced API calls: ETag-based caching minimizes network requests
  • Consistent experience: Users see the same flags across app restarts

AsyncStorage Adapter​

Best for Expo and when simplicity is preferred.

Installation​

npm install @ops-ai/react-native-toggly-storage-async @react-native-async-storage/async-storage

For Expo:

npx expo install @react-native-async-storage/async-storage
npm install @ops-ai/react-native-toggly-storage-async

Usage​

import { TogglyProvider } from '@ops-ai/react-native-toggly';
import { createAsyncStorageAdapter } from '@ops-ai/react-native-toggly-storage-async';

const storage = createAsyncStorageAdapter();

function App() {
return (
<TogglyProvider
appKey="your-app-key"
environment="production"
storage={storage}
>
<MyApp />
</TogglyProvider>
);
}

Options​

const storage = createAsyncStorageAdapter({
keyPrefix: 'myapp_toggly_', // Custom key prefix (default: '@toggly/')
});

MMKV 4 Adapter​

Use @ops-ai/react-native-toggly-storage-mmkv4 1.0.0 or newer with MMKV 4 and Nitro. Keep the separate MMKV 2/3 adapter for hosts that still use those native versions.

Installation​

npm install @ops-ai/react-native-toggly-storage-mmkv4@^1.0.0 react-native-mmkv@^4.0.0 react-native-nitro-modules@^0.37.1
cd ios && pod install

MMKV 4 requires a native build. Expo Go cannot load these native modules; Expo apps need a development build. Meet your selected React Native release's Node, iOS, Android, and Nitro requirements. The adapter requires Nitro ^0.37.1; earlier Nitro releases lack the current React Native JSI hook. Installing packages and checking TypeScript do not verify a native host build.

Usage​

import type { ReactNode } from 'react';
import { TogglyProvider } from '@ops-ai/react-native-toggly';
import { createMMKV4StorageAdapter } from '@ops-ai/react-native-toggly-storage-mmkv4';

const storage = createMMKV4StorageAdapter();

export function App({ children }: { children: ReactNode }) {
return (
<TogglyProvider
appKey="YOUR_PUBLIC_FRONTEND_APP_KEY"
environment="Production"
storage={storage}
>
{children}
</TogglyProvider>
);
}

The default MMKV ID is toggly and the default key prefix is toggly:. Options include id, keyPrefix, path, encrypted, encryptionKey, and an existing mmkv instance. Set encrypted: true with an encryptionKey when the adapter creates an encrypted store. When supplying an instance, configure its ID, path, and encryption on that instance:

import { createMMKV } from 'react-native-mmkv';
import { createMMKV4StorageAdapter } from '@ops-ai/react-native-toggly-storage-mmkv4';

export function createEncryptedStorage(encryptionKey: string) {
return createMMKV4StorageAdapter({
keyPrefix: 'toggly:',
mmkv: createMMKV({ id: 'toggly', encryptionKey }),
});
}

The host supplies the encryption key; do not embed a production key in source. MMKV 4 uses createMMKV, while MMKV 2/3 uses the MMKV constructor.

Migrating from MMKV 2 or 3​

Replace the legacy adapter package and import with the MMKV 4 adapter. Preserve the same id, keyPrefix, path, and encryption key to keep Toggly's storage addresses. Verify native-store migration on every target device before shipping. The adapter neither deletes existing data nor copies cache entries between native MMKV implementations. Storage companions persist feature data only; they do not own a telemetry reporter or persist telemetry buffers.

MMKV Adapter​

This adapter supports MMKV 2 and 3. Its package remains @ops-ai/react-native-toggly-storage-mmkv; do not pair it with MMKV 4.

Best for performance-critical applications. MMKV is significantly faster than AsyncStorage.

Installation​

npm install @ops-ai/react-native-toggly-storage-mmkv react-native-mmkv@^3.0.0
cd ios && pod install

The command above selects MMKV 3. Existing MMKV 2 hosts can use react-native-mmkv@^2.0.0 with the same legacy adapter. Choose the native version supported by your host.

warning

MMKV requires native code and won't work with Expo Go. You'll need a development build for Expo.

Usage​

import { TogglyProvider } from '@ops-ai/react-native-toggly';
import { createMMKVStorageAdapter } from '@ops-ai/react-native-toggly-storage-mmkv';

const storage = createMMKVStorageAdapter();

function App() {
return (
<TogglyProvider
appKey="your-app-key"
environment="production"
storage={storage}
>
<MyApp />
</TogglyProvider>
);
}

Options​

import { MMKV } from 'react-native-mmkv';
import { createMMKVStorageAdapter } from '@ops-ai/react-native-toggly-storage-mmkv';

// With encryption
const storage = createMMKVStorageAdapter({
encryptionKey: 'your-secret-key',
});

// With custom MMKV instance
const mmkv = new MMKV({ id: 'toggly-storage' });
const storage = createMMKVStorageAdapter({ mmkv });

// Combined options
const storage = createMMKVStorageAdapter({
mmkv: new MMKV({ id: 'my-app-toggly' }),
encryptionKey: 'secret',
});

Comparison​

FeatureAsyncStorageMMKV
SpeedGoodExcellent
Expo Go CompatibleYesNo (dev build required)
EncryptionNoYes
Native SetupMinimalPod install required
Memory UsageHigherLower

Custom Storage Adapter​

You can create a custom storage adapter by implementing the TogglyStorage interface:

import { TogglyStorage } from '@ops-ai/react-native-toggly-core';

class MyCustomStorage implements TogglyStorage {
async get(key: string): Promise<string | null> {
// Retrieve value from your storage
return yourStorageSystem.get(key);
}

async set(key: string, value: string): Promise<void> {
// Store value in your storage
await yourStorageSystem.set(key, value);
}

async delete(key: string): Promise<void> {
// Remove value from your storage
await yourStorageSystem.remove(key);
}
}

// Usage
const storage = new MyCustomStorage();

<TogglyProvider storage={storage}>
<App />
</TogglyProvider>

Storage Behavior​

Cache Keys​

To limit growth when many identities are cached, set maxCacheKeys on TogglyProvider. See Client-side cache limits.

The SDK stores several keys in storage:

KeyDescription
flagsCurrent feature flag values
etagDefinitions revision for cache validation (If-None-Match and WebSocket ?rev=)
identityCurrent user identity
lastSyncTimestamp of last sync

Cache Invalidation​

The cache is automatically invalidated when:

  • User identity changes (setIdentity)
  • Manual refresh is called (refresh())
  • ETag doesn't match server response (definitions revision changed)

See WebSocket sync for how revision caching works with live updates.

Offline Behavior​

When the device is offline:

  1. SDK loads flags from storage cache
  2. If no cache exists, uses featureDefaults
  3. When connectivity returns, flags are automatically refreshed

Storage Errors​

Storage adapters propagate read and write failures to the core SDK. Provide TogglyProvider.onError to report those failures to your monitoring provider:

<TogglyProvider
appKey="your-app-key"
environment="production"
storage={storage}
onError={(error) => {
crashlytics().recordError(error);
}}
>
<App />
</TogglyProvider>

If a storage operation fails during refresh, the SDK keeps the last-known-good in-memory flags when available and reports the failure rather than silently discarding it.

Best Practices​

  1. Always provide featureDefaults - Ensures flags work even with empty cache
  2. Use MMKV for production - Better performance, especially on Android
  3. Use AsyncStorage for Expo Go development - Easier setup, no native code
  4. Consider encryption for sensitive flags - Use MMKV with encryption key
  5. Handle storage errors gracefully - Report onError so storage failures are visible in production