Usage
Day-to-day CRUD with the plugin: executing DDL, running parameterized writes, querying rows, and binding values — including BLOBs.
DDL and unparameterized bulk SQL: execute()
Use execute() for CREATE TABLE, other DDL, or bulk DML that has no bound parameters.
statements must be a non-empty array, and each array element must be exactly one SQL
statement — packing "INSERT ...; INSERT ..." into one string fails on every platform.
await CapacitorSqlite.execute({
database: 'myapp',
statements: [
'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)',
'CREATE INDEX idx_users_name ON users (name)',
],
});
Statements run in a single transaction by default. Pass transaction: false to keep prior
successful statements committed if a later one fails, or when calling execute() from inside an
already-active beginTransaction() (nested transactions return TRANSACTION_FAILED).
Parameterized writes: run()
run() executes one parameterized statement and reports changes and lastInsertId.
const result = await CapacitorSqlite.run({
database: 'myapp',
statement: 'INSERT INTO users (name) VALUES (?)',
values: ['Alice'],
});
if (!result.success) throw new Error(result.error.message);
console.log(result.data.lastInsertId);
lastInsertId is 0 for UPDATE/DELETE, for statements that insert no row, and for any
INSERT/REPLACE containing an ON CONFLICT clause — SQLite does not update its rowid counter
when the DO UPDATE arm of an upsert runs. It is also conservatively 0 whenever SQLite's
connection-level rowid counter is otherwise unchanged (WITHOUT ROWID tables, replacing the same
explicit rowid, or reusing a deleted rowid). When the exact affected row's id matters, use
query() with RETURNING instead:
const upserted = await CapacitorSqlite.query<{ id: number }>({
database: 'myapp',
statement: `INSERT INTO users (id, name) VALUES (?, ?)
ON CONFLICT(id) DO UPDATE SET name = excluded.name
RETURNING id`,
values: [1, 'Alice'],
});
Reading rows: query()
query() accepts only result-producing SQL: SELECT, PRAGMA, EXPLAIN, and
INSERT/UPDATE/DELETE/REPLACE ... RETURNING. Plain write DML without RETURNING returns
INVALID_PARAMS — use run() for that.
const queried = await CapacitorSqlite.query<{ id: number; name: string }>({
database: 'myapp',
statement: 'SELECT * FROM users WHERE id = ?',
values: [1],
});
if (!queried.success) throw new Error(queried.error.message);
console.log(queried.data.rows);
Column names become object keys. INTEGER results outside Number.MAX_SAFE_INTEGER come back as
strings, not imprecise numbers.
Android caveat: query() runs through SQLiteDatabase.rawQuery(sql, String[]), which only
accepts string bind arguments. The plugin scans the SQL first and inlines numeric, boolean, and
BLOB ? values as SQL literals to preserve their type. The scanner skips ? inside string
literals, quoted identifiers, and comments.
Placeholders
Only anonymous ? placeholders with a positional values array are supported — the value count
must exactly match the placeholder count. ? inside strings, quoted identifiers, and SQL
comments is not counted.
await CapacitorSqlite.query({
database: 'myapp',
statement: 'SELECT * FROM users WHERE id = ? AND active = ?',
values: [123, true],
});
Numbered (?1) and named (:name, @name, $name) placeholders are valid SQLite syntax but
are rejected by this plugin's cross-platform contract — the wrapper exposes positional arrays
only, and a statement using them fails validation before it reaches the native layer.
Value types and BLOBs
| JS type | SQLite affinity |
|---|---|
string |
TEXT |
number |
INTEGER / REAL |
boolean |
INTEGER (0 / 1) |
null |
NULL |
Uint8Array |
BLOB |
Prefer Uint8Array for BLOB parameters. A plain number[] (each item 0-255) is also accepted
on iOS, Android, and Electron, but the Web implementation accepts Uint8Array only.
Because the Capacitor bridge does not transport typed arrays natively, the JS wrapper sends
Android/iOS BLOB inputs through a private tagged base64 envelope, and native code decodes
directly to ByteArray/Data. Ordinary strings — including strings that happen to look like the
envelope marker — remain TEXT. Electron and Web keep Uint8Array through structured clone and
never touch that envelope. Keep individual bridge payloads reasonably bounded; for multi-megabyte
media, store files outside SQLite and persist a reference instead.
Next steps
- Migrations for schema versioning.
- Transactions for multi-statement atomicity.
- Bulk writes when inserting many rows.