La principale alternative à @capacitor-community/sqlite sur Capacitor 8 est @capgo/capacitor-fast-sql: un plugin SQLite natif pour iOS, Android et le web qui garde SQLCipher optionnel et envoie les données de requêtes via un canal HTTP local plutôt que le pont Capacitor. Ce guide compare honnêtement les deux, indique quand rester sur le plugin communautaire et détaille la migration de votre code et des bases de données existantes de vos utilisateurs.
Pourquoi les équipes cherchent une alternative
@capacitor-community/sqlite est le plugin SQLite par défaut pour Capacitor depuis des années et il est toujours maintenu (la version 8.1.1 a été publiée en août 2026). Les raisons qui poussent à changer sont précises :
- Sérialisation du bridge. Chaque résultat passe par le bridge Capacitor sous forme de JSON. C’est acceptable pour de petites requêtes, mais cela se fait sentir quand vous chargez des milliers de lignes pour la synchronisation ou la recherche.
- SQLCipher est toujours inclus. Le plugin dépend de SQLCipher sur iOS et Android, même si vous ne chiffrez jamais. Cela augmente la taille native et signifie que votre binaire contient du code de chiffrement que vous devez prendre en compte dans vos réponses de conformité à l’exportation.
- Modèle de stockage web. Sur le web, il utilise le
jeep-sqlitecomposant : sql.js en mémoire, persisté dans IndexedDB lorsque vous appelezsaveToStore(). Vous devez penser à sauvegarder, et toute la base de données reste en mémoire. - Une surface API étendue. Wrappers de connexion, contrôles de cohérence, connexions en lecture seule et non conformes, synchronisation de tables, import et export JSON. Puissant, mais beaucoup à apprendre pour « exécuter du SQL ».
- Une seule phrase secrète par application. Le chiffrement utilise un secret unique stocké par le plugin (
setEncryptionSecret), partagé par toutes les bases de données chiffrées de l’application.
Comparaison côte à côte
@capacitor-community/sqlite 8.1 |
@capgo/capacitor-fast-sql 8.2 |
|
|---|---|---|
| Plateformes | iOS, Android, Web, Electron | iOS, Android, Web |
| Data path | bridge Capacitor | Serveur HTTP local avec un token par connexion (natif) |
| Moteur web | jeep-sqlite (sql.js + IndexedDB, manuel) saveToStore) |
Officiel @sqlite.org/sqlite-wasm avec OPFS, les écritures se font dans le fichier |
| Chiffrement | SQLCipher toujours inclus, une seule passphrase gérée par le plugin | SQLCipher optionnel, clé passée par base à l'ouverture connect() |
| Swift Package Manager | Oui (Package.swift) |
Oui (Package.swift) |
| Versionnage du schéma | upgradeStatements / addUpgradeStatement |
Le vôtre, généralement PRAGMA user_version |
| Import/export JSON, sync des tables | Oui | Non |
| Base de données pré-remplie depuis les assets | copyFromAssets() |
Copiez le fichier vous-même avant connect() |
| Assistant clé-valeur | Non | KeyValueStore |
| BLOBs | Oui | Oui, en Uint8Array |
| Support ORM | TypeORM intégré capacitor driver |
Drivers personnalisés (Kysely fonctionne bien) |
| Configuration native supplémentaire | Web : copier sql-wasm.wasm |
iOS NSAllowsLocalNetworking, règle cleartext localhost Android |
| Mainteneur | communauté Capacitor | Capgo |
Quand rester sur le plugin communautaire
Soyez pragmatiques. Restez si :
- vous déployez une Electron build,
- vous utilisez TypeORM (son pilote capacitor est écrit pour le plugin communautaire, voir TypeORM avec Capacitor),
- you rely on Import/export JSON ou synchronisation des tables et ne souhaitez pas les reconstruire,
- vos requêtes sont légères et l’application fonctionne bien aujourd’hui.
Quand Fast SQL est le meilleur choix
Migrez si :
- vous chargez ou écrivez de grands jeux de données (moteurs de synchronisation, catalogues hors ligne, index de recherche, logs CRDT),
- vous souhaitez chiffrer uniquement certaines bases de données, avec des clés issues du Keychain ou du Keystore,
- vous ne voulez pas inclure SQLCipher dans une application qui n’en a pas besoin,
- vous voulez un vrai fichier SQLite sur le web via OPFS plutôt qu'une copie en mémoire,
- vous voulez un simple API : connexion, requête, exécution, transaction.
Pour un guide complet de Fast SQL, voir Comment utiliser SQLite dans une application Capacitor.
Étape 1 : installer Fast SQL à côté de l’ancien plugin
Gardez le plugin communautaire installé pour une release afin de pouvoir migrer les données existantes.
bun add @capgo/capacitor-fast-sql
bunx cap sync
Configuration native requise par Fast SQL :
- iOS:
NSAppTransportSecurity>NSAllowsLocalNetworking=truedansInfo.plist. - Android: un
network_security_config.xmlqui autorise le trafic en clair verslocalhostet127.0.0.1, référencé dans le manifest. - Chiffrement:
net.zetetic:sqlcipher-androiddans le module d'application Android et leCapgoCapacitorFastSql/SQLCipherpod sur iOS. Détails dans chiffrer SQLite dans Capacitor. - Le Web:
Cross-Origin-Opener-Policy: same-originetCross-Origin-Embedder-Policy: require-corpen-têtes pour OPFS.
Étape 2 : mapper l’API
Ouvrir une base de données
Avant :
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();
Après :
import { FastSQL } from '@capgo/capacitor-fast-sql';
const db = await FastSQL.connect({ database: 'app', walMode: true, performancePresets: true });
FastSQL.connect() renvoie la connexion existante si la base de données est déjà ouverte, il n’y a donc isConnection / retrieveConnection manipulation.
Correspondance des méthodes
| Plugin communautaire | SQL rapide |
|---|---|
createConnection() + open() |
FastSQL.connect({ database }) |
closeConnection(name) |
FastSQL.disconnect(name) |
db.query(sql, values) renvoie { values } |
db.query(sql, params) renvoie les lignes |
db.run(sql, values) renvoie { changes: { changes, lastId } } |
db.run(sql, params) renvoie { rowsAffected, insertId } |
db.execute(multiStatementString) |
Un db.execute(sql) par instruction, ou db.executeBatch([...]) |
db.executeSet(set) |
db.executeBatch(ops) par { statement, params } |
beginTransaction / commitTransaction / rollbackTransaction |
db.transaction(async (tx) => ...) ou beginTransaction() / commit() / rollback() |
addUpgradeStatement / upgradeStatements |
Un PRAGMA user_version migration runner |
copyFromAssets() |
Copier un bundle .db en Directory.Data avant la première connect() |
setEncryptionSecret() + mode: 'secret' |
encrypted: true, encryptionKey sur connect() |
initWebStore() / saveToStore() |
Inutile, OPFS persiste les écritures |
Requêtes et écritures
Avant :
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;
Après :
const notes = await db.query('SELECT * FROM notes WHERE pinned = ?', [1]);
const { insertId } = await db.run('INSERT INTO notes (title) VALUES (?)', ['Hello']);
Les deux plugins renvoient les lignes sous forme d'objets indexés par nom de colonne.
Exécution de plusieurs instructions
Le plugin communautaire execute() accepte une chaîne de caractères contenant plusieurs instructions séparées par ;. Fast SQL prépare une instruction par appel. Découpez votre SQL, ou mieux, conservez les instructions sous forme de tableau :
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 })));
Ne découpez pas sur ; avec une approche naïve split(';') naïf si une instruction contient un corps de trigger ou une chaîne littérale avec un point-virgule.
Mettre à jour les instructions vers user_version
Le plugin communautaire stocke la version du schéma dans PRAGMA user_version également. Si vous conservez les mêmes numéros de version, les bases de données de vos utilisateurs existants sont déjà à la bonne version après la migration de fichiers de l'étape 3.
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}`);
});
}
}
Vérifiez le chemin réel user_version sur une installation existante avant de vous y fier. Si vous n’êtes pas sûr, loggez-le depuis l’ancien plugin dans votre release actuelle.
Bases de données préremplies
Le plugin communautaire copyFromAssets() copie les bases de données bundled au premier lancement. Avec Fast SQL, placez le fichier là où le plugin le cherche avant de vous connecter. Fast SQL stocke <name>.db dans Documents sur iOS et dans le répertoire files de l’app sur Android, qui sont tous deux Directory.Data dans @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 });
}
Pour les gros fichiers, téléchargez-les par morceaux ou livrez un fichier compressé et utilisez la copie native du plugin file plutôt que du base64 en JavaScript.
Step 3: migrate existing user data
Les utilisateurs qui mettent à jour votre app ont une base de données créée par le plugin communautaire. Elle se trouve à un autre chemin, avec un autre nom :
| Plugin communautaire | SQL rapide | |
|---|---|---|
| iOS | Documents/<name>SQLite.db (par défaut iosDatabaseLocation) |
Documents/<name>.db |
| Android | databases/<name>SQLite.db |
files/<name>.db |
Option A : copier le fichier (bases de données non chiffrées)
La plus rapide, et elle conserve tout : index, triggers, 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' });
}
Appel à faire avant le premier FastSQL.connect({ database: name }). Remarques :
getUrl()(iOS et Android) renvoie l’URL du fichier de base de données. Passer unfile://URL sansdirectorypermet au plugin Filesystem de lire n’importe quel chemin dans votre sandbox.- Fermer la connexion et effectuer un checkpoint en premier garantit qu'aucune écriture récente ne reste dans un
-walfichier. - Conservez l'ancien fichier pendant une release, puis supprimez-le plus tard avec la
delete()méthode une fois que vous êtes sûr de vous. Supprimez ensuite le plugin communautaire.
Option B : copier les lignes en JavaScript (bases de données chiffrées ou changement de schéma)
Si la source est chiffrée avec le secret stocké par le plugin communautaire, la voie la plus simple et la plus sûre est de laisser ce plugin déchiffrer et copier les lignes dans une nouvelle base de données Fast SQL. Cela vous permet également de chiffrer avec une nouvelle clé par base de données issue du stockage sécurisé.
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);
}
}
Utiliser 'no-encryption' et false dans createConnection si la source n'est pas chiffrée. Le plugin communautaire nécessite iosIsEncryption / androidIsEncryption dans capacitor.config pour ses modes chiffrés, que vous avez déjà si vous les avez utilisés. Pour les grandes tables, parcourez par pages avec WHERE rowid > ? ORDER BY rowid LIMIT 5000.
Les deux plugins utilisent les valeurs par défaut de SQLCipher 4, donc un fichier chiffré peut aussi s’ouvrir directement dans Fast SQL avec le même mot de passe. Le plugin communautaire gère ce mot de passe en interne, mais la copie des lignes évite de dépendre de ce détail.
Étape 4 : supprimer l'ancien plugin
Après une ou deux releases avec la migration en place :
bun remove @capacitor-community/sqlite jeep-sqlite
bunx cap sync
Supprimer sql-wasm.wasm de vos assets et de tout jeep-sqlite configuration en main.ts. Retirer le plugin retire aussi sa dépendance SQLCipher, donc si vous n’activez pas le chiffrement de Fast SQL, les binaires iOS et Android sont plus petits.
La migration de données code est en JavaScript, elle peut donc être livrée avec les live updates Capgo, mais la release qui ajoute Fast SQL est un changement natif et nécessite un build pour les stores. Déployez d’abord le build natif. Build Capgo peut le générer dans le cloud si vous n’avez pas de Mac sous la main (les uploads vers l’App Store exigent Xcode 26 ou plus depuis avril 2026).
Troubleshooting
Fast SQL se connecte, mais toutes les requêtes échouent sur iOS. NSAllowsLocalNetworking est absent de l’application Info.plist, ou l’application n’a pas été recompilée après son ajout.
Android : CLEARTEXT communication to localhost not permitted. Ajoutez localhost domain-config à votre configuration de sécurité réseau. Si vous en aviez déjà une pour le SSL pinning, fusionnez-les.
Seule une partie de mon schéma a été créée. Vous avez passé une chaîne multi-instructions à execute(). Découpez-l’en un tableau.
INSERT ... RETURNING ne renvoie rien sur Android. Dans Fast SQL 8.2, Android ne renvoie des lignes que pour les instructions commençant par SELECT, PRAGMA ou EXPLAIN. Utilisez insertId depuis run().
Booleans do not match on Android reads. Passer 1/0 au lieu de true/false dans les paramètres de requête. Android lie les paramètres de requête en lecture au type texte.
La base de données copiée est vide. Le plugin communautaire a été configuré avec un iosDatabaseLocation, ou le nom de la source diffère. Journalisez l’URL renvoyée par getUrl().
Les données web de l’ancien plugin ont disparu. Le plugin communautaire stockait les données web dans IndexedDB via jeep-sqlite, Fast SQL utilise OPFS. Resynchronisez les clients web depuis votre backend, ou exportez avec l’ancien plugin exportToJson et importez les lignes dans Fast SQL.
Autres options
@capgo/capacitor-data-storage-sqlitesi ce dont vous avez vraiment besoin est un store clé-valeur basé sur SQLite. Voir le stockage clé-valeur dans Capacitor.- Un ORM ou un query builder par-dessus: Kysely fonctionne avec Fast SQL via un court driver personnalisé. Drizzle convient mieux au plugin communautaire pour l’instant.
L’API Fast SQL complet se trouve dans le doc du plugin et sur le page du plugin.