Skip to content

Expo Adapter

The Expo adapter enables LiveStore in React Native applications built with Expo. It uses native SQLite via expo-sqlite for high-performance local persistence on iOS and Android devices.

  • Signals-based reactivity — High-performance state management with fine-grained updates
  • iOS and Android support — Works seamlessly on both platforms with native performance
  • Native SQLite storage — Uses expo-sqlite for fast, reliable persistence directly on the device
  • Offline-first — Full functionality without network connectivity; syncs when connected
  • Real-time sync — Optional sync backend integration for multi-device data synchronization
  • Integrated devtools — Debug and inspect your store via the LiveStore Devtools
Terminal window
npm install @livestore/adapter-expo @livestore/livestore @livestore/react expo-sqlite expo-application

For a complete setup including sync and devtools, see the Expo getting started guide.

Create an adapter and a custom useAppStore() hook, then set up a StoreRegistry with <StoreRegistryProvider>:

import {
const Suspense: ExoticComponent<SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
,
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>] (+1 overload)

Returns a stateful value, and a function to update it.

@version16.8.0

@seehttps://react.dev/reference/react/useState

useState
} from 'react'
import {
function unstable_batchedUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)

React Native also implements unstable_batchedUpdates

unstable_batchedUpdates
as
function batchUpdates<A, R>(callback: (a: A) => R, a: A): R (+1 overload)

React Native also implements unstable_batchedUpdates

batchUpdates
,
class SafeAreaView

@deprecatedUse react-native-safe-area-context instead. This component is deprecated and will be removed in a future release.

SafeAreaView
,
class Text
Text
} from 'react-native'
import {
const makePersistedAdapter: (options?: MakeDbOptions) => Adapter

Creates a persisted LiveStore adapter for Expo/React Native applications.

This adapter stores data in SQLite databases on the device filesystem, providing persistence across app restarts. It supports optional sync backends for multi-device synchronization.

Requirements:

  • React Native New Architecture (Fabric) must be enabled
  • Expo SDK 51+ recommended

@example

import { makePersistedAdapter } from '@livestore/adapter-expo'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makePersistedAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
storage: {
subDirectory: 'my-app',
},
})

@example

// Minimal setup without sync
const adapter = makePersistedAdapter()

@seehttps://livestore.dev/docs/reference/adapters/expo for detailed setup guide

makePersistedAdapter
} from '@livestore/adapter-expo'
import {
const queryDb: {
<TResultSchema, TResult = TResultSchema>(queryInput: QueryInputRaw<TResultSchema, ReadonlyArray<any>> | QueryBuilder<TResultSchema, any, any>, options?: {
map?: (rows: TResultSchema) => TResult;
label?: string;
deps?: DepKey;
}): LiveQueryDef<TResult>;
<TResultSchema, TResult = TResultSchema>(queryInput: ((get: GetAtomResult) => QueryInputRaw<TResultSchema, ReadonlyArray<any>>) | ((get: GetAtomResult) => QueryBuilder<TResultSchema, any, any>), options?: {
map?: (rows: TResultSchema) => TResult;
label?: string;
deps?: DepKey;
}): LiveQueryDef<TResult>;
}

NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.

When using contextual data when constructing the query, please make sure to include it in the deps option.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
,
class StoreRegistry

Store Registry coordinating store loading, caching, and retention

@public

StoreRegistry
} from '@livestore/livestore'
import {
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element

React context provider that makes a

StoreRegistry

available to descendant components.

Wrap your application (or a subtree) with this provider to enable

useStore

and

useStoreRegistry

hooks within that tree.

@example

import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
const storeRegistry = new StoreRegistry({
defaultOptions: { batchUpdates }
})
function App() {
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
<MyComponent />
</StoreRegistryProvider>
)
}

StoreRegistryProvider
,
const useStore: <TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>(options: RegistryStoreOptions<TSchema, TContext, TSyncPayloadSchema>) => Store<TSchema, TContext> & ReactApi

Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.

@example

