Offline & Data· 5 min read

MMKV vs AsyncStorage vs SQLite: Picking Local Storage

MMKV vs AsyncStorage vs SQLite for React Native in 2026: speed, data shape, encryption and size limits, which to use for what, and how to migrate.

By

Full-Stack, Mobile & AI Engineer

"Where do I store this?" comes up on every React Native project, usually several times: a theme preference, an auth token, a cached feed, a draft post, ten thousand offline records. Most apps end up using the wrong tool for at least one of them, and it shows up as slow startups, lost data or a security finding.

The three common answers are MMKV, AsyncStorage and SQLite. They're not competitors so much as tools for different shapes of data. This guide compares MMKV vs AsyncStorage vs SQLite in 2026, gives a simple rule for each kind of data, and covers encryption and migration.

Key takeaways

  • MMKV is a very fast key-value store with synchronous reads, ideal for settings, flags, small cached objects and persisted state.
  • AsyncStorage is a simple asynchronous key-value store; it still works, but MMKV is faster and more convenient for most new apps.
  • SQLite is a real database: use it for collections that grow, need queries, or must work offline.
  • Secrets don't belong in any of them unencrypted. Tokens go in the Keychain or Keystore (for example via expo-secure-store).
  • Most apps use two or three: MMKV for small state, SQLite for data, secure storage for credentials.

The comparison

MMKVAsyncStorageSQLite
Data modelKey-valueKey-value (strings)Tables and SQL queries
APISynchronousAsynchronous (promises)Async and sync APIs (expo-sqlite)
SpeedVery fast (memory-mapped, JSI)Slower for many reads and writesFast for queries over large data
Good forSettings, flags, small cached objects, persisted storesSimple legacy storageLists, records, offline data, search
QueryingBy key onlyBy key onlyFiltering, sorting, joins, indexes
EncryptionBuilt-in option with a keyNot built inPossible with SQLCipher-enabled builds
Expo GoNeeds a development buildWorksexpo-sqlite works

When to use MMKV

MMKV (via react-native-mmkv) stores key-value data in memory-mapped files and is accessed directly through JSI, so reads are synchronous. That makes it perfect for values you need immediately at startup without awaiting anything:

import { createMMKV } from "react-native-mmkv";

export const storage = createMMKV();

storage.set("theme", "dark");
const theme = storage.getString("theme"); // synchronous, no await

Version 4 is built on Nitro Modules (install react-native-nitro-modules alongside it) and requires React Native 0.76 or later; older guides that use new MMKV() refer to version 3. It's also the most common backend for persisting state libraries (for example Zustand's persist middleware) and TanStack Query's cache, because the synchronous API keeps hydration fast.

Use it for: user preferences, onboarding flags, feature flag caches, persisted UI state, small cached API responses.

Don't use it for: thousands of records you need to filter or sort, or anything you'd want to query.

When AsyncStorage is still fine

@react-native-async-storage/async-storage is the long-standing default key-value store. It's asynchronous, stores strings, and works everywhere, including Expo Go. If your app already uses it for a few small values and performance is fine, there's no urgent reason to change. For new apps, MMKV is usually the better default, and AsyncStorage remains handy when you can't add native code.

When to use SQLite

As soon as your data is a collection (workouts, messages, products, orders taken offline), you want a database:

import * as SQLite from "expo-sqlite";

const db = await SQLite.openDatabaseAsync("app.db");
await db.execAsync(`
  CREATE TABLE IF NOT EXISTS sessions (
    id TEXT PRIMARY KEY NOT NULL,
    started_at INTEGER NOT NULL,
    duration_s INTEGER NOT NULL,
    synced INTEGER NOT NULL DEFAULT 0
  );
`);
const pending = await db.getAllAsync("SELECT * FROM sessions WHERE synced = 0 ORDER BY started_at");

SQLite gives you indexes, transactions, filtering and sorting without loading everything into memory. Pair it with an ORM such as Drizzle for typed queries and migrations, or with WatermelonDB for very large, reactive datasets.

Secrets: a fourth category

Access tokens, refresh tokens and encryption keys should live in the operating system's secure storage (the iOS Keychain and Android Keystore), through expo-secure-store or react-native-keychain. If you need MMKV or SQLite encrypted, store their encryption key in secure storage, not in your JavaScript bundle.

