MMKV
Set up MMKV for storage in an Expo app with ReadyNative: fastest sync key/value (Nitro) · dev build required (no Expo Go)
Expo Go: no - dev build required
Ships native code. Make a dev build with bunx expo run:ios / bunx expo run:android (or bunx eas-cli build --profile development).
MMKV is the fastest option: a synchronous, memory-mapped key/value store on react-native-mmkv 4 (Nitro), behind the same src/lib/storage.ts. I'd pick it when you already need a dev build for something else (Unistyles, Sentry, RevenueCat…) and want stores and zustand persist to hydrate before first paint with no async fallback. Stay on kv-store if Expo Go matters, and choose async-storage only for a library that hard-requires it. Callers don't change either way: they import storage from @/lib/storage.
Setup
bun run setup --storage mmkv- Make a dev build -
react-native-mmkvis autolinked native code:npx expo run:iosornpx expo run:android(oreas build --profile development). Expo Go will not load it.
Setup writes src/lib/storage.ts (createMmkvStorage() with the instance id readynative), adds react-native-mmkv ^4.3.2 and react-native-nitro-modules 0.37.1 (the same pin as ui/unistyles), a __mocks__/react-native-nitro-modules.ts jest stand-in, and a round-trip test. There's no config plugin.
Usage
import { storage } from "@/lib/storage";
storage.getItemSync?.("readynative:draft"); // always present with MMKV
storage.setItemSync?.("readynative:draft", JSON.stringify(draft));
await storage.setItem("readynative:draft", JSON.stringify(draft)); // async methods wrap the sync onesThe built-in stores, zustand persist / Jotai atomWithAppStorage and i18n all go through the adapter, so switching to MMKV needs no caller changes - they stop awaiting.
Gotchas
- Dev build required.
bun run doctorflags it if you're on Expo Go. react-native-nitro-modulesthrows on import without the native runtime, hence the__mocks__file. If you add another Nitro library, extend that mock or add your ownjest.mock.- Each
createMMKV({ id })is its own file. Don't changeMMKV_INSTANCE_IDafter shipping or stored data becomes invisible. - Web falls back to
localStorage(web isn't a gate). - Swapping stacks:
bun run setup --storage kv-store|async-storagereplacessrc/lib/storage.ts. Keys stay the samereadynative:*strings but the data lives in a different file, so existing values do not move - users start fresh. No migration ships.
Remove it
Every app needs a storage option, so there is no none to fall back to - you switch instead. While modules/ exists (a tree set up with --keep-modules), one call does it:
bun run setup --storage kv-store --yes --keep-modulesA finalized tree can't switch: clone a fresh copy of your tier repo, run setup there with the new option and port your code across - see Can I change a module after setup?.
Reference
Everything below is generated from modules/storage/mmkv/module.json - the same file bun run setup reads, so it is what actually lands in your repo.
Install
bun run setup --storage mmkvModule id: storage/mmkv.
Dependencies
| Package | Version | Kind |
|---|---|---|
react-native-mmkv | ^4.3.2 | dependency |
react-native-nitro-modules | 0.37.1 | dependency |
Privacy
Required-reason APIs: UserDefaults (CA92.1).
Doctor checks
- dev build toolchain (Xcode / Android SDK)
After setup
- MMKV 4 is a native (Nitro) module: make a dev build with
npx expo run:ios/npx expo run:android(oreas build --profile development). Expo Go will not load it.
Files
3 files copied to the project root
__mocks__/react-native-nitro-modules.tssrc/lib/__tests__/storage-mmkv.test.tssrc/lib/storage.ts