La principal alternativa a @capacitor-community/sqlite en Capacitor 8 es @capgo/capacitor-fast-sql: un plugin SQLite nativo para iOS, Android y web que mantiene SQLCipher opcional y envía los datos de las consultas a través de un canal HTTP local en lugar del bridge de Capacitor. Esta guía compara ambos de forma honesta, indica cuándo conviene quedarse con el plugin de la comunidad y explica cómo migrar tanto tu code como las bases de datos existentes de tus usuarios.
Por qué los equipos buscan una alternativa
@capacitor-community/sqlite ha sido el plugin SQLite predeterminado para Capacitor durante años y sigue manteniéndose (la versión 8.1.1 se lanzó en agosto de 2026). Las razones para cambiar son específicas:
- Serialización del bridge. Cada resultado pasa por el bridge de Capacitor como JSON. Eso está bien para consultas pequeñas, pero se nota cuando cargas miles de filas para sincronización o búsqueda.
- SQLCipher siempre está incluido. El plugin depende de SQLCipher en iOS y Android incluso si nunca encriptas. Eso aumenta el tamaño nativo y significa que tu binario contiene code de cifrado que debes tener en cuenta en las respuestas de cumplimiento de exportación.
- Modelo de almacenamiento web. En la web, utiliza el
jeep-sqlitecomponente: sql.js en memoria, persistido en IndexedDB cuando llamas asaveToStore(). Debes recordar guardar, y toda la base de datos vive en memoria. - Una gran superficie de API. Wrappers de conexión, comprobaciones de consistencia, conexiones de solo lectura y no conformes, tablas de sincronización, importación y exportación JSON. Potente, pero mucho que aprender para «ejecutar SQL».
- Una sola passphrase por app. La cifración usa un único secreto almacenado por el plugin (
setEncryptionSecret), compartido por todas las bases de datos cifradas de la app.
Comparación lado a lado
@capacitor-community/sqlite 8.1 |
@capgo/capacitor-fast-sql 8.2 |
|
|---|---|---|
| Plataformas | iOS, Android, Web, Electron | iOS, Android, Web |
| Ruta de datos | bridge de Capacitor | Servidor HTTP local con token por conexión (nativo) |
| Motor web | jeep-sqlite (sql.js + IndexedDB, manual saveToStore) |
Oficial @sqlite.org/sqlite-wasm con OPFS, las escrituras van al archivo |
| Cifrado | SQLCipher siempre incluido, una passphrase gestionada por el plugin | SQLCipher opcional, clave pasada por base de datos en connect() |
| Swift Package Manager | Sí (Package.swift) |
Sí (Package.swift) |
| Versionado de esquema | upgradeStatements / addUpgradeStatement |
El tuyo, normalmente PRAGMA user_version |
| Importación/exportación JSON, sincronización de tablas | Sí | No |
| Base de datos precargada desde assets | copyFromAssets() |
Copia el archivo por tu cuenta antes connect() |
| Helper de clave-valor | No | KeyValueStore |
| BLOBs | Sí | Sí, como Uint8Array |
| Soporte de ORM | TypeORM integrado capacitor driver |
Drivers personalizados (Kysely funciona bien) |
| Configuración nativa adicional | Web: copiar sql-wasm.wasm |
iOS NSAllowsLocalNetworking, regla cleartext de localhost en Android |
| Mantenedor | comunidad de Capacitor | Capgo |
Cuándo seguir con el plugin de la comunidad
Sé práctico. Quédate si:
- lanzas un Electron build,
- usas TypeORM (su driver capacitor está escrito para el plugin de la comunidad, ver TypeORM con Capacitor),
- dependes de importación/exportación JSON o tablas de sync y no quieres reconstruirlas,
- tus queries son pequeñas y la app funciona bien hoy.
Cuándo Fast SQL es mejor opción
Migra si:
- cargas o escribes datasets grandes (motores de sync, catálogos offline, índices de búsqueda, logs de CRDT),
- quieres cifrar solo algunas bases de datos, con claves del Keychain o Keystore,
- no quieres SQLCipher en una app que no lo necesita,
- quieres un archivo SQLite real en la web a través de OPFS, en lugar de una copia en memoria,
- quieres un API simple: conectar, consultar, ejecutar, transacción.
Para una guía completa de Fast SQL, consulta Cómo usar SQLite en una app de Capacitor.
Paso 1: instala Fast SQL junto al plugin anterior
Mantén el plugin de la comunidad instalado durante una versión para poder migrar los datos existentes.
bun add @capgo/capacitor-fast-sql
bunx cap sync
Configuración nativa que necesita Fast SQL:
- iOS:
NSAppTransportSecurity>NSAllowsLocalNetworking=trueenInfo.plist. - Android: una
network_security_config.xmlque permite cleartext alocalhosty127.0.0.1, referenciado desde el manifiesto. - Cifrado:
net.zetetic:sqlcipher-androiden el módulo de la app de Android y elCapgoCapacitorFastSql/SQLCipherpod en iOS. Detalles en cifrando SQLite en Capacitor. - Web:
Cross-Origin-Opener-Policy: same-originyCross-Origin-Embedder-Policy: require-corpheaders para OPFS.
Paso 2: mapea el API
Abrir una base de datos
Antes:
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();
Después:
import { FastSQL } from '@capgo/capacitor-fast-sql';
const db = await FastSQL.connect({ database: 'app', walMode: true, performancePresets: true });
FastSQL.connect() devuelve la conexión existente si la base de datos ya está abierta, así que no hay isConnection / retrieveConnection confusión.
Mapeo de métodos
| Plugin de la comunidad | SQL rápido |
|---|---|
createConnection() + open() |
FastSQL.connect({ database }) |
closeConnection(name) |
FastSQL.disconnect(name) |
db.query(sql, values) devuelve { values } |
db.query(sql, params) devuelve filas |
db.run(sql, values) devuelve { changes: { changes, lastId } } |
db.run(sql, params) devuelve { rowsAffected, insertId } |
db.execute(multiStatementString) |
Uno db.execute(sql) por sentencia, o db.executeBatch([...]) |
db.executeSet(set) |
db.executeBatch(ops) con { statement, params } |
beginTransaction / commitTransaction / rollbackTransaction |
db.transaction(async (tx) => ...) o beginTransaction() / commit() / rollback() |
addUpgradeStatement / upgradeStatements |
Una PRAGMA user_version ejecutor de migraciones |
copyFromAssets() |
Copiar un bundle .db en Directory.Data antes de la primera connect() |
setEncryptionSecret() + mode: 'secret' |
encrypted: true, encryptionKey en connect() |
initWebStore() / saveToStore() |
No hace falta, OPFS persiste las escrituras |
Queries y escrituras
Antes:
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;
Después:
const notes = await db.query('SELECT * FROM notes WHERE pinned = ?', [1]);
const { insertId } = await db.run('INSERT INTO notes (title) VALUES (?)', ['Hello']);
Ambos plugins devuelven filas como objetos con claves por nombre de columna.
Ejecución de múltiples sentencias
La comunidad execute() acepta una cadena con varias sentencias separadas por ;. Fast SQL prepara una sentencia por llamada. Divide tu SQL o, mejor aún, mantén las sentencias como un array:
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 })));
No dividas en ; con un enfoque ingenuo split(';') si alguna instrucción contiene un cuerpo de trigger o un literal de cadena con un punto y coma.
Actualizar sentencias a user_version
El plugin de la comunidad guarda la versión del esquema en PRAGMA user_version también. Si mantienes los mismos números de versión, las bases de datos de tus usuarios actuales ya están en la versión correcta tras la migración del archivo del paso 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}`);
});
}
}
Comprueba el user_version en una instalación existente antes de confiar en esto. Si no estás seguro, regístralo desde el antiguo plugin en tu versión actual.
Bases de datos prepobladas
Del plugin de la comunidad copyFromAssets() copia las bases de datos empaquetadas en el primer inicio. Con Fast SQL, coloca el archivo donde el plugin lo busca antes de conectar. Fast SQL almacena <name>.db en Documents en iOS y en la app files directorio de la app en Android, que son ambos Directory.Data en @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 });
}
Para archivos grandes, descárgalos por partes o envía un archivo comprimido y usa la copia nativa del plugin de archivos en lugar de base64 en JavaScript.
Paso 3: migra los datos de usuario existentes
Los usuarios que actualizan tu app tienen una base de datos creada por el plugin de la comunidad. Se encuentra en una ruta diferente con un nombre distinto:
| Plugin de la comunidad | SQL rápido | |
|---|---|---|
| iOS | Documents/<name>SQLite.db (por defecto iosDatabaseLocation) |
Documents/<name>.db |
| Android | databases/<name>SQLite.db |
files/<name>.db |
Opción A: copiar el archivo (bases de datos sin cifrar)
El más rápido, y conserva todo: índices, 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' });
}
Llámalo antes del primer FastSQL.connect({ database: name }). Notas:
getUrl()(iOS y Android) devuelve la URL del archivo de base de datos. Al pasar unafile://URL sindirectorypermite al plugin Filesystem leer desde cualquier ruta dentro de tu sandbox.- Cerrar la conexión y hacer checkpoint primero asegura que no queden escrituras recientes en un
-walarchivo. - Conserva el archivo antiguo durante una release y elimínalo después con el
delete()método una vez que tengas confianza. Luego, elimina el plugin de la comunidad.
Opción B: copiar filas en JavaScript (bases de datos cifradas o un cambio de esquema)
Cuando la fuente está cifrada con el secreto almacenado del plugin de la comunidad, la ruta más simple y segura es dejar que ese plugin descifre y copie las filas a una nueva base de datos Fast SQL. Esto también permite cifrar con una nueva clave por base de datos desde el almacenamiento seguro.
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);
}
}
Usa 'no-encryption' y false en createConnection si la fuente no está cifrada. El plugin de la comunidad requiere iosIsEncryption / androidIsEncryption en capacitor.config por sus modos cifrados, que ya tienes si los usaste. Para tablas grandes, usa paginación con WHERE rowid > ? ORDER BY rowid LIMIT 5000.
Ambos plugins usan los valores por defecto de SQLCipher 4, así que un archivo cifrado también puede abrirse en Fast SQL directamente con la misma passphrase. Sin embargo, el plugin de la comunidad gestiona esa passphrase internamente, y la copia de filas evita depender de ese detalle.
Paso 4: eliminar el plugin antiguo
Después de uno o dos lanzamientos con la migración implementada:
bun remove @capacitor-community/sqlite jeep-sqlite
bunx cap sync
Eliminar sql-wasm.wasm de tus assets y cualquier jeep-sqlite configuración en main.ts. Eliminar el plugin también elimina su dependencia de SQLCipher, por lo que si no optas por el cifrado de Fast SQL, los binarios de iOS y Android serán más pequeños.
La migración de datos code es JavaScript, así que puede enviarse con actualizaciones Live Update de Capgo, pero el release que añade Fast SQL es un cambio nativo y requiere un build para la tienda. Envía primero el build nativo. Build de Capgo puede generarlo en la nube si no tienes un Mac a mano (las subidas a App Store requieren Xcode 26 o posterior desde abril de 2026).
Solución de problemas
Fast SQL se conecta, pero todas las consultas fallan en iOS. NSAllowsLocalNetworking is missing from the app’s Info.plist, o la app no se recompiló después de añadirlo.
Android: CLEARTEXT communication to localhost not permitted. Añade el localhost domain-config a tu configuración de seguridad de red. Si ya tenías una para SSL pinning, combínalas.
Solo se creó parte de mi esquema. Pasaste una cadena con múltiples sentencias a execute(). Divídela en un array.
INSERT ... RETURNING no devuelve nada en Android. En Fast SQL 8.2, Android devuelve filas solo para sentencias que comienzan con SELECT, PRAGMA o EXPLAIN. Usa insertId de los run().
Los booleanos no coinciden en las lecturas de Android. Pasa 1/0 en lugar de true/false en los parámetros de consulta. Android enlaza los parámetros de consulta de lectura como texto.
La base de datos copiada está vacía. El plugin de la comunidad se configuró con un iosDatabaseLocation, o el nombre de origen es diferente. Registra la URL devuelta por getUrl().
Los datos web del plugin antiguo se han perdido. El plugin de la comunidad guardaba datos web en IndexedDB mediante jeep-sqlite, Fast SQL usa OPFS. Resincroniza los clientes web desde tu backend, o exporta con el plugin antiguo exportToJson e importa las filas en Fast SQL.
Otras opciones
@capgo/capacitor-data-storage-sqlitesi lo que realmente necesitas es un almacén de clave-valor basado en SQLite. Mira el almacenamiento clave-valor en Capacitor.- Un ORM o query builder encima de: Kysely funciona con Fast SQL mediante un pequeño driver personalizado. Drizzle se adapta mejor al plugin de la comunidad actualmente.
La API completa de Fast SQL está en la docs del plugin y en la página del plugin.