Storage adapters
The four factories that tell the Storage tab which stores exist, and what they may do to them.
Four factories, all built on the last one. Each returns a StorageAdapterDefinition for
storage.adapters; ids are assigned from the names at init(), suffixed on collision.
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as SecureStore from 'expo-secure-store';
import { createMMKV } from 'react-native-mmkv';
const mmkv = createMMKV();
createDevtoolsClient({
storage: {
adapters: [
asyncStorageAdapter({ driver: AsyncStorage }),
mmkvAdapter({ driver: mmkv }),
secureStoreAdapter({ driver: SecureStore, keys: ['session', 'pin'] }),
defineStorageAdapter({
name: 'In-memory',
kind: 'sync',
getAllKeys: () => [...map.keys()],
getItem: (key) => map.get(key) ?? null,
setItem: (key, text) => {
map.set(key, text);
},
removeItem: (key) => {
map.delete(key);
},
}),
],
},
});| Factory | For |
|---|---|
asyncStorageAdapter({ driver }) | @react-native-async-storage/async-storage and anything copying its API. Uses getMany (v3) or multiGet (v1/v2) for batch reads when present. |
mmkvAdapter({ driver }) | react-native-mmkv, both majors — v4's remove and v3's delete are both accepted. |
secureStoreAdapter({ driver, keys }) | expo-secure-store. Takes keys because the keychain cannot be listed, and an optional options passed through to every call. |
defineStorageAdapter({ ... }) | Anything else. Duck-types nothing; takes exactly what you hand it. |
Shared fields
All four accept these:
| Field | Type | What it does |
|---|---|---|
name | string | Shown in the store dropdown. Defaulted from the library. |
readOnly | boolean | Makes this store read-only regardless of what else you passed. |
supportedTypes | StorageValueType[] | What the store can actually hold. Defaults to all four. |
blacklist | RegExp | ((key: string) => boolean) | A key it matches is never listed, never read and never written. |
kind | 'sync' | 'async' | The badge the tab shows. Never a branch — every read is awaited either way. |
defineStorageAdapter
Needs either getAllKeys or a fixed keys list — passing keys is what turns enumeration off, and it
may be a function, resolved on every read, for an app that keeps its own list of what it stored. Then
any of getItem (required), getMany, setItem and removeItem.
Whether the tab can edit or delete is derived from which of those you provided, so a store you registered read-only in effect is read-only in the UI without a flag. Sync functions are fine everywhere: they are awaited, not branched on.
getItem may return a bare string | null, or a { text, valueType } when the type matters — that
second form is how mmkvAdapter keeps a stored 1 from rendering as "1", and what an edit is written
back through.
blacklist
A RegExp or a (key: string) => boolean. A key it matches is never listed, never read, and never
written — the filtering happens in the adapter, before the read, not in the view, so the value never
reaches memory at all. A /g regex is safe: lastIndex is reset before every test.
The summary says a blacklist is set, but never how many keys it matched. That is a count this tab deliberately never learns.
supportedTypes
The list of types the store can really hold; it defaults to all four. asyncStorageAdapter and
secureStoreAdapter declare ['string'] for you, since both hand back a string whatever went in; MMKV
takes all four.
The Add-key sheet offers only these types, so a write the store would have flattened is never offered in the first place. Binary is never offered for either creating or editing — there is no text form of the bytes to round-trip.