@react-native-async-storage/async-storage
Async Storage is unencrypted, persistent string key-value storage for React Native and supported sibling platforms. Version 3 creates named storage instances backed by SQLite on Android, iOS, and macOS, IndexedDB on the web, and a legacy single-database fallback on Windows and visionOS. It supports promise-based single and atomic batch operations. It is useful for preferences, cached responses, and state persistence, but it is not a secrets vault, relational database, or synchronous replacement for in-memory state.
A sensible default for non-secret React Native persistence, especially when named stores and atomic batches help. Version 3 has meaningful platform floors and migration breaks, so read the migration guide before upgrading and keep credentials elsewhere.
Use it if
- You need durable app preferences, cached JSON, onboarding flags, or persisted state across React Native restarts
- You want separate named databases for users, caches, or features on Android, iOS, macOS, and the web
- You need atomic setMany, getMany, and removeMany operations over groups of string keys
- You target the current platform floors: React Native 0.76+, Android API 24+, and iOS 13+
- You need to store tokens, passwords, private keys, or regulated data: the README calls the storage unencrypted, so use a platform keychain or secure-store package
- Your application still targets React Native below 0.76, Android below API 24, or iOS below 13: version 3's compatibility table drops those environments
- You need multiple named databases on Windows or visionOS: the database-naming documentation says those legacy fallbacks support only one storage
- You are upgrading code that depends on callbacks, mergeItem, multiMerge, or useAsyncStorage: the version 3 migration guide removes all of them and switches new work to instance methods
- You need queries, indexes, partial record updates, or transactions spanning structured records: values are strings and objects require JSON serialization, so SQLite or another database API is a better fit
Setup reality
Version 3 is not a drop-in upgrade from the long-lived singleton API. Install the package, then run pod install inside ios or macos for native projects; Expo users need a development build when native code is involved. The new default is createAsyncStorage('database-name'), and the name maps to a SQLite file directory on Android, iOS, and macOS or directly to an IndexedDB database on web. Do not include a file extension in the name. Windows and visionOS use a legacy fallback and cannot create multiple stores. The package requires React Native 0.76 on Android and iOS, 0.78 on macOS, 0.79 on Windows and visionOS, Android API 24, iOS 13, and macOS 12. Values remain strings, so JSON.stringify and JSON.parse are still application code, including schema migration and corrupt-data handling. Version 3 removes callbacks, mergeItem, multiMerge, and the useAsyncStorage hook. Its batch names are setMany, getMany, and removeMany, and the docs say writes are atomic. The default export remains as a compatibility pointer to the version 2 storage, but new integrations should not build on it. Jest cannot load the native module directly: transform the package's ESM source and use the shipped /jest in-memory mock, clearing all mock databases between tests. Production reads and writes can reject with typed native, web, SQLite, other-storage, or unknown errors, so wrap persistence boundaries. Finally, none of these platform stores encrypts data. Put credentials in Keychain, Keystore, or Expo SecureStore and reserve Async Storage for data whose disclosure does not become an account compromise.
Patterns
Create a named version 3 storagecreate-storage
import { createAsyncStorage } from '@react-native-async-storage/async-storage';
export const userStorage = createAsyncStorage('user');Do not add .db or .sqlite to the name; the native backends normalize it into their own database path.
Store and retrieve a stringstore-string
await userStorage.setItem('theme', 'dark');
const theme = await userStorage.getItem('theme');
if (theme === null) {
console.log('no saved theme');
}getItem returns null for a missing key; an empty string is a real stored value and should not be treated as missing.
Serialize an object explicitlystore-json
const preferences = { theme: 'dark', fontScale: 1.1 };
await userStorage.setItem('preferences', JSON.stringify(preferences));
const raw = await userStorage.getItem('preferences');
const restored = raw === null ? null : JSON.parse(raw);The storage only accepts strings; validate or migrate restored JSON before trusting its shape after an app update.
Remove one saved valueremove-item
await userStorage.removeItem('draft');Removing a missing key is harmless; use a separate secure store when the value is an authentication secret.
Write several values atomicallywrite-batch
await userStorage.setMany([
['theme', 'dark'],
['locale', 'en-GB'],
['onboardingComplete', 'true'],
]);Version 3 calls this setMany, not the legacy multiSet, and the documentation says the batch is atomic.
Read several keys in one operationread-batch
const values = await userStorage.getMany(['theme', 'locale']);
const byKey = new Map(values);
console.log(byKey.get('theme'));A requested key that does not exist is returned with null, so keep missing-value handling explicit.
Delete several keys atomicallyremove-batch
await userStorage.removeMany(['draft', 'pendingUpload', 'lastError']);The version 3 name is removeMany; old multiRemove calls belong only to the compatibility singleton.
Inspect keys before clearing one databaselist-and-clear
const keys = await userStorage.getAllKeys();
console.log('removing', keys.length, 'keys');
await userStorage.clear();clear affects the selected named storage, but it is still destructive; do not use it for a shared compatibility store during logout unless every key is user-owned.
Handle typed storage failureshandle-storage-errors
import { SqliteStorageError } from '@react-native-async-storage/async-storage';
try {
await userStorage.setItem('theme', 'dark');
} catch (error) {
if (error instanceof SqliteStorageError) {
reportStorageFailure(error.message);
} else {
throw error;
}
}The docs distinguish native-module, web, SQLite, other-storage, and unknown errors; avoid swallowing all persistence failures as cache misses.
Use the package's Jest implementationmock-in-jest
jest.mock('@react-native-async-storage/async-storage', () =>
require('@react-native-async-storage/async-storage/jest')
);
const { clearAllMockStorages } = require('@react-native-async-storage/async-storage/jest');
afterEach(() => clearAllMockStorages());The package ships ESM source, so Jest may also need it added to transformIgnorePatterns before this mock can load.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| react-native-mmkv | npm | You need a faster synchronous mobile key-value store and accept its native integration and different web story |
| expo-secure-store | npm | You are in Expo and need encrypted platform storage for small credentials or tokens |
| react-native-keychain | npm | You need Keychain and Keystore-backed secrets plus biometric or access-control options |