About sqlite-to-fast-sql
sqlite-to-fast-sql is a migration skill for Capacitor developers moving from bridge-based SQLite plugins to @capgo/capacitor-fast-sql while preserving local data. It covers API changes, platform configuration, encryption options, and data transfer approaches.
Key features
- 01Inventory existing SQL plugins and data
- 02Map query, write, and transaction APIs
- 03Configure iOS, Android, and Web
- 04Migrate the old database and verify data
- 05Set up optional SQLCipher encryption
Use cases
- 01Replace a SQLite plugin with Fast SQL
- 02Preserve existing local databases during migration
- 03Investigate slow large queries or batch writes
- 04Enable database encryption and test migration
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ cap-go/ capgo-skills --skill sqlite-to-fast-sqlAfter installing, type/sqlite-to-fast-sqlin Claude Code to confirm it loaded.
Bundled filesmetadata.json
Details
From the project READMESQLite to Fast SQL Migration
Swap a bridge-based SQL plugin for @capgo/capacitor-fast-sql without losing user data.
When to Use
TRIGGER when:
package.jsonhas@capacitor-community/sqlite,cordova-sqlite-storage,@capawesome-team/capacitor-sqlite, an ORM capacitor driver, or another SQLite plugin, and the user wants Fast SQL.- Large
SELECTs or sync writes are slow because every row crosses the Capacitor bridge as JSON. - The app needs encryption, transactions, batch writes, BLOBs, or an encrypted key-value store.
Do not use for:
@capacitor/preferencesor IndexedDB-only apps ->capacitor-offline-first.@ionic-enterprise/secure-storage/ Identity Vault ->ionic-enterprise-sdk-migration.- Apps already on Fast SQL needing schema design or sync logic ->
capacitor-offline-first.
Live Project Snapshot
Detected SQL-related packages:
!node -e "const fs=require('fs');if(!fs.existsSync('package.json'))process.exit(0);const pkg=JSON.parse(fs.readFileSync('package.json','utf8'));const needles=['sqlite','sqlcipher','typeorm','watermelondb','pouchdb','@capacitor-community/sqlite','@capawesome-team/capacitor-sqlite','@capgo/capacitor-fast-sql'];const out=[];for(const section of ['dependencies','devDependencies']){for(const [name,version] of Object.entries(pkg[section]||{})){if(needles.some((needle)=>name.includes(needle)))out.push(section+'.'+name+'='+version)}}console.log(out.sort().join('\n'))"
How Fast SQL Differs
- Native SQLite is reached through a token-protected HTTP server on
localhost, not the Capacitor bridge, so big result sets and batches avoid bridge serialization. This is why iOS ATS and Android cleartext config are required. - Plugin major follows Capacitor major (v8 for Capacitor 8). Current: 8.2.x.
- Database files: iOS
Documents/<database>.db, Android<filesDir>/<database>.db. Other plugins use different paths and names, so existing data is not picked up automatically. - Web: official
@sqlite.org/sqlite-wasmwith OPFS persistence (bundled dependency, nosql.jsinstall). Older versions used sql.js + IndexedDB; that data is not migrated automatically. - Encryption: SQLCipher, opt-in per platform.
Procedure
Step 1: Inventory
Read package.json and grep the data layer. Record: plugin and version, database names, schema/migration mechanism (user_version, upgrade statements, ORM migrations), use of transactions, BLOBs, encryption (and where the key comes from), JSON import/export, web support, and approximate data size. Report this before changing code.
Step 2: Map the API
import { FastSQL, KeyValueStore } from '@capgo/capacitor-fast-sql';
const db = await FastSQL.connect({ database: 'app' }); // { encrypted, encryptionKey, readOnly }
const rows = await db.query('SELECT * FROM todo WHERE done = ?', [0]);
const { rowsAffected, insertId } = await db.run('INSERT INTO todo (title) VALUES (?)', ['A']);
await db.execute('CREATE TABLE IF NOT EXISTS todo (id INTEGER PRIMARY KEY, title TEXT, done INTEGER DEFAULT 0)');
await db.executeBatch([{ statement: 'INSERT INTO log (m) VALUES (?)', params: ['x'] }]);
await db.transaction(async (tx) => { await tx.run('UPDATE a SET n = n - 1'); await tx.run('UPDATE b SET n = n + 1'); });
await FastSQL.disconnect('app');
@capacitor-community/sqlite | Fast SQL |
|---|---|
new SQLiteConnection(CapacitorSQLite), createConnection(name, encrypted, mode, version, readonly), db.open() | FastSQL.connect({ database, encrypted, encryptionKey, readOnly }) |
db.query(sql, values) -> { values } | db.query(sql, params) -> rows array |
db.run(sql, values) -> { changes: { changes, lastId } } | db.run(sql, params) -> { rowsAffected, insertId } |
db.execute(multiStatementSql) | db.execute(sql) per statement, or executeBatch |
db.executeSet([{ statement, values }]) | db.executeBatch([{ statement, params }]) |
beginTransaction/commitTransaction/rollbackTransaction, transaction flag on calls | db.transaction(cb) or beginTransaction() / commit() / rollback() |
addUpgradeStatement + version | Keep your own PRAGMA user_version migration runner |
closeConnection(name) | FastSQL.disconnect(name) |
importFromJson / exportToJson | Not provided; write rows with executeBatch |
cordova-sqlite-storage db.executeSql(sql, params, ok, err) / db.transaction(tx => tx.executeSql(...)) | db.query/db.run and db.transaction(async tx => ...) |
Key-value data (Ionic Storage, Preferences-like wrappers) -> KeyValueStore.open({ database, encrypted, encryptionKey, store }) with set/get/has/remove/clear/keys.
Step 3: Install and configure
npm install @capgo/capacitor-fast-sql
npx cap sync
iOS ios/App/App/Info.plist (loopback only; does not weaken ATS for other hosts):
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
Android android/app/src/main/res/xml/network_security_config.xml, referenced by android:networkSecurityConfig="@xml/network_security_config" on <application>:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">localhost</domain>
<domain includeSubdomains="false">127.0.0.1</domain>
</domain-config>
</network-security-config>
If the app already has a network security config, merge the domain-config instead of replacing the file.
Encryption (only if encrypted: true):
- Android:
implementation 'net.zetetic:sqlcipher-android:4.13.0'(or current) inandroid/app/build.gradledependencies. - iOS (CocoaPods):
pod 'CapgoCapacitorFastSql/SQLCipher', :path => '../../node_modules/@capgo/capacitor-fast-sql'inios/App/Podfile, thenpod install. For SPM projects check the plugin README for the current SQLCipher option. - Keep the key out of JS source: store it with
@capgo/capacitor-native-biometricor fetch it from your backend.
Web (only if the app ships on web): serve Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp, and in Vite add optimizeDeps.exclude: ['@sqlite.org/sqlite-wasm']. CapgoCapacitorFastSql.configureWeb({ ... }) before the first connect() is optional and a no-op on native.
Step 4: Preserve existing data
Pick one with the user:
- Dual-plugin release (safest): keep the old plugin installed for one release. On first launch, if a
migratedflag is missing, read tables from the old plugin,executeBatchthem into Fast SQL inside a transaction, verify counts, set the flag. Remove the old plugin in the next release. - File copy: copy the old
.dbfile to Fast SQL's path before the firstconnect()(old community plugin files are typically named<name>SQLite.db; verify the real path on a device first). Encrypted files keep working only with the same key and a compatible SQLCipher version. - Re-sync from server: acceptable when local data is a cache; confirm with the user.
Step 5: Swap code and remove the old plugin
Replace imports and calls behind the existing data-access layer; keep schema and migration steps. Then (after the carry-over release if option 1):
npm uninstall @capacitor-community/sqlite # or the plugin in use
npx cap sync
Verification
rg -n "@capacitor-community/sqlite|cordova-sqlite|CapacitorSQLite|SQLiteConnection" -g '!node_modules' srcreturns nothing after cleanup.- Upgrade test: install the current store build, create data, install the new build over it; row counts match.
- Smoke test on both platforms: create, read, update, delete, transaction rollback (throw inside
transactionand confirm no partial writes), BLOB round-trip (Uint8Array). - Encrypted DB: opening with a wrong key fails; file is unreadable with plain
sqlite3. - Measure a large query before/after to confirm the performance goal.
Error Handling
| Error / symptom | Fix |
|---|---|
iOS: requests to http://localhost:<port> fail, The resource could not be loaded because the App Transport Security policy requires the use of a secure connection | Add NSAllowsLocalNetworking |
Android: net::ERR_CLEARTEXT_NOT_PERMITTED or CLEARTEXT communication to localhost not permitted by network security policy | Add the localhost network_security_config.xml and reference it in the manifest |
iOS: Encryption is not available in this build. Add SQLCipher to enable encryption. | Install the SQLCipher subspec or use encrypted: false |
Android: Encryption is not available. ... to your app's build.gradle to enable encryption. | Add net.zetetic:sqlcipher-android to the app module |
Encryption key is required when encrypted is true | Pass encryptionKey with encrypted: true |
| App starts with an empty database after update | New file path; run the Step 4 carry-over |
Web: OPFS / SharedArrayBuffer errors | Missing COOP/COEP headers or Vite pre-bundling sqlite-wasm |
query returns objects where code expected { values } | Old community-plugin result shape; update callers to use the rows array |