expo-native-storage is a drop-in replacement for AsyncStorage with zero breaking changes. This guide will help you migrate your existing AsyncStorage code.
Why migrate?
Migrating to expo-native-storage provides:
15x faster on Android (up to 32x with scale)
19x smaller bundle size (19.6KB vs 381KB)
Sync API for immediate access without Promises
Same API - minimal code changes required
Quick migration
The simplest migration is a direct import replacement:
Before (AsyncStorage)
import AsyncStorage from '@react-native-async-storage/async-storage' ;
await AsyncStorage . setItem ( 'key' , 'value' );
const value = await AsyncStorage . getItem ( 'key' );
await AsyncStorage . removeItem ( 'key' );
await AsyncStorage . clear ();
After (expo-native-storage)
import Storage from 'expo-native-storage' ;
await Storage . setItem ( 'key' , 'value' );
const value = await Storage . getItem ( 'key' );
await Storage . removeItem ( 'key' );
await Storage . clear ();
Simply replace the import statement. All method signatures are identical.
Step-by-step migration
Install expo-native-storage
Add expo-native-storage to your project: npx expo install expo-native-storage
Rebuild your app
expo-native-storage requires native code, so rebuild your app: npx expo prebuild --clean
npx expo run:ios # or run:android
Replace imports
Update all AsyncStorage imports in your codebase: // Before
import AsyncStorage from '@react-native-async-storage/async-storage' ;
// After
import Storage from 'expo-native-storage' ;
Use find-and-replace in your editor to update all files at once:
Find: from '@react-native-async-storage/async-storage'
Replace: from 'expo-native-storage'
Update method calls
Replace AsyncStorage with Storage in all method calls: // Before
await AsyncStorage . setItem ( 'key' , 'value' );
const value = await AsyncStorage . getItem ( 'key' );
// After
await Storage . setItem ( 'key' , 'value' );
const value = await Storage . getItem ( 'key' );
Use find-and-replace:
Find: AsyncStorage.
Replace: Storage.
Test your app
Run your app and verify all storage operations work correctly: // Test basic operations
await Storage . setItem ( 'test' , 'value' );
const result = await Storage . getItem ( 'test' );
console . log ( result ); // Should log 'value'
Remove AsyncStorage (optional)
Once migration is complete, uninstall AsyncStorage: npm uninstall @react-native-async-storage/async-storage
API compatibility
expo-native-storage implements all common AsyncStorage methods:
Fully compatible methods
AsyncStorage Method expo-native-storage Notes setItem(key, value)✅ setItem(key, value) Identical getItem(key)✅ getItem(key) Identical removeItem(key)✅ removeItem(key) Identical clear()✅ clear() Identical multiSet(items)✅ multiSet(items) Identical multiGet(keys)✅ multiGet(keys) Identical
Additional methods in expo-native-storage
expo-native-storage provides extra functionality:
Method Description setObject(key, object)Store objects directly (auto JSON.stringify) getObject<T>(key)Retrieve objects with type safety setItemSync(key, value)Synchronous string storage getItemSync(key)Synchronous string retrieval setObjectSync(key, object)Synchronous object storage getObjectSync<T>(key)Synchronous object retrieval removeItemSync(key)Synchronous item removal clearSync()Synchronous clear all
Migration examples
Example 1: User authentication
Before (AsyncStorage)
After (expo-native-storage)
import AsyncStorage from '@react-native-async-storage/async-storage' ;
const login = async ( token : string ) => {
await AsyncStorage . setItem ( 'authToken' , token );
};
const getToken = async () => {
return await AsyncStorage . getItem ( 'authToken' );
};
const logout = async () => {
await AsyncStorage . removeItem ( 'authToken' );
};
Example 2: User preferences
Before (AsyncStorage)
After (expo-native-storage)
import AsyncStorage from '@react-native-async-storage/async-storage' ;
const savePreferences = async ( prefs : UserPrefs ) => {
await AsyncStorage . setItem ( 'preferences' , JSON . stringify ( prefs ));
};
const loadPreferences = async () => {
const json = await AsyncStorage . getItem ( 'preferences' );
return json ? JSON . parse ( json ) : null ;
};
expo-native-storage’s setObject and getObject methods handle JSON serialization automatically, eliminating boilerplate code.
Example 3: Theme persistence with sync API
Before (AsyncStorage)
After (expo-native-storage)
import AsyncStorage from '@react-native-async-storage/async-storage' ;
function ThemeProvider ({ children }) {
const [ theme , setTheme ] = useState ( 'light' );
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
AsyncStorage . getItem ( 'theme' ). then ( savedTheme => {
if ( savedTheme ) setTheme ( savedTheme );
setLoading ( false );
});
}, []);
const updateTheme = async ( newTheme : string ) => {
setTheme ( newTheme );
await AsyncStorage . setItem ( 'theme' , newTheme );
};
if ( loading ) return < LoadingScreen />;
return < ThemeContext . Provider value ={{ theme , updateTheme }}>{ children } </ ThemeContext . Provider > ;
}
The sync API eliminates loading states and reduces code complexity by providing immediate access to stored values.
Data migration
Your existing AsyncStorage data will not automatically transfer to expo-native-storage. To migrate existing data:
import AsyncStorage from '@react-native-async-storage/async-storage' ;
import Storage from 'expo-native-storage' ;
export async function migrateData () {
try {
// Get all keys from AsyncStorage
const keys = await AsyncStorage . getAllKeys ();
// Migrate each key
for ( const key of keys ) {
const value = await AsyncStorage . getItem ( key );
if ( value !== null ) {
await Storage . setItem ( key , value );
}
}
console . log ( `Migrated ${ keys . length } keys from AsyncStorage` );
// Optional: Clear AsyncStorage after successful migration
// await AsyncStorage.clear();
} catch ( error ) {
console . error ( 'Migration failed:' , error );
}
}
Run the migration once on app startup:
import { useEffect , useState } from 'react' ;
import { migrateData } from './migration' ;
export default function App () {
const [ isMigrated , setIsMigrated ] = useState ( false );
useEffect (() => {
const migrate = async () => {
// Check if already migrated
const migrated = Storage . getItemSync ( 'migration_complete' );
if ( ! migrated ) {
await migrateData ();
Storage . setItemSync ( 'migration_complete' , 'true' );
}
setIsMigrated ( true );
};
migrate ();
}, []);
if ( ! isMigrated ) return < LoadingScreen />;
return < MainApp />;
}
Breaking changes
expo-native-storage has no breaking changes from AsyncStorage for common operations. However, note these differences:
getAllKeys method not implemented
AsyncStorage’s getAllKeys() is not currently available in expo-native-storage. Workaround : Track keys manually:// Store a list of keys
const keys = Storage . getObjectSync < string []>( '_keys' ) || [];
// Add key when storing
const setItemTracked = async ( key : string , value : string ) => {
await Storage . setItem ( key , value );
const keys = Storage . getObjectSync < string []>( '_keys' ) || [];
if ( ! keys . includes ( key )) {
Storage . setObjectSync ( '_keys' , [ ... keys , key ]);
}
};
mergeItem not implemented
AsyncStorage’s mergeItem() is not available. Workaround : Read, merge, and write manually:const mergeObject = async ( key : string , updates : Record < string , any >) => {
const existing = await Storage . getObject ( key ) || {};
const merged = { ... existing , ... updates };
await Storage . setObject ( key , merged );
};
Different storage locations
expo-native-storage uses platform-native storage (UserDefaults/SharedPreferences) while AsyncStorage uses SQLite. Impact : Existing AsyncStorage data won’t automatically appear in expo-native-storage. Use the data migration script above if needed.
Next steps
Best Practices Learn patterns for effective storage usage.
Troubleshooting Resolve common migration issues.