function Issue() {
// Suspends until loaded or returns immediately if already loaded
const issueStore = useStore(issueStoreOptions('abc123'))
const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
const toggleStatus = () =>
issueStore.commit(
issueEvents.issueStatusChanged({
id: issue.id,
status: issue.status === 'done' ? 'todo' : 'done',
}),
)
const preloadParentIssue = (issueId: string) =>
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
})
return (
<>
<h2>{issue.title}</h2>
<button onClick={() => toggleStatus()}>Toggle Status</button>
<button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
</>
)
}

@returnsThe loaded store instance augmented with React hooks

@throwsunknown - store loading error or if called outside <StoreRegistryProvider>

useStore
} from '@livestore/react'
import {
import schema
schema
,
import tables
tables
} from './schema.ts'
const
const adapter: Adapter
adapter
=
function makePersistedAdapter(options?: MakeDbOptions): Adapter

Creates a persisted LiveStore adapter for Expo/React Native applications.

This adapter stores data in SQLite databases on the device filesystem, providing persistence across app restarts. It supports optional sync backends for multi-device synchronization.

Requirements:

  • React Native New Architecture (Fabric) must be enabled
  • Expo SDK 51+ recommended

@example

import { makePersistedAdapter } from '@livestore/adapter-expo'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makePersistedAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
storage: {
subDirectory: 'my-app',
},
})

@example

// Minimal setup without sync
const adapter = makePersistedAdapter()

@seehttps://livestore.dev/docs/reference/adapters/expo for detailed setup guide

makePersistedAdapter
()
const
const suspenseFallback: JSX.Element
suspenseFallback
= <
class Text
Text
>Loading...</
class Text
Text
>
const
const safeAreaStyle: {
flex: number;
}
safeAreaStyle
= {
flex: number
flex
: 1 }
const
const useAppStore: () => Store<any, {}> & ReactApi
useAppStore
= () =>
useStore<any, {}, Codec<Json, Json, never, never>>(options: RegistryStoreOptions<any, {}, Codec<Json, Json, never, never>>): Store<any, {}> & ReactApi

Returns a store instance augmented with hooks (store.useQuery() and store.useClientDocument()) for reactive queries.

@example

function Issue() {
// Suspends until loaded or returns immediately if already loaded
const issueStore = useStore(issueStoreOptions('abc123'))
const [issue] = issueStore.useQuery(queryDb(tables.issue.select()))
const toggleStatus = () =>
issueStore.commit(
issueEvents.issueStatusChanged({
id: issue.id,
status: issue.status === 'done' ? 'todo' : 'done',
}),
)
const preloadParentIssue = (issueId: string) =>
storeRegistry.preload({
...issueStoreOptions(issueId),
unusedCacheTime: 10_000,
})
return (
<>
<h2>{issue.title}</h2>
<button onClick={() => toggleStatus()}>Toggle Status</button>
<button onMouseEnter={() => preloadParentIssue(issue.parentIssueId)}>Open Parent Issue</button>
</>
)
}

@returnsThe loaded store instance augmented with React hooks

@throwsunknown - store loading error or if called outside <StoreRegistryProvider>

