# Expo Adapter

The Expo adapter enables LiveStore in React Native applications built with [Expo](https://expo.dev). It uses native SQLite via `expo-sqlite` for high-performance local persistence on iOS and Android devices.

## Key Features

- **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](/building-with-livestore/devtools)

## Requirements

- [Expo New Architecture](https://docs.expo.dev/guides/new-architecture/) (Fabric) must be enabled
- `expo-sqlite` ^16.0.0
- `expo-application` ^7.0.0

:::note
Expo Web is not currently supported. See [#130](https://github.com/livestorejs/livestore/issues/130) for progress.
:::

## Installation

```bash
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](/getting-started/expo).

## Basic Usage

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


## `reference/platform-adapters/expo-adapter/usage.tsx`

```tsx filename="reference/platform-adapters/expo-adapter/usage.tsx"
import { Suspense, useState } from 'react'
import { unstable_batchedUpdates as batchUpdates, SafeAreaView, Text } from 'react-native'

import { makePersistedAdapter } from '@livestore/adapter-expo'
import { queryDb, StoreRegistry } from '@livestore/livestore'
import { StoreRegistryProvider, useStore } from '@livestore/react'

import { schema, tables } from './schema.ts'

const adapter = makePersistedAdapter()
const suspenseFallback = <Text>Loading...</Text>
const safeAreaStyle = { flex: 1 }

const useAppStore = () =>
  useStore({
    storeId: 'my-app',
    schema,
    adapter,
    batchUpdates,
  })

export const App = () => {
  const [storeRegistry] = useState(() => new StoreRegistry())
  return (
    <SafeAreaView style={safeAreaStyle}>
      <Suspense fallback={suspenseFallback}>
        <StoreRegistryProvider storeRegistry={storeRegistry}>
          <TodoList />
        </StoreRegistryProvider>
      </Suspense>
    </SafeAreaView>
  )
}

const TodoList = () => {
  const store = useAppStore()
  const todos = store.useQuery(queryDb(tables.todos.select()))
  return <Text>{todos.length} todos</Text>
}
```

### `reference/platform-adapters/expo-adapter/schema.ts`

```ts filename="reference/platform-adapters/expo-adapter/schema.ts"
import { defineMaterializer, Events, makeSchema, Schema, State } from '@livestore/livestore'

export const tables = {
  todos: State.SQLite.table({
    name: 'todos',
    columns: {
      id: State.SQLite.text({ primaryKey: true }),
      text: State.SQLite.text(),
      completed: State.SQLite.boolean({ default: false }),
    },
  }),
} as const

const events = {
  todoCreated: Events.synced({
    name: 'v1.TodoCreated',
    schema: Schema.Struct({ id: Schema.String, text: Schema.String }),
  }),
} as const

const materializers = State.SQLite.materializers(events, {
  [events.todoCreated.name]: defineMaterializer(events.todoCreated, ({ id, text }) =>
    tables.todos.insert({ id, text, completed: false }),
  ),
})

const state = State.SQLite.makeState({ tables, materializers })

export const schema = makeSchema({ events, state })
```

For more details on the registry, and hooks, see the [React integration guide](/framework-integrations/react-integration).

## Configuration Options


## `reference/platform-adapters/expo-adapter/adapter.ts`

```ts filename="reference/platform-adapters/expo-adapter/adapter.ts"
/** biome-ignore-all lint/correctness/noUnusedVariables: docs snippet keeps inline adapter */
// ---cut---
import { makePersistedAdapter } from '@livestore/adapter-expo'

const adapter = makePersistedAdapter({
  storage: {
    // Optional: custom base directory (defaults to expo-sqlite's default)
    // directory: '/custom/path/to/databases',
    subDirectory: 'my-app',
  },
})
```

### Available Options

| Option | Type | Description |
|--------|------|-------------|
| `storage.directory` | `string` | Base directory for database files (defaults to `expo-sqlite`'s default directory) |
| `storage.subDirectory` | `string` | Subdirectory relative to `directory` for organizing databases |
| `sync` | `SyncOptions` | Sync backend configuration (see [Syncing](/building-with-livestore/syncing)) |
| `clientId` | `string` | Custom client identifier (defaults to device ID) |
| `sessionId` | `string` | Session identifier (defaults to `'static'`) |
| `resetPersistence` | `boolean` | Clear local databases on startup (development only) |

## Adding a Sync Backend

Connect to a sync backend for multi-device synchronization:


## `reference/platform-adapters/expo-adapter/sync-backend.ts`

```ts filename="reference/platform-adapters/expo-adapter/sync-backend.ts"
/** biome-ignore-all lint/correctness/noUnusedVariables: docs snippet keeps inline adapter */
// ---cut---
import { makePersistedAdapter } from '@livestore/adapter-expo'
import { makeWsSync } from '@livestore/sync-cf/client'

const adapter = makePersistedAdapter({
  sync: { backend: makeWsSync({ url: 'wss://your-sync-backend.com' }) },
})
```

See the [Syncing documentation](/building-with-livestore/syncing) for available sync providers and configuration options.

## Platform Notes

### Android

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:

```bash
npx expo install expo-build-properties
```

Then configure `app.json`:

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

See [Expo build properties documentation](https://docs.expo.dev/versions/latest/sdk/build-properties/#pluginconfigtypeandroid) for more details.

### iOS

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

## Devtools

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](/building-with-livestore/devtools) for full documentation.

## Storage & Persistence

### Database Location

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

**Expo Go:**
```bash
open $(find $(xcrun simctl get_app_container booted host.exp.Exponent data) -path "*/Documents/ExponentExperienceData/*livestore*" -print -quit)/SQLite
```

**Development builds:**
```bash
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`).

### Resetting Local Persistence

During development, you can clear local databases on startup:


## `reference/platform-adapters/expo-adapter/reset-persistence.ts`

```ts filename="reference/platform-adapters/expo-adapter/reset-persistence.ts"
import { makePersistedAdapter } from '@livestore/adapter-expo'

const resetPersistence = process.env.EXPO_PUBLIC_LIVESTORE_RESET === 'true'

const _adapter = makePersistedAdapter({
  storage: { subDirectory: 'dev' },
  resetPersistence,
})
```

:::caution
This deletes all local LiveStore data for the configured store. It only clears on-device data and does not affect any connected sync backend. Ensure this flag is disabled in production builds.
:::

## Architecture

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](/platform-adapters/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)   │
            └───────────────┘
```

## Future Improvements

We're exploring moving database operations to a background thread to further improve UI responsiveness during intensive writes. Follow [livestore/livestore](https://github.com/livestorejs/livestore) for updates.

## See Also

- [Expo Getting Started Guide](/getting-started/expo) — Complete setup tutorial
- [Expo Adapter Examples](/examples/expo-adapter) — Example applications
- [React Integration](/framework-integrations/react-integration) — Provider and hooks documentation
- [Syncing](/building-with-livestore/syncing) — Multi-device synchronization
- [Devtools](/building-with-livestore/devtools) — Debugging and inspection tools