Skip to content
Mobile
Skill

/expo-sqlite

Persist and query local data with expo-sqlite in Expo or React Native apps. Use for SQLite setup, SQL queries, schema migrations, transactions, SQLiteProvider, expo-sqlite/kv-store, or the localStorage polyfill.

From plugin
expo
53k6 skills

Claude Code

$ npx -y skills add expo/expo --skill expo-sqlite --agent claude-code

SKILL.md

expo-sqlite.SKILL.md
name: expo-sqlite
description: Persist and query local data with expo-sqlite in Expo or React Native apps. Use for SQLite setup, SQL queries, schema migrations, transactions, SQLiteProvider, expo-sqlite/kv-store, or the localStorage polyfill.
license: MIT

expo-sqlite

Use SQLite for structured local data, offline queues, and persistent caches. For simple string key-value storage, use `expo-sqlite/kv-store`. Preserve an existing ORM or storage architecture unless the task calls for changing it.

Setup and API selection

Install with `npx expo install expo-sqlite`. Check the installed package version and its types before using newer APIs such as `db.sql`. Use the [SQLite documentation](https://docs.expo.dev/versions/latest/sdk/sqlite/) for the app's SDK version; use [unversioned docs](https://docs.expo.dev/versions/unversioned/sdk/sqlite/) only when working with unreleased code.

The package supports Android, iOS, macOS, tvOS, and web. Standard SQLite is included in Expo Go; custom native build options require rebuilding the app. Web requires additional setup in [references/web-and-config.md](references/web-and-config.md).

| Task | API | | ------------------------------------------------ | ------------------------------------- | | Static DDL or multiple SQL statements | `db.execAsync(source)` | | Write and receive `{ lastInsertRowId, changes }` | `db.runAsync(source, params)` | | Read one row, or `null` if absent | `db.getFirstAsync<T>(source, params)` | | Read a bounded result set | `db.getAllAsync<T>(source, params)` | | Iterate rows without collecting the full result | `db.getEachAsync<T>(source, params)` | | Concise parameterized queries | `` db.sql<T>`SELECT ...` `` | | Reuse a compiled statement | `db.prepareAsync(source)` |

Prefer asynchronous methods for application work. Synchronous database methods can block the JavaScript thread, especially for large queries or migrations.

Bind values, not SQL syntax

`execAsync()` does not bind or escape values. Use it for trusted, static SQL. For data, use variadic arguments, arrays, or named parameters:

import type { SQLiteDatabase } from 'expo-sqlite';

async function addTodo(db: SQLiteDatabase, title: string) {
  const result = await db.runAsync('INSERT INTO todos (title) VALUES (?)', title);
  return result.lastInsertRowId;
}

async function setCompleted(db: SQLiteDatabase, id: number, completed: boolean) {
  await db.runAsync('UPDATE todos SET completed = $completed WHERE id = $id', {
    $completed: completed ? 1 : 0,
    $id: id,
  });
}

Bindings represent values, not table names, column names, or SQL fragments. Use fixed SQL or an allowlist for dynamic identifiers. Bind `null` for SQL NULL; serialize objects explicitly. `Uint8Array` and `ArrayBuffer` can be bound to BLOB columns in the current package.

The `db.sql` tag replaces interpolations with bound parameters. Do not put quotes around `${value}` or interpolate an array expecting an expanded `IN` list:

import type { SQLiteDatabase } from 'expo-sqlite';

type Todo = { id: number; title: string; completed: number };

async function findTodo(db: SQLiteDatabase, id: number) {
  const sql = db.sql;
  return await sql<Todo>`SELECT * FROM todos WHERE id = ${id}`.first();
}

Awaiting a tagged `SELECT` returns rows; `.first()` returns a row or `null`, `.each()` iterates rows, and `.values()` returns arrays of column values. Writes without `RETURNING` normally return `SQLiteRunResult`; prefer `runAsync()` when you need unambiguous write metadata. A generic type describes the expected row shape; it does not validate stored data or transform SQLite integers into JavaScript booleans.

React integration and migrations

Mount one stable `SQLiteProvider` above the consumers of a database. Descendants call `useSQLiteContext()` to obtain it. Put initialization in `onInit`, which completes before the children render. Keep `onInit` and options stable so a render does not reopen the database.

This example migrates a new database through versions 1 and 2, and upgrades an existing version 1 database without deleting its rows:

import { SQLiteProvider, type SQLiteDatabase } from 'expo-sqlite';
import type { PropsWithChildren } from 'react';

export function DatabaseProvider({ children }: PropsWithChildren) {
  return (
    <SQLiteProvider databaseName="app.db" onInit={initializeDatabase}>
      {children}
    </SQLiteProvider>
  );
}

async function initializeDatabase(db: SQLiteDatabase) {
  // Connection settings must be applied outside the migration transaction.
  await db.execAsync('PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON;');

  const version = await db.getFirstAsync<{ user_version: number }>('PRAGMA user_version');
  if (version === null) {
    throw new Error('Could not read the database schema version');
  }
  if (version.user_version > 2) {
    throw new Error('Database schema is newer than this app supports');
  }
  if (version.user_version === 2) {
    return;
  }

  // onInit gates consumers; no other database work should run during initialization.
  await db.withTransactionAsync(async () => {
    if (version.user_version < 1) {
      await db.execAsync(`
        CREATE TABLE todos (id INTEGER PRIMARY KEY NOT NULL, title TEXT NOT NULL);
      `);
    }
    if (version.user_version < 2) {
      await db.execAsync('ALTER TABLE todos ADD COLUMN completed INTEGER NOT NULL DEFAULT 0');
    }
    await db.execAsync('PRAGMA user_version = 2');
  });
}

Keep schema changes and their `user_version` update in the same transaction so failure rolls both back. WAL generally improves database performance; enable foreign keys on each connection that relies on them. Do not delete an existing database to work around a migration e

Read more
@expo
Ships withexpo

An open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.

Stars on GitHub

Momentum

stars in total
53.5k
Star history of expo/expo on GitHub, a chart by star-history.com

Other skills on expo.

deep-code-review

deep-code-review

In-depth design-focused code review - understands codebase context before evaluating PR changes, posts structured feedback to…

@expo@expoInstall Skill
expo-api-docs

expo-api-docs

Write TSDoc comments for Expo SDK APIs following official conventions. MUST USE when introducing new user-facing TypeScript APIs…

@expo@expoInstall Skill
expo-review

expo-review

Run Expo's configured AI code reviewer on local changes or an expo/expo pull request, summarize findings and reviewer coverage,…

@expo@expoInstall Skill
updating-expo-config

updating-expo-config

Add a new property to the Expo config (app.json / app.config.js) schema. Covers the universe JSON schema, `@expo/config-types`,…

@expo@expoInstall Skill
expo-app-intents

expo-app-intents

Add or maintain Apple App Intents in existing Expo apps using expo-app-intents. Use for Siri and Shortcuts actions, Swift intent…

@expo@expoInstall Skill

More skills from other plugins.

modlens

modlens

Plug-in vision for text-only models. Hard rule: when a file path or URL with an image extension (.png, .jpg, .jpeg, .webp, .gif,…

modlens2w4.6k
@liustack@liustackInstall Skill
appllama-app-design-skill

appllama-app-design-sk…

Build native-feeling, benchmark-quality mobile app screens (Expo / React Native). Use when designing or implementing any mobile…

appllama2mo3.9k
@appllama@appllamaInstall Skill
add-component

add-component

Add a SwiftUI component from ShipSwift. Use when the user says "add component", "add a view", "add X view", "I need a chart",…

shipswift2w3.3k
@signerlabs@signerlabsInstall Skill

Questions about expo-sqlite.

Which coding agents can use expo-sqlite?
expo-sqlite installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.