useStore
({
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.storeId: string

Unique identifier for the Store instance, stable for its lifetime.

  • Valid characters: Only alphanumeric characters, underscores (_), and hyphens (-) are allowed. Must match /^[a-zA-Z0-9_-]+$/.
  • Globally unique: Use globally unique IDs (e.g., nanoid) to prevent collisions across stores.
  • Use namespaces: Prefix to avoid collisions and for easier identification when debugging (e.g., app-root, workspace-abc123, issue-456)

storeId
: 'my-app',
CreateStoreOptions<any, {}, Codec<Json, Json, never, never>>.schema: any

The LiveStore schema defining tables, events, and materializers.

schema
,
CreateStoreOptions<TSchema extends LiveStoreSchema, TContext = {}, TSyncPayloadSchema extends Codec<Json, Json> = Codec<Json, Json, never, never>>.adapter: Adapter

Adapter used for data storage and synchronization.

adapter
,
CreateStoreOptions<any, {}, Codec<Json, Json, never, never>>.batchUpdates?: (run: () => void) => void

Needed in React so LiveStore can apply multiple events in a single render.

@example

// With React DOM
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
// With React Native
import { unstable_batchedUpdates as batchUpdates } from 'react-native'

batchUpdates
,
})
export const
const App: () => JSX.Element
App
= () => {
const [
const storeRegistry: StoreRegistry
storeRegistry
] =
useState<StoreRegistry>(initialState: StoreRegistry | (() => StoreRegistry)): [StoreRegistry, Dispatch<SetStateAction<StoreRegistry>>] (+1 overload)

Returns a stateful value, and a function to update it.

@version16.8.0

@seehttps://react.dev/reference/react/useState

useState
(() => new
new StoreRegistry(config?: StoreRegistryConfig): StoreRegistry

Creates a new StoreRegistry instance.

@example

const registry = new StoreRegistry({
defaultOptions: {
batchUpdates,
unusedCacheTime: 30_000,
}
})

StoreRegistry
())
return (
<
class SafeAreaView

@deprecatedUse react-native-safe-area-context instead. This component is deprecated and will be removed in a future release.

SafeAreaView
style?: StyleProp<ViewStyle>
style
={
const safeAreaStyle: {
flex: number;
}
safeAreaStyle
}>
<
const Suspense: ExoticComponent<SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
SuspenseProps.fallback?: ReactNode

A fallback react tree to show when a Suspense child (like React.lazy) suspends

fallback
={
const suspenseFallback: JSX.Element
suspenseFallback
}>
<
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element

React context provider that makes a

StoreRegistry

available to descendant components.

Wrap your application (or a subtree) with this provider to enable

useStore

and

useStoreRegistry

hooks within that tree.

@example

import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
const storeRegistry = new StoreRegistry({
defaultOptions: { batchUpdates }
})
function App() {
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
<MyComponent />
</StoreRegistryProvider>
)
}

StoreRegistryProvider
storeRegistry: StoreRegistry
storeRegistry
={
const storeRegistry: StoreRegistry
storeRegistry
}>
<
const TodoList: () => JSX.Element
TodoList
/>
</
const StoreRegistryProvider: ({ storeRegistry, children }: StoreRegistryProviderProps) => JSX.Element

React context provider that makes a

StoreRegistry

available to descendant components.

Wrap your application (or a subtree) with this provider to enable

useStore

and

useStoreRegistry

hooks within that tree.

@example

import { StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider } from '@livestore/react'
import { unstable_batchedUpdates as batchUpdates } from 'react-dom'
const storeRegistry = new StoreRegistry({
defaultOptions: { batchUpdates }
})
function App() {
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
<MyComponent />
</StoreRegistryProvider>
)
}

StoreRegistryProvider
>
</
const Suspense: ExoticComponent<SuspenseProps>

Lets you display a fallback until its children have finished loading.

@seehttps://react.dev/reference/react/Suspense React Docs

@example

import { Suspense } from 'react';
<Suspense fallback={<Loading />}>
<ProfileDetails />
</Suspense>

Suspense
>
</
class SafeAreaView

@deprecatedUse react-native-safe-area-context instead. This component is deprecated and will be removed in a future release.

