Expoexpo-sqliteDatabaseOfflineReact Native

Local Database in Expo with expo-sqlite (Offline Data Guide)

Add a real on-device SQL database to your Expo app with expo-sqlite — open a database, create tables, run queries with parameters, use transactions, and build an offline data layer. SDK 54+ examples.

DeveloperMill2 min read

When your app needs to work offline with structured data — a todo list, a cached feed, saved records you filter and sort — key/value storage isn't enough. expo-sqlite ships a full SQLite database on the device with a clean async API. It's the most-searched Expo module for offline data, and this guide covers everything from opening a database to transactions.

Install

npx expo install expo-sqlite

Open a database and create a table

import * as SQLite from "expo-sqlite"

const db = await SQLite.openDatabaseAsync("app.db")

await db.execAsync(`
  CREATE TABLE IF NOT EXISTS todos (
    id INTEGER PRIMARY KEY NOT NULL,
    text TEXT NOT NULL,
    done INTEGER DEFAULT 0
  );
`)

openDatabaseAsync creates the file if it doesn't exist and reuses it on the next launch.

Insert with parameters (safe from injection)

Use ? placeholders and pass values as arguments — never concatenate strings:

await db.runAsync("INSERT INTO todos (text) VALUES (?)", "Ship the app")

// runAsync returns metadata about the write:
const result = await db.runAsync("INSERT INTO todos (text) VALUES (?)", "Write tests")
console.log(result.lastInsertRowId, result.changes)

Query rows

Pick the helper that matches what you need back:

// All rows
const todos = await db.getAllAsync<{ id: number; text: string; done: number }>(
  "SELECT * FROM todos ORDER BY id DESC"
)

// A single row
const first = await db.getFirstAsync("SELECT * FROM todos WHERE id = ?", 1)

// Stream row-by-row for large results
for await (const row of db.getEachAsync("SELECT * FROM todos")) {
  console.log(row)
}

Update and delete

await db.runAsync("UPDATE todos SET done = 1 WHERE id = ?", 1)
await db.runAsync("DELETE FROM todos WHERE id = ?", 1)

Use transactions for batched writes

Wrap multiple writes so they all succeed or all roll back — faster and safer:

await db.withTransactionAsync(async () => {
  await db.runAsync("INSERT INTO todos (text) VALUES (?)", "Task A")
  await db.runAsync("INSERT INTO todos (text) VALUES (?)", "Task B")
  await db.runAsync("INSERT INTO todos (text) VALUES (?)", "Task C")
})

Provide the database with a React context

For app-wide access, wrap your tree in SQLiteProvider and read it with useSQLiteContext():

import { SQLiteProvider, useSQLiteContext } from "expo-sqlite"

// <SQLiteProvider databaseName="app.db">{children}</SQLiteProvider>

function TodoList() {
  const db = useSQLiteContext()
  // db.getAllAsync(...) etc.
}

Gotchas

  • Run your CREATE TABLE migrations once at startup (they're idempotent with IF NOT EXISTS).
  • Prefer runAsync/getAllAsync over the old callback-based transaction API from older SDKs.
  • SQLite is for structured data — for a couple of settings use AsyncStorage, and for secrets use expo-secure-store.

Next steps

Combine SQLite with expo-file-system to store file blobs alongside their metadata, and see the complete lineup in the top 10 Expo SDK modules.

Frequently asked questions

How do I create a local database in Expo?

Install expo-sqlite and call `await SQLite.openDatabaseAsync('app.db')`. Then run `db.execAsync(...)` with a CREATE TABLE statement. The database file is stored on the device and persists across app launches.

How do I prevent SQL injection in expo-sqlite?

Never string-concatenate values into SQL. Use parameter placeholders (`?`) and pass values as arguments: `db.runAsync('INSERT INTO todos (text) VALUES (?)', text)`. expo-sqlite escapes them safely.

When should I use expo-sqlite instead of AsyncStorage?

Use expo-sqlite when you have structured, relational, or queryable data — lists you filter, sort, or join. Use AsyncStorage or SecureStore for a handful of simple key/value settings. SQLite scales to thousands of rows with fast queries.

Build it faster with a template

Production-ready Expo React Native templates with the patterns from this post already wired up.

Browse templates