Using MMKV with Zustand, Redux Persist and Expo

MMKV's synchronous API makes it a natural backend for state persistence. A small adapter connects it to Zustand's persist middleware:

import { createMMKV } from "react-native-mmkv";
import { createJSONStorage } from "zustand/middleware";

const mmkv = createMMKV();

export const zustandStorage = createJSONStorage(() => ({
  getItem: (key) => mmkv.getString(key) ?? null,
  setItem: (key, value) => mmkv.set(key, value),
  removeItem: (key) => mmkv.remove(key),
}));
// persist((set) => ({ ... }), { name: "settings", storage: zustandStorage })

Redux Persist expects promises, so wrap the same three calls in Promise.resolve(...). In Expo, install it with npx expo install react-native-mmkv react-native-nitro-modules; it contains native code, so it runs in a development build rather than Expo Go.

MMKV vs expo-secure-store: they solve different problems. Secure storage keeps small secrets such as tokens in the Keychain or Keystore. MMKV is fast general storage for app data. Use both, and if MMKV holds anything sensitive, encrypt it with a key that lives in secure storage.

A simple decision rule

DataStore it in
Theme, language, flags, small UI stateMMKV
Persisted Zustand or Redux stateMMKV
Cached API responses for instant startupMMKV (via TanStack Query persistence)
User-created records, offline data, anything queriedSQLite (optionally with Drizzle or WatermelonDB)
Tokens, passwords, keysSecure storage (Keychain / Keystore)
Large files (images, audio, video)The file system, with paths stored in SQLite
From the field: MMKV, SQLite and WatermelonDB are all part of the offline-first toolkit I've used in production, including the offline-first workout tracking behind MOVE's 100K+ active users. Picking storage by the shape of the data, not by habit, is what keeps both startup fast and data safe.

Common storage mistakes

  • One giant JSON blob under a single key. Every change rewrites everything, and one corrupt write loses it all. Split data by key, or move it to SQLite.
  • Storing lists of records in key-value storage. Fine at 50 items, painful at 5,000. Collections belong in a database.
  • Tokens in plain storage. Anyone with access to a rooted or jailbroken device, or an unencrypted backup, can read them. Use secure storage.
  • No versioning of stored data. When the shape of stored data changes between app versions, old data crashes new code. Store a schema version and migrate on startup.
  • Never clearing anything. Caches grow forever. Set limits and expiry for cached responses and files.

Migrating from AsyncStorage to MMKV

  1. Add react-native-mmkv (and react-native-nitro-modules) and create a development build.
  2. On first launch of the new version, read each AsyncStorage key, write it to MMKV, and set a "migrated" flag.
  3. Switch reads and writes to MMKV.
  4. After a release or two, remove the migration code and the AsyncStorage dependency.

Test the migration on a device that has real data from the old version, not only on a fresh install.

FAQ

Is MMKV faster than AsyncStorage?

Yes, considerably for frequent reads and writes. MMKV uses memory-mapped files and synchronous access through JSI, while AsyncStorage is asynchronous and slower per operation. The difference matters most at startup and for state that's read often.

Should I use SQLite or MMKV in React Native?

Use MMKV for small key-value data such as settings, flags and persisted state. Use SQLite when you store collections of records that grow over time, need filtering or sorting, or must work offline. Many apps use both.

Is AsyncStorage deprecated?

AsyncStorage still works and is maintained as a community package, but it's no longer the obvious default. For new apps, MMKV is usually faster and more convenient for key-value data, and SQLite is better for structured data.

Where should I store auth tokens in React Native?

In secure storage backed by the iOS Keychain and Android Keystore, for example expo-secure-store or react-native-keychain. Don't store tokens in plain AsyncStorage or unencrypted MMKV, which aren't designed to protect secrets.

Next steps

If your app's storage has grown into a tangle of AsyncStorage keys, or you need offline data done properly, I can design the storage layer and migrate safely. Get in touch, or see how I work as a React Native developer.

Sources

Bhuwanesh Sisodia

Bhuwanesh Sisodia

Full-Stack, Mobile & AI Engineer

Bhuwanesh Sisodia is a Full-Stack, Mobile & AI Engineer based in Mumbai, with 7+ years of experience and 50+ apps shipped to the App Store and Google Play. He works independently with startups and product teams worldwide.

About meGitHubLinkedInX

Related