Docs

Troubleshooting

Symptoms, likely causes, and fixes for the most common failures. For the full error code reference, see API: error codes.

open() returns DB_ALREADY_OPEN

Cause: a database with that name is already open with a different readonly value or directory than the current call.

Fix: reuse the exact readonly/directory used by the original open(), or close() the database first if you genuinely need to reopen it with different settings. Remember that iOS and Electron match database names case-insensitively, so 'MyApp' and 'myapp' collide.

A migration fails partway through a list

Cause: each migration commits in its own transaction — a later version's failure does not roll back earlier versions that already committed.

Fix: see Migrations. Retry open() with the same migrations array; it resumes from the first still-pending version.

A failed run()/commitTransaction() doesn't trigger my catch block

Cause: every method resolves to SqliteResult and never rejects. try/catch alone does not see a SQL failure.

Fix: check result.success after every call inside the transaction, and throw explicitly when it's false — see Transactions.

TRANSACTION_FAILED from execute()/runBatch()/runMany()

Cause: one of these was called with its default transaction: true while a manual transaction (beginTransaction()) is already active on that connection — nested transactions are rejected.

Fix: pass transaction: false so the call participates in the existing transaction instead of nesting a new one. See Transactions → nesting rules.

INVALID_PARAMS on a statement that "looks right"

Common causes:

  • Bind values count doesn't exactly match the number of anonymous ? placeholders (placeholders inside string literals, quoted identifiers, and comments don't count — but they also aren't counted toward values, so double-check the SQL text).
  • A numbered (?1) or named (:name, @name, $name) placeholder was used — only anonymous ? is supported.
  • Two SQL statements were packed into one string ("INSERT ...; INSERT ...") — each array element must be exactly one statement.
  • query() was called on write DML without a RETURNING clause — use run() instead, or add RETURNING.

See Usage for the placeholder rules in full.

lastInsertId is 0 after an insert that should have worked

Cause: this is expected, not a bug, in several cases: the statement was UPDATE/DELETE; the INSERT/REPLACE had an ON CONFLICT clause and resolved via DO UPDATE; the table is WITHOUT ROWID; or the row's explicit rowid replaced an identical existing rowid.

Fix: use query() with a RETURNING clause to get the exact affected row's id — see Usage → parameterized writes.

Web: writes failing with SQLITE_IOERR

Cause: OPFS sync access handles are exclusive per file. The same persistent database was opened from two tabs, windows, workers, or a hot-reload survivor at the same time.

Fix: close the database in the stale context, or reload it, before continuing in the new one. ':memory:' databases are unaffected. See Installation → Web.

Web: isAvailable() returns false, or OPFS never initializes

Cause: the page isn't served with Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp, so SharedArrayBuffer (required by the OPFS VFS) is unavailable — or the browser is Safari below version 17.

Fix: set both headers on your dev/prod server — see Installation → Web for the exact Vite config used by this plugin's own playground. ':memory:' databases still work without OPFS.

Electron: NOT_AVAILABLE

Cause: either node:sqlite isn't present (Electron/Node version below 40/24), or the main process never registered the plugin on CapacitorCustomPlatform (missing import '@devioarts/capacitor-sqlite/electron/settings', or new CapacitorSqlite() never ran).

Fix: confirm your Electron/Node version, and confirm registration as shown in Installation → Electron.

Electron: worker seems stuck after a crash or long-running call

Fix: call capacitorSqlite.dispose() — it terminates the current worker; the next plugin call automatically spawns a fresh one. See Installation → Electron.

A bulk insert of thousands of rows is slow

Cause: looping individually-awaited run() calls (or Promise.all() over them) creates one bridge round trip per row — it does not collapse into one SQLite operation, and on Web it pays an OPFS durability barrier per row.

Fix: use runBatch() or runMany() — see Bulk writes.

Android: a :memory: database still has data after close() + reopen

Cause: SQLiteDatabase's connection pool has been observed to keep :memory: contents alive across a close/reopen cycle on Android specifically (tracked as mdb-02 in the shared test suite) — the other three platforms destroy :memory: data on close().

Fix: run explicit cleanup SQL (DROP TABLE, etc.) if your code depends on a guaranteed reset. See Concepts → in-memory databases.

Next

If a failure doesn't match anything above, inspect error.details.nativeMessage on the returned SqliteFailure — see API: error codes.

Last updated on July 17, 2026