Docs

Concepts

Read this before writing application code — it explains the shapes and rules that every guide and API page assumes.

Every call resolves, never rejects

Every plugin method returns a Promise that resolves to a SqliteResult<T> — a discriminated union of SqliteSuccess<T> ({ success: true, data: T }) and SqliteFailure ({ success: false, error: SqliteError }). SQL failures, validation errors, and platform errors all surface through success: false, not through a rejected Promise.

const result = await CapacitorSqlite.run({ database: 'myapp', statement: 'INSERT ...' });
if (!result.success) {
  console.error(result.error.code, result.error.message);
  return;
}
console.log(result.data.changes);

This matters most inside try/catch blocks around transactions: a failed run() or commitTransaction() does not throw, so try/catch alone does not trigger a rollback. See Transactions.

SqliteError.details always includes nativeCode, nativeMessage, and source; treat any other keys as platform-specific debugging hints, not part of the stable contract.

Databases are opened by name, not by path

open({ database: 'myapp' }) takes a database name (no extension), not a filesystem path. The plugin resolves an actual file location per platform and per directory option — see Platform notes for the resulting paths. Passing a raw absolute or relative path is not supported.

On iOS and Electron, the open-database registry matches names case-insensitively, so 'myapp' and 'MyApp' resolve to the same open handle and cannot both be open with different settings at once.

Reopening an already-open database

Calling open() again for a database that is already open reuses the existing connection when readonly and directory match the original call. If migrations are supplied on the second call, any still-pending versions are applied before it resolves; already-applied versions are skipped. Reopening with a different readonly value or directory returns DB_ALREADY_OPEN. Reopening with migrations while a manual transaction is active on that connection returns MIGRATION_FAILED, so migration SQL never mixes into an in-flight application transaction.

Migrations run against PRAGMA user_version

open() reads the database's PRAGMA user_version, then runs every supplied migration whose version exceeds that stored value, ascending. Each migration commits in its own transaction. Details, ordering guarantees, and the non-atomicity of a multi-version list are covered in Migrations.

Transactions are connection-scoped

beginTransaction() / commitTransaction() / rollbackTransaction() operate on one database's connection. A second beginTransaction() while one is already active — or calling execute() / runBatch() with their default transaction: true while a manual transaction is active — returns TRANSACTION_FAILED. close() automatically rolls back any open transaction. See Transactions.

In-memory databases

':memory:' as the database name opens an ephemeral database — useful for tests or scratch space. The directory option is ignored for it. On iOS, Web, and Electron, close() destroys the database outright, so a later open({ database: ':memory:' }) always starts empty. On Android, SQLiteDatabase's connection pool has been observed to keep a :memory: database's contents alive across a close/reopen cycle (tracked as mdb-02 in the shared test suite); run explicit cleanup SQL (DROP TABLE, etc.) if your code depends on a guaranteed reset there.

Calls against one database are serialized

On Android and iOS, every plugin call — across all open databases — is funneled through a single native SQLite thread/serial queue per platform (one executor thread on Android, one serial work queue in front of each database's connection queue on iOS), so manual transactions stay affinity-correct on the same native thread. Web and Electron each use one underlying SQLite worker per plugin instance, with a separate logical FIFO per database.

The practical effect: always await transaction boundaries before issuing dependent work, and a long operation on one database can delay calls to a different database that share the same native thread/worker. Apps that keep only one database open are unaffected by the multi-database case. See Platform notes for the full caveat list.

Value types

JS type SQLite affinity
string TEXT
number INTEGER / REAL
boolean INTEGER (0 / 1)
null NULL
Uint8Array BLOB

number values must be finite, and integer values must stay within Number.MAX_SAFE_INTEGER. An INTEGER result outside that safe range comes back as a string instead of an imprecise number. See Usage for BLOB transport details.

Next steps

  • Usage — CRUD basics, placeholders, and value binding.
  • Migrations — versioning rules and failure semantics.
  • Transactions — manual transaction lifecycle.
  • Bulk writesrunBatch() and runMany().

Last updated on July 17, 2026