Die wichtigste Alternative zu @capacitor-community/sqlite auf Capacitor 8 ist @capgo/capacitor-fast-sql: ein natives SQLite-Plugin für iOS, Android und Web, das SQLCipher optional hält und Abfragedaten über einen lokalen HTTP-Channel statt über die Capacitor-Bridge schickt. Dieser Guide vergleicht beide ehrlich, zeigt, wann du beim Community-Plugin bleiben solltest, und begleitet die Migration deiner code und der bestehenden Datenbanken deiner Nutzer.
Warum Teams nach Alternativen suchen
@capacitor-community/sqlite ist seit Jahren das Standard-SQLite-Plugin für Capacitor und wird weiterhin gepflegt (Version 8.1.1 erschien im August 2026). Die Gründe für einen Wechsel sind konkret:
- Bridge-Serialisierung. Jedes Ergebnis geht als JSON durch die Capacitor-Bridge. Das ist bei kleinen Abfragen okay, aber spürbar, wenn du tausende Zeilen für Sync oder Suche lädst.
- SQLCipher ist immer enthalten. Das Plugin hängt auf iOS und Android von SQLCipher ab, selbst wenn du nie verschlüsselst. Das vergrößert die native App und heißt, dass dein Binary Verschlüsselungs-code enthält, das du bei Export-Compliance-Antworten berücksichtigen musst.
- Web-Storage-Modell. Im Web nutzt es die
jeep-sqliteKomponente: sql.js im Speicher, persistiert nach IndexedDB beim Aufruf vonsaveToStore(). Du musst daran denken zu speichern, und die gesamte Datenbank liegt im Arbeitsspeicher. - Eine große API-Angriffsfläche. Connection Wrappers, Konsistenzprüfungen, Read-only- und Non-conformed-Verbindungen, Sync-Tabellen, JSON-Import und -Export. Mächtig, aber viel Lernaufwand für einfaches „SQL ausführen“.
- Ein Passphrase pro App. Die Verschlüsselung nutzt ein einzelnes Secret, das vom Plugin gespeichert wird (
setEncryptionSecret), das von jeder verschlüsselten Datenbank in der App geteilt wird.
Direkter Vergleich
@capacitor-community/sqlite 8.1 |
@capgo/capacitor-fast-sql 8.2 |
|
|---|---|---|
| Plattformen | iOS, Android, Web, Electron | iOS, Android, Web |
| Datenpfad | Capacitor Bridge | Lokaler HTTP-Server mit Token pro Verbindung (native) |
| Web-Engine | jeep-sqlite (sql.js + IndexedDB, manuell) saveToStore) |
Offiziell @sqlite.org/sqlite-wasm mit OPFS wird direkt in die Datei geschrieben |
| Verschlüsselung | SQLCipher immer mitgeliefert, eine vom Plugin verwaltete Passphrase | Optionales SQLCipher, Key wird pro Datenbank übergeben connect() |
| Swift Package Manager | Ja (Package.swift) |
Ja (Package.swift) |
| Schema-Versioning | upgradeStatements / addUpgradeStatement |
Meistens selbst implementiert PRAGMA user_version |
| JSON Import/Export, Tabellen-Sync | Ja | Nein |
| Vorbefüllte Datenbank aus Assets | copyFromAssets() |
Datei vorher selbst kopieren connect() |
| Key-Value-Helper | Nein | KeyValueStore |
| BLOBs | Ja | Ja, als Uint8Array |
| ORM-Unterstützung | TypeORM integriert capacitor Treiber |
Eigene Driver (Kysely funktioniert gut) |
| Zusätzliches Native Setup | Web: Kopieren sql-wasm.wasm |
iOS NSAllowsLocalNetworking, Android localhost cleartext Regel |
| Maintainer | Capacitor Community | Capgo |
Wann man beim Community-Plugin bleiben sollte
Sei praxisnah. Bleib dabei, wenn:
- du einen Electron build,
- nutzt du TypeORM (dessen capacitor-Driver auf dem Community-Plugin basiert, siehe TypeORM mit Capacitor),
- du auf ... setzt JSON-Import/Export oder Tabellen-Sync und willst diese nicht neu aufbauen,
- deine Queries sind klein und die App läuft aktuell gut.
Wann Fast SQL die bessere Wahl ist
Wechsle, wenn:
- du große Datensätze lädst oder schreibst (Sync-Engines, Offline-Kataloge, Suchindizes, CRDT-Logs),
- du möchtest nur bestimmte Datenbanken verschlüsseln, mit Keys aus dem Keychain oder Keystore,
- du willst kein SQLCipher in einer App, die es gar nicht benötigt,
- du möchtest eine echte SQLite-Datei im Web über OPFS anstatt einer In-Memory-Kopie nutzen,
- du suchst ein einfaches API: connect, query, run, transaction.
Für eine vollständige Anleitung zu Fast SQL selbst schau hier: So nutzt du SQLite in einer Capacitor App.
Schritt 1: Fast SQL neben dem alten Plugin installieren
Behalte das Community-Plugin für ein Release installiert, um bestehende Daten zu migrieren.
bun add @capgo/capacitor-fast-sql
bunx cap sync
Native Konfiguration für Fast SQL benötigt:
- iOS:
NSAppTransportSecurity>NSAllowsLocalNetworking=truein einemInfo.plist. - Android: ein
network_security_config.xmldas Cleartext erlaubt fürlocalhostund127.0.0.1, im Manifest referenziert. - Verschlüsselung:
net.zetetic:sqlcipher-androidim Android-App-Modul und demCapgoCapacitorFastSql/SQLCipherPod unter iOS. Details in SQLite-Verschlüsselung in Capacitor. - Web:
Cross-Origin-Opener-Policy: same-originundCross-Origin-Embedder-Policy: require-corpHeader für OPFS.
Schritt 2: API mappen
Datenbank öffnen
Vorher:
import { CapacitorSQLite, SQLiteConnection } from '@capacitor-community/sqlite';
const sqlite = new SQLiteConnection(CapacitorSQLite);
const db = await sqlite.createConnection('app', false, 'no-encryption', 1, false);
await db.open();
Nachher:
import { FastSQL } from '@capgo/capacitor-fast-sql';
const db = await FastSQL.connect({ database: 'app', walMode: true, performancePresets: true });
FastSQL.connect() gibt die bestehende Verbindung zurück, falls die Datenbank bereits offen ist, sodass kein isConnection / retrieveConnection dance.
Methoden-Mapping
| Community-Plugin | Schnelles SQL |
|---|---|
createConnection() + open() |
FastSQL.connect({ database }) |
closeConnection(name) |
FastSQL.disconnect(name) |
db.query(sql, values) gibt zurück { values } |
db.query(sql, params) gibt Zeilen zurück |
db.run(sql, values) gibt zurück { changes: { changes, lastId } } |
db.run(sql, params) gibt zurück { rowsAffected, insertId } |
db.execute(multiStatementString) |
Eine db.execute(sql) pro Statement oder db.executeBatch([...]) |
db.executeSet(set) |
db.executeBatch(ops) mit { statement, params } |
beginTransaction / commitTransaction / rollbackTransaction |
db.transaction(async (tx) => ...) oder beginTransaction() / commit() / rollback() |
addUpgradeStatement / upgradeStatements |
Ein PRAGMA user_version Migration Runner |
copyFromAssets() |
Kopiere ein Bundle .db in Directory.Data vor dem ersten connect() |
setEncryptionSecret() + mode: 'secret' |
encrypted: true, encryptionKey auf connect() |
initWebStore() / saveToStore() |
Nicht nötig, OPFS speichert Schreibvorgänge |
Queries und Writes
Vorher:
const res = await db.query('SELECT * FROM notes WHERE pinned = ?', [1]);
const notes = res.values ?? [];
const { changes } = await db.run('INSERT INTO notes (title) VALUES (?)', ['Hello']);
const id = changes?.lastId;
Nachher:
const notes = await db.query('SELECT * FROM notes WHERE pinned = ?', [1]);
const { insertId } = await db.run('INSERT INTO notes (title) VALUES (?)', ['Hello']);
Beide Plugins geben Zeilen als Objekte zurück, deren Keys die Spaltennamen sind.
Mehrere Statements ausführen
Die Community execute() akzeptiert einen String mit mehreren Statements, getrennt durch ;. Fast SQL bereitet pro Aufruf ein Statement vor. Teile dein SQL oder behalte Statements am besten als Array bei:
const schema = [
'CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL)',
'CREATE INDEX IF NOT EXISTS idx_notes_title ON notes (title)',
];
await db.executeBatch(schema.map((statement) => ({ statement })));
Nicht splitten bei ; mit einem naiven split(';') falls ein Statement einen Trigger-Body oder ein String-Literal mit einem Semikolon enthält.
Statements auf user_version upgraden
Das Community-Plugin speichert die Schema-Version in PRAGMA user_version ebenfalls. Wenn du dieselben Versionsnummern beibehältst, sind die Datenbanken deiner bestehenden Nutzer nach der Dateimigration in Schritt 3 bereits auf der richtigen Version.
import type { SQLConnection } from '@capgo/capacitor-fast-sql';
// Same content as your old upgrade statements, index 0 = version 1
const versions: string[][] = [
['CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL)'],
['ALTER TABLE notes ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0'],
];
export async function migrate(db: SQLConnection) {
const [{ user_version }] = await db.query('PRAGMA user_version');
for (let v = Number(user_version); v < versions.length; v++) {
await db.transaction(async (tx) => {
for (const statement of versions[v]) await tx.execute(statement);
await tx.execute(`PRAGMA user_version = ${v + 1}`);
});
}
}
Prüfe die tatsächliche user_version bei einer bestehenden Installation, bevor du dich darauf verlässt. Logge sie im aktuellen Release aus dem alten Plugin, falls du unsicher bist.
Vorbefüllte Datenbanken
Des Community-Plugins copyFromAssets() kopiert gebündelte Datenbanken beim ersten Start. Mit Fast SQL legst du die Datei dort ab, wo das Plugin vor der Verbindung sucht. Fast SQL speichert <name>.db in einem Documents auf iOS und in der App files Verzeichnis auf Android, die beide Directory.Data in einem @capacitor/filesystem:
import { Directory, Filesystem } from '@capacitor/filesystem';
import { FastSQL } from '@capgo/capacitor-fast-sql';
async function openCatalog() {
const exists = await Filesystem.stat({ path: 'catalog.db', directory: Directory.Data })
.then(() => true)
.catch(() => false);
if (!exists) {
const blob = await (await fetch('/assets/catalog.db')).blob();
const base64 = await new Promise<string>((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve((reader.result as string).split(',')[1]);
reader.readAsDataURL(blob);
});
await Filesystem.writeFile({ path: 'catalog.db', data: base64, directory: Directory.Data });
}
return FastSQL.connect({ database: 'catalog', readOnly: true });
}
Für große Dateien: Lade sie in Chunks herunter oder liefere eine komprimierte Datei aus und nutze die native Kopierfunktion des File-Plugins statt Base64 in JavaScript.
Schritt 3: Bestehende Nutzerdaten migrieren
Nutzer, die deine App aktualisieren, haben eine Datenbank, die vom Community-Plugin erstellt wurde. Sie liegt unter einem anderen Pfad und hat einen anderen Namen:
| Community-Plugin | Schnelles SQL | |
|---|---|---|
| iOS | Documents/<name>SQLite.db (Standard iosDatabaseLocation) |
Documents/<name>.db |
| Android | databases/<name>SQLite.db |
files/<name>.db |
Option A: Datei kopieren (unverschlüsselte Datenbanken)
Am schnellsten und behält alles: Indizes, Trigger, user_version.
import { CapacitorSQLite, SQLiteConnection } from '@capacitor-community/sqlite';
import { Directory, Filesystem } from '@capacitor/filesystem';
import { Preferences } from '@capacitor/preferences';
export async function moveCommunityDatabase(name: string): Promise<void> {
const done = await Preferences.get({ key: `sqlite-moved:${name}` });
if (done.value === '1') return;
const sqlite = new SQLiteConnection(CapacitorSQLite);
const exists = (await sqlite.isDatabase(name)).result;
if (exists) {
const conn = await sqlite.createConnection(name, false, 'no-encryption', 1, false);
await conn.open();
await conn.execute('PRAGMA wal_checkpoint(TRUNCATE)', false).catch(() => undefined);
const { url } = await conn.getUrl();
await sqlite.closeConnection(name, false);
if (url) {
await Filesystem.copy({ from: url, to: `${name}.db`, toDirectory: Directory.Data });
}
}
await Preferences.set({ key: `sqlite-moved:${name}`, value: '1' });
}
Rufe es vor dem ersten Mal auf FastSQL.connect({ database: name }). Hinweise:
getUrl()(iOS und Android) gibt die URL der Datenbankdatei zurück. Bei Übergabe eines vollständigenfile://URL ohnedirectoryermöglicht es dem Filesystem-Plugin, von jedem Pfad innerhalb deines Sandboxes zu lesen.- Zuerst die Verbindung zu schließen und ein Checkpoint zu setzen, stellt sicher, dass keine aktuellen Schreibvorgänge in einer
-walDatei. - Behalte die alte Datei für ein Release und lösche sie später mit der Methode der
delete()Community-Verbindung, sobald du dir sicher bist. Entferne dann das Community-Plugin.
Option B: Zeilen in JavaScript kopieren (verschlüsselte Datenbanken oder Schema-Änderungen)
Wenn die Quelle mit dem Secret des Community-Plugins verschlüsselt ist, ist der sicherste Weg, das Plugin die Zeilen entschlüsseln und in eine neue Fast SQL-Datenbank kopieren zu lassen. So kannst du auch mit einem neuen, pro Datenbank generierten Key aus dem Secure Storage verschlüsseln.
import { CapacitorSQLite, SQLiteConnection } from '@capacitor-community/sqlite';
import { FastSQL } from '@capgo/capacitor-fast-sql';
import { Preferences } from '@capacitor/preferences';
export async function copyRows(name: string, target: { encryptionKey?: string }) {
// Record which destination mode was migrated, so a later call with a different target fails loudly
const mode = target.encryptionKey ? 'encrypted' : 'plain';
const done = await Preferences.get({ key: `sqlite-copied:${name}` });
if (done.value === mode) return;
if (done.value) throw new Error(`${name} was already copied as ${done.value}, not ${mode}`);
const sqlite = new SQLiteConnection(CapacitorSQLite);
const src = await sqlite.createConnection(name, true, 'secret', 1, false);
await src.open();
try {
const dst = await FastSQL.connect({
database: name,
encrypted: Boolean(target.encryptionKey),
encryptionKey: target.encryptionKey,
});
const objects = ((await src.query(
`SELECT type, name, sql FROM sqlite_master
WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%'`,
)).values ?? []) as { type: string; name: string; sql: string }[];
const tables = objects.filter((o) => o.type === 'table');
const others = objects.filter((o) => o.type !== 'table');
const version = (await src.query('PRAGMA user_version')).values?.[0]?.user_version ?? 0;
await dst.transaction(async (tx) => {
for (const t of tables) await tx.execute(t.sql);
for (const t of tables) {
const rows = (await src.query(`SELECT * FROM "${t.name}"`)).values ?? [];
if (!rows.length) continue;
const cols = Object.keys(rows[0]);
const statement = `INSERT INTO "${t.name}" (${cols.map((c) => `"${c}"`).join(', ')}) VALUES (${cols.map(() => '?').join(', ')})`;
await tx.executeBatch(rows.map((r) => ({ statement, params: cols.map((c) => r[c]) })));
}
for (const o of others) await tx.execute(o.sql);
await tx.execute(`PRAGMA user_version = ${Number(version)}`);
});
// Only mark the copy as done once the transaction has committed
await Preferences.set({ key: `sqlite-copied:${name}`, value: mode });
} finally {
await sqlite.closeConnection(name, false);
}
}
Nutzt 'no-encryption' und false in einem createConnection falls die Quelle nicht verschlüsselt ist. Das Community-Plugin benötigt iosIsEncryption / androidIsEncryption in einem capacitor.config für seine Verschlüsselungsmodi, die Sie bereits haben, falls Sie sie verwendet haben. Bei großen Tabellen blättern Sie mit WHERE rowid > ? ORDER BY rowid LIMIT 5000.
Beide Plugins nutzen die SQLCipher 4-Standardeinstellungen, sodass sich eine verschlüsselte Datei auch direkt in Fast SQL mit derselben Passphrase öffnen lässt. Das Community-Plugin verwaltet diese Passphrase jedoch intern, und das Kopieren von Zeilen macht die Abhängigkeit von diesem Detail unnötig.
Schritt 4: Das alte Plugin entfernen
Nach ein oder zwei Releases mit der Migration:
bun remove @capacitor-community/sqlite jeep-sqlite
bunx cap sync
Entfernen sql-wasm.wasm aus Ihren Assets und allen jeep-sqlite Setup in main.ts. Mit dem Plugin entfällt auch seine SQLCipher-Abhängigkeit. Wenn Sie die Verschlüsselung von Fast SQL also nicht aktivieren, werden die iOS- und Android-Binaries kleiner.
Die Datenmigration code ist JavaScript und kann daher mit Capgo Live Updatesgeliefert werden, aber das Release, das Fast SQL hinzufügt, ist eine native Änderung und erfordert einen Store-Build. Liefern Sie zuerst den Native Build aus. Capgo Build kann in der Cloud erstellt werden, wenn man keinen Mac zur Hand hat (App Store Uploads erfordern seit April 2026 Xcode 26 oder neuer).
Fehlerbehebung
Fast SQL verbindet sich, aber jede Abfrage schlägt unter iOS fehl. NSAllowsLocalNetworking is missing from the app’s Info.plist, oder die App wurde nach dem Hinzufügen nicht neu gebaut.
Android: CLEARTEXT communication to localhost not permitted. Füge localhost hinzu domain-config zu deiner Network Security Config. Falls du bereits eine für SSL Pinning nutzt, merge sie einfach.
Es wurde nur ein Teil meines Schemas erstellt. Du hast einen Multi-Statement-String an execute()übergeben. Splitte ihn stattdessen in ein Array.
INSERT ... RETURNING gibt unter Android nichts zurück. In Fast SQL 8.2 liefert Android nur Zeilen für Statements, die mit beginnen: SELECT, PRAGMA oder EXPLAIN. Nutze insertId von run().
Booleans passen bei Android-Reads nicht zusammen. Übergeben 1/0 anstatt true/false in Query-Parametern. Android bindet Read-Query-Parameter als Text.
Die kopierte Datenbank ist leer. Das Community-Plugin wurde mit einem benutzerdefinierten iosDatabaseLocation, oder der Quellname weicht ab. Logge die von der URL zurückgegebene getUrl().
Web-Daten des alten Plugins sind weg. Das Community-Plugin speicherte Web-Daten in IndexedDB über jeep-sqlite, Fast SQL nutzt OPFS. Synchronisiere Web-Clients erneut von deinem Backend oder exportiere mit dem alten Plugin exportToJson und importiere die Zeilen in Fast SQL.
Andere Optionen
@capgo/capacitor-data-storage-sqlitewenn du eigentlich einen SQLite-basierten String-Key-Value-Store suchst. Siehe Key-Value-Storage in Capacitor.- Ein ORM oder Query Builder obendrauf: Kysely funktioniert mit Fast SQL über einen kurzen Custom-Driver. Drizzle passt aktuell besser zum Community-Plugin.
Die vollständige Fast SQL API findest du in den Plugin-Docs und auf der Plugin-Seite.