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
valuescount 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 towardvalues, 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 aRETURNINGclause — userun()instead, or addRETURNING.
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.