Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/iterate/sqlfu/llms.txt

Use this file to discover all available pages before exploring further.

expo-sqlite gives React Native apps on-device SQLite storage. The createExpoSqliteClient adapter wraps an expo-sqlite database handle as an AsyncClient, so generated query wrappers return promises and work naturally with async/await in your components and hooks.
1

Install dependencies

npm install sqlfu expo-sqlite
expo-sqlite requires Expo SDK 50 or later for the async API (openDatabaseAsync). Older versions used a synchronous API. This guide covers the async API.
2

Configure sqlfu

The mobile runtime cannot read migration files from disk at runtime — they must be bundled. Leave db out of the config; sqlfu uses definitions.sql for type generation and generates a migration bundle that the app imports directly:
sqlfu.config.ts
import {defineConfig} from 'sqlfu';

export default defineConfig({
  definitions: './definitions.sql',
  migrations: './migrations',
  queries: './sql',
});
Generated wrappers are async by default, matching expo-sqlite.
3

Write schema and queries

Add your schema to definitions.sql:
definitions.sql
create table todos (
  id integer primary key,
  title text not null,
  completed integer not null default 0
);
Add a query to sql/queries.sql:
sql/queries.sql
/** @name listTodos */
select id, title, completed
from todos
order by id;
4

Draft and generate

Run the authoring commands on your development machine:
npx sqlfu draft
npx sqlfu generate
sqlfu draft writes the next migration file to migrations/. sqlfu generate creates TypeScript wrappers in sql/.generated/ and the migration bundle in migrations/.generated/migrations.ts. Both are committed to your repo and bundled into the app.
Do not run sqlfu migrate as your app’s migration step. Instead, import the generated migrate function and call it at database open time — see the next step.
5

Open the database and call your queries

Open the database with SQLite.openDatabaseAsync, wrap it with createExpoSqliteClient, and apply migrations before returning the client:
src/db.ts
import * as SQLite from 'expo-sqlite';
import {createExpoSqliteClient} from 'sqlfu';

import {migrate} from './migrations/.generated/migrations.ts';
import {listTodos} from './sql/.generated/queries.sql.ts';

export async function openAppDatabase() {
  const db = await SQLite.openDatabaseAsync('app.db');
  const client = createExpoSqliteClient(db);

  await migrate(client);

  return client;
}

export async function loadTodos() {
  const client = await openAppDatabase();
  return listTodos(client);
}
The migration bundle is idempotent. It applies any missing migrations and skips ones already recorded in the on-device sqlfu_migrations table. Calling it on every app open is safe.

Async behavior

expo-sqlite is asynchronous. All generated wrappers and client.all(...) calls return promises:
// await required
const todos = await listTodos(client);
const rows = await client.all({sql: 'select * from todos', params: []});
Do not pass generate.sync: true in your sqlfu.config.ts for an Expo project. The expo-sqlite driver is async, and setting sync: true would produce wrappers that do not match the adapter’s actual behavior.

Adapters reference

The Expo SQLite adapter snippet and the full compatibility matrix.

SQL migrations

Migration history, the generated bundle, and drift detection.

Build docs developers (and LLMs) love