This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
Expo SQLite
A library that provides access to a database that can be queried through a SQLite API.
expo-sqlite gives your app access to a database that can be queried through a SQLite API. The database is persisted across restarts of your app.
On Apple TV, the underlying database file is in the caches directory and not the application documents directory, per Apple platform guidelines.
Installation
If you are installing this in an existing React Native app, make sure to install expo in your project.
Configuration in app config
You can configure expo-sqlite for advanced configurations using its built-in config plugin if you use config plugins in your project (Continuous Native Generation (CNG)). The plugin allows you to configure various properties that cannot be set at runtime and require building a new app binary to take effect. If your app does not use CNG, then you'll need to manually configure the library.
Example app.json with config plugin
Configurable properties
Web setup
Web support is in alpha and may be unstable. Create an issue on GitHub if you encounter any issues.
To use expo-sqlite on web, you need to configure Metro bundler to support wasm files and add HTTP headers to allow SharedArrayBuffer usage.
Add the following configuration to your metro.config.js. If you don't have the metro.config.js yet, you can run npx expo customize metro.config.js. Learn more.
If you deploy your app to web hosting services, you will also need to add the Cross-Origin-Embedder-Policy and Cross-Origin-Opener-Policy headers to your web server. Learn more about the COEP, COOP headers, and SharedArrayBuffer.
If you deploy your app on EAS Hosting, you can configure the headers in your app config:
Usage
Import the module from expo-sqlite.
Basic CRUD operations
Prepared statements
Prepared statements allow you to compile your SQL query once and execute it multiple times with different parameters. They automatically escape input parameters to defend against SQL injection attacks, and are recommended for queries that include user input. You can get a prepared statement by calling prepareAsync() or prepareSync() method on a database instance. The prepared statement can fulfill CRUD operations by calling executeAsync() or executeSync() method.
Note: Remember to call
finalizeAsync()orfinalizeSync()method to release the prepared statement after you finish using the statement.try-finallyblock is recommended to ensure the prepared statement is finalized.
Tagged template literals API
For convenience and improved developer experience, expo-sqlite provides Bun-inspired tagged template literals API through the db.sql property. This API automatically escapes parameters to prevent SQL injection attacks and provides automatic type inference based on the query type.
useSQLiteContext() hook
useSQLiteContext() hook with React.Suspense
As with the useSQLiteContext() hook, you can also integrate the SQLiteProvider with React.Suspense to show a fallback component until the database is ready. To enable the integration, pass the useSuspense prop to the SQLiteProvider component.
Executing queries within an async transaction
Due to the nature of async/await, any query that runs while the transaction is active will be included in the transaction. This includes query statements that are outside of the scope function passed to withTransactionAsync() and may be surprising behavior. For example, the following test case runs queries inside and outside of a scope function passed to withTransactionAsync(). However, all of the queries will run within the actual SQL transaction because the second UPDATE query runs before the transaction finishes.
Promise.all([ // 1. A new transaction begins db.withTransactionAsync(async () => { // 2. The value "first" is inserted into the test table and we wait 2 // seconds await db.execAsync('INSERT INTO test (data) VALUES ("first")'); await sleep(2000); // 4. Two seconds in, we read the latest data from the table const row = await db.getFirstAsync<{ data: string }>('SELECT data FROM test'); // ❌ The data in the table will be "second" and this expectation will fail. // Additionally, this expectation will throw an error and roll back the // transaction, including the `UPDATE` query below since it ran within // the transaction. expect(row.data).toBe('first'); }), // 3. One second in, the data in the test table is updated to be "second". // This `UPDATE` query runs in the transaction even though its code is // outside of it because the transaction happens to be active at the time // this query runs. sleep(1000).then(async () => db.execAsync('UPDATE test SET data = "second"')), ]);
The withExclusiveTransactionAsync() function addresses this. Only queries that run within the scope function passed to withExclusiveTransactionAsync() will run within the actual SQL transaction.
Executing PRAGMA queries
Tip: Enable WAL journal mode when you create a new database to improve performance in general.
Import an existing database
To open a new SQLite database using an existing .db file you already have, you can use the SQLiteProvider with assetSource.
Sharing a database between apps/extensions (iOS)
To share a database with other apps/extensions in the same App Group, you can use shared containers by following the steps below:
1
Configure the App Group in app config:
2
Use Paths.appleSharedContainers from the expo-file-system library to retrieve the path to the shared container:
Passing binary data
Use Uint8Array to pass binary data to the database:
Browse an on-device database
The expo-sqlite library includes a built-in DevTools inspector plugin that is automatically enabled in development and requires no extra setup. It lets you browse tables, view and edit rows, run SQL queries, and export databases directly from your browser. To open it, press Shift + M in the Expo CLI terminal to open the dev tools menu, and then select Open expo-sqlite to launch the inspector.
Alternatively, you can also use the drizzle-studio-expo dev tools plugin to launch Drizzle Studio, connected to a database in your app, directly from Expo CLI. This plugin can be used with any expo-sqlite configuration and does not require Drizzle ORM. Learn how to install and use the plugin.
Key-value storage
The expo-sqlite library provides Storage as a drop-in replacement for the @react-native-async-storage/async-storage library. This key-value store is backed by SQLite. If your project already uses expo-sqlite, you can leverage expo-sqlite/kv-store without needing to add another dependency.
Storage provides the same API as @react-native-async-storage/async-storage:
A key benefit of using expo-sqlite/kv-store is the addition of synchronous APIs for added convenience:
If you're currently using @react-native-async-storage/async-storage in your project, switching to expo-sqlite/kv-store is as simple as changing the import statement:
- import AsyncStorage from '@react-native-async-storage/async-storage'; + import AsyncStorage from 'expo-sqlite/kv-store';
The localStorage API
The expo-sqlite/localStorage/install module provides a drop-in implementation for the localStorage API. If you're already familiar with this API from the web, or you would like to be able to share storage code between web and other platforms, this may be useful. To use it, you just need to import the expo-sqlite/localStorage/install module:
Note:
import 'expo-sqlite/localStorage/install';is a no-op on web and will be excluded from the production JS bundle.
Security
SQL injections are a class of vulnerabilities where attackers trick your app into executing user input as SQL code. You must escape all user input passed to SQLite to defend against SQL injections. Prepared statements are an effective defense against this problem. They explicitly separate a SQL query's logic from its input parameters, and SQLite automatically escapes inputs when executing prepared statements.
Third-party library integrations
The expo-sqlite library is designed to be a solid SQLite foundation. It enables broader integrations with third-party libraries for more advanced higher-level features. Here are some of the libraries that you can use with expo-sqlite.
Drizzle ORM
Drizzle is a "headless TypeScript ORM with a head". It runs on Node.js, Bun, Deno, and React Native. It also has a CLI companion called drizzle-kit for generating SQL migrations.
Check out the Drizzle ORM documentation and the expo-sqlite integration guide for more details.
Knex.js
Knex.js is a SQL query builder that is "flexible, portable, and fun to use!"
Check out the expo-sqlite integration guide for more details.
SQLCipher
Note: SQLCipher is not supported on Expo Go.
SQLCipher is a fork of SQLite that adds encryption and authentication to the database. The expo-sqlite library supports SQLCipher for Android, iOS, and macOS. To use SQLCipher, you need to add the useSQLCipher config to your app.json as shown in the Configuration in app config section and run npx expo prebuild.
Right after you open a database, you need to set a password for the database using the PRAGMA key = 'password' statement.
API
Cheatsheet for the common API
The following table summarizes the common API for SQLiteDatabase and SQLiteStatement classes:
No API data file found, sorry!