SafeAreaView
>
)
}
const
const TodoList: () => JSX.Element
TodoList
= () => {
const
const store: Store<any, {}> & ReactApi
store
=
const useAppStore: () => Store<any, {}> & ReactApi
useAppStore
()
const
const todos: unknown
todos
=
const store: Store<any, {}> & ReactApi
store
.
useQuery: <LiveQueryDef<unknown, "def">>(queryable: LiveQueryDef<unknown, "def">, options?: {
store?: Store;
}) => unknown

Returns the result of a query and subscribes to future updates.

Example:

const App = () => {
const todos = useQuery(queryDb(tables.todos.query.where({ complete: true })))
return <div>{todos.map((todo) => <div key={todo.id}>{todo.title}</div>)}</div>
}

useQuery
(
queryDb<unknown, unknown>(queryInput: QueryBuilder<unknown, any, any> | QueryInputRaw<unknown, readonly any[]>, options?: {
map?: (rows: unknown) => unknown;
label?: string;
deps?: DepKey;
} | undefined): LiveQueryDef<unknown, "def"> (+1 overload)

NOTE queryDb is only supposed to read data. Don't use it to insert/update/delete data but use events instead.

When using contextual data when constructing the query, please make sure to include it in the deps option.

@example

const todos$ = queryDb(tables.todos.where({ complete: true }))

@example

// Group-by raw SQL query
const colorCounts$ = queryDb({
query: sql`SELECT color, COUNT(*) as count FROM todos WHERE complete = ? GROUP BY color`,
schema: Schema.Array(Schema.Struct({
color: Schema.String,
count: Schema.Number,
})),
bindValues: [1],
})

@example

// Using contextual data when constructing the query
const makeFilteredQuery = (filter: string) =>
queryDb(tables.todos.where({ title: { op: 'like', value: filter } }), { deps: [filter] })
const filteredTodos$ = makeFilteredQuery('buy coffee')

queryDb
(
import tables
tables
.
any
todos
.
any
select
()))
return <
class Text
Text
>{
const todos: unknown
todos
.
any
length
} todos</
class Text
Text
>
}

For more details on the registry, and hooks, see the React integration guide.

import {
const makePersistedAdapter: (options?: MakeDbOptions) => Adapter

Creates a persisted LiveStore adapter for Expo/React Native applications.

This adapter stores data in SQLite databases on the device filesystem, providing persistence across app restarts. It supports optional sync backends for multi-device synchronization.

Requirements:

  • React Native New Architecture (Fabric) must be enabled
  • Expo SDK 51+ recommended

@example

import { makePersistedAdapter } from '@livestore/adapter-expo'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makePersistedAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
storage: {
subDirectory: 'my-app',
},
})

@example

// Minimal setup without sync
const adapter = makePersistedAdapter()

@seehttps://livestore.dev/docs/reference/adapters/expo for detailed setup guide

makePersistedAdapter
} from '@livestore/adapter-expo'
const
const adapter: Adapter
adapter
=
function makePersistedAdapter(options?: MakeDbOptions): Adapter

Creates a persisted LiveStore adapter for Expo/React Native applications.

This adapter stores data in SQLite databases on the device filesystem, providing persistence across app restarts. It supports optional sync backends for multi-device synchronization.

Requirements:

  • React Native New Architecture (Fabric) must be enabled
  • Expo SDK 51+ recommended

@example

import { makePersistedAdapter } from '@livestore/adapter-expo'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makePersistedAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
storage: {
subDirectory: 'my-app',
},
})

@example

// Minimal setup without sync
const adapter = makePersistedAdapter()

@seehttps://livestore.dev/docs/reference/adapters/expo for detailed setup guide

makePersistedAdapter
({
storage?: {
directory?: string;
subDirectory?: string;
}
storage
: {
// Optional: custom base directory (defaults to expo-sqlite's default)
// directory: '/custom/path/to/databases',
subDirectory?: string

Sub-directory relative to the configured directory (or expo-sqlite's default directory if not specified).

Example of a resulting path for subDirectory: 'my-app': /data/Containers/Data/Application/<APP_UUID>/Documents/ExponentExperienceData/@<USERNAME>/<APPNAME>/SQLite/my-app/<STORE_ID>/livestore-eventlog@3.db

subDirectory
: 'my-app',
},
})
OptionTypeDescription
storage.directorystringBase directory for database files (defaults to expo-sqlite’s default directory)
storage.subDirectorystringSubdirectory relative to directory for organizing databases
syncSyncOptionsSync backend configuration (see Syncing)
clientIdstringCustom client identifier (defaults to device ID)
sessionIdstringSession identifier (defaults to 'static')
resetPersistencebooleanClear local databases on startup (development only)

Connect to a sync backend for multi-device synchronization:

import {
const makePersistedAdapter: (options?: MakeDbOptions) => Adapter

Creates a persisted LiveStore adapter for Expo/React Native applications.

This adapter stores data in SQLite databases on the device filesystem, providing persistence across app restarts. It supports optional sync backends for multi-device synchronization.

Requirements:

  • React Native New Architecture (Fabric) must be enabled
  • Expo SDK 51+ recommended

@example

import { makePersistedAdapter } from '@livestore/adapter-expo'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makePersistedAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
storage: {
subDirectory: 'my-app',
},
})

@example

// Minimal setup without sync
const adapter = makePersistedAdapter()

@seehttps://livestore.dev/docs/reference/adapters/expo for detailed setup guide

makePersistedAdapter
} from '@livestore/adapter-expo'
const
const adapter: Adapter
adapter
=
function makePersistedAdapter(options?: MakeDbOptions): Adapter

Creates a persisted LiveStore adapter for Expo/React Native applications.

This adapter stores data in SQLite databases on the device filesystem, providing persistence across app restarts. It supports optional sync backends for multi-device synchronization.

Requirements:

  • React Native New Architecture (Fabric) must be enabled
  • Expo SDK 51+ recommended

@example

import { makePersistedAdapter } from '@livestore/adapter-expo'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makePersistedAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
storage: {
subDirectory: 'my-app',
},
})

@example

// Minimal setup without sync
const adapter = makePersistedAdapter()

@seehttps://livestore.dev/docs/reference/adapters/expo for detailed setup guide

makePersistedAdapter
({
sync?: SyncOptions
sync
: {
backend?: SyncBackendConstructor<any, JsonValue>
backend
:
function makeWsSync(options: WsSyncOptions): SyncBackendConstructor<SyncMetadata>

Creates a sync backend that uses WebSocket to communicate with the sync backend.

@example

import { makeWsSync } from '@livestore/sync-cf/client'
const syncBackend = makeWsSync({ url: 'wss://sync.example.com' })

makeWsSync
({
WsSyncOptions.url: string

URL of the sync backend

The protocol can either http/https or ws/wss

url
: 'wss://your-sync-backend.com' }) },
})

See the Syncing documentation for available sync providers and configuration options.

Android requires HTTPS for network connections by default. During development with a local sync backend using http:// or ws://, add expo-build-properties to allow cleartext traffic:

Terminal window
npx expo install expo-build-properties

Then configure app.json:

{
"expo": {
"plugins": [
[
"expo-build-properties",
{
"android": {
"usesCleartextTraffic": true
}
}
]
]
}
}

See Expo build properties documentation for more details.

No special configuration required. The adapter automatically retrieves the iOS vendor ID for client identification.

LiveStore provides integrated devtools for debugging your store. In development, press shift + m in the Expo CLI terminal, then select “LiveStore Devtools” to open the browser-based inspector.

See the Devtools reference for full documentation.

Databases are stored in the device’s SQLite directory. The exact path depends on your setup:

Expo Go:

Terminal window
open $(find $(xcrun simctl get_app_container booted host.exp.Exponent data) -path "*/Documents/ExponentExperienceData/*livestore*" -print -quit)/SQLite

Development builds:

Terminal window
open $(xcrun simctl get_app_container booted [APP_BUNDLE_ID] data)/Documents/SQLite

Replace [APP_BUNDLE_ID] with your app’s bundle identifier (e.g., dev.livestore.myapp).

During development, you can clear local databases on startup:

import {
const makePersistedAdapter: (options?: MakeDbOptions) => Adapter

Creates a persisted LiveStore adapter for Expo/React Native applications.

This adapter stores data in SQLite databases on the device filesystem, providing persistence across app restarts. It supports optional sync backends for multi-device synchronization.

Requirements:

  • React Native New Architecture (Fabric) must be enabled
  • Expo SDK 51+ recommended

@example

import { makePersistedAdapter } from '@livestore/adapter-expo'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makePersistedAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
storage: {
subDirectory: 'my-app',
},
})

@example

// Minimal setup without sync
const adapter = makePersistedAdapter()

@seehttps://livestore.dev/docs/reference/adapters/expo for detailed setup guide

makePersistedAdapter
} from '@livestore/adapter-expo'
const
const resetPersistence: boolean
resetPersistence
=
var process: NodeJS.Process
process
.
NodeJS.Process.env: NodeJS.ProcessEnv

The process.env property returns an object containing the user environment. See environ(7).

An example of this object looks like:

{
TERM: 'xterm-256color',
SHELL: '/usr/local/bin/bash',
USER: 'maciej',
PATH: '~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin',
PWD: '/Users/maciej',
EDITOR: 'vim',
SHLVL: '1',
HOME: '/Users/maciej',
LOGNAME: 'maciej',
_: '/usr/local/bin/node'
}

It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other Worker threads. In other words, the following example would not work:

node -e 'process.env.foo = "bar"' &#x26;&#x26; echo $foo

While the following will:

import { env } from 'node:process';
env.foo = 'bar';
console.log(env.foo);

Assigning a property on process.env will implicitly convert the value to a string. This behavior is deprecated. Future versions of Node.js may throw an error when the value is not a string, number, or boolean.

import { env } from 'node:process';
env.test = null;
console.log(env.test);
// => 'null'
env.test = undefined;
console.log(env.test);
// => 'undefined'

Use delete to delete a property from process.env.

import { env } from 'node:process';
env.TEST = 1;
delete env.TEST;
console.log(env.TEST);
// => undefined

On Windows operating systems, environment variables are case-insensitive.

import { env } from 'node:process';
env.TEST = 1;
console.log(env.test);
// => 1

Unless explicitly specified when creating a Worker instance, each Worker thread has its own copy of process.env, based on its parent thread's process.env, or whatever was specified as the env option to the Worker constructor. Changes to process.env will not be visible across Worker threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of process.env on a Worker instance operates in a case-sensitive manner unlike the main thread.

@sincev0.1.27

env
.
string | undefined
EXPO_PUBLIC_LIVESTORE_RESET
=== 'true'
const
const _adapter: Adapter
_adapter
=
function makePersistedAdapter(options?: MakeDbOptions): Adapter

Creates a persisted LiveStore adapter for Expo/React Native applications.

This adapter stores data in SQLite databases on the device filesystem, providing persistence across app restarts. It supports optional sync backends for multi-device synchronization.

Requirements:

  • React Native New Architecture (Fabric) must be enabled
  • Expo SDK 51+ recommended

@example

import { makePersistedAdapter } from '@livestore/adapter-expo'
import { makeWsSync } from '@livestore/sync-cf/client'
const adapter = makePersistedAdapter({
sync: {
backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
},
storage: {
subDirectory: 'my-app',
},
})

@example

// Minimal setup without sync
const adapter = makePersistedAdapter()

@seehttps://livestore.dev/docs/reference/adapters/expo for detailed setup guide

makePersistedAdapter
({
storage?: {
directory?: string;
subDirectory?: string;
}
storage
: {
subDirectory?: string

Sub-directory relative to the configured directory (or expo-sqlite's default directory if not specified).

Example of a resulting path for subDirectory: 'my-app': /data/Containers/Data/Application/<APP_UUID>/Documents/ExponentExperienceData/@<USERNAME>/<APPNAME>/SQLite/my-app/<STORE_ID>/livestore-eventlog@3.db

subDirectory
: 'dev' },
resetPersistence?: boolean

Warning: This will reset both the app and eventlog database. This should only be used during development.

@defaultfalse

resetPersistence
,
})

The Expo adapter runs LiveStore directly in the main JavaScript thread, using native SQLite bindings provided by expo-sqlite. This differs from the web adapter, which uses web workers and WASM-based SQLite.

┌─────────────────────────────────────────┐
│ React Native App │
│ ┌───────────────────────────────────┐ │
│ │ LiveStore Client │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ State DB │ │ Eventlog DB │ │ │
│ │ │(expo-sqlite)│ │(expo-sqlite)│ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
▼ WebSocket
┌───────────────┐
│ Sync Backend │
│ (optional) │
└───────────────┘

We’re exploring moving database operations to a background thread to further improve UI responsiveness during intensive writes. Follow livestore/livestore for updates.