Choosing Between LocalStorage and SQLite in Ionic Apps
A concise decision guide comparing Ionic's LocalStorage and SQLite options, with code samples, validation steps, and practical limits to help you pick the right persistence strategy.
24 May 2026, 00:01 UTC

Decision and constraints
When persisting data in an Ionic application you must pick a storage mechanism that fits the app’s offline needs, data volume, performance expectations, and willingness to add native plugin overhead. The two most common options are the browser‑based LocalStorage API and a SQLite database accessed via Cordova/Capacitor plugins. This guide walks through the trade‑offs, shows a compact comparison table, and provides concrete code snippets for both approaches, followed by validation steps you can run on a device or emulator.
Comparison table
| Option | Storage type | Size limit | Performance | Plugin required | Typical use cases |
|---|---|---|---|---|---|
| LocalStorage | Key‑value string store (browser) | ≈5 MB per origin | Fast, synchronous reads/writes | No | Simple settings, small caches, feature flags |
| SQLite | Relational DB (via Cordova/Capacitor plugin) | Limited by device storage (typically GBs) | Slightly slower, asynchronous API | Yes (cordova-sqlite-storage or @capacitor-community/sqlite) | Complex queries, large datasets, offline sync, user‑generated content |
Trade‑offs
LocalStorage offers zero‑plugin simplicity: you can start using it immediately in any web view, and operations are synchronous, which feels instant for tiny payloads. However, it stores only strings, so any object must be JSON‑serialized, and the ~5 MB ceiling can be hit quickly if you cache images or large logs. Clearing site data in the browser (or the WebView) wipes the store, making it unsuitable for data that must survive a user‑initiated clear.
SQLite removes the size restriction and gives you a full relational engine with indexes, transactions, and complex queries. The downside is the added native plugin: you must install the plugin, run npx cap sync (or Cordova equivalent), and handle asynchronous APIs. The plugin increases the app binary size and introduces a build step for iOS/Android. Error handling is more involved because you need to manage database open/close lifecycle and potential SQL errors.
Concrete implementation – SQLite (Capacitor)
Assume you have an Ionic Capacitor project and have added the SQLite plugin:
npm install @capacitor-community/sqlite
npx cap sync
Then you can open a connection, create a table, insert a row, and read it back:
import { SQLiteDBConnection } from '@capacitor-community/sqlite';
async function demoSQLite() {
const db = await new SQLiteDBConnection()
.createConnection('myapp_db', false, 'no-encryption', 1, false);
await db.open();
// Ensure table exists
await db.run(
'CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, title TEXT, body TEXT)'
);
// Insert a sample note
await db.run(
'INSERT INTO notes (title, body) VALUES (?, ?)',
['First note', 'This is the body']
);
// Retrieve all notes
const result = await db.query('SELECT * FROM notes');
console.log('Notes:', result.values);
// Close when done (optional in long‑running apps)
await db.close();
}
Place this function in a service or page method and call it after the platform is ready (e.g., in ngOnInit or ionViewDidEnter).
Concrete implementation – LocalStorage
For the same logical operation using LocalStorage, you only need to serialize/deserialize JSON:
function demoLocalStorage() {
const key = 'notes';
// Read existing notes (or start with empty array)
const raw = localStorage.getItem(key) ?? '[]';
let notes: Array<{id:number;title:string;body:string}> = JSON.parse(raw);
// Add a new note
const newId = notes.length > 0 ? Math.max(...notes.map(n => n.id)) + 1 : 1;
notes.push({ id: newId, title: 'First note', body: 'This is the body' });
// Write back
localStorage.setItem(key, JSON.stringify(notes));
// Verify
console.log('Notes from LocalStorage:', JSON.parse(localStorage.getItem(key) ?? '[]'));
}
This code can run in any Ionic page or service without extra setup.
Validation and practical checks
SQLite validation
- Run the app on a device or emulator:
npx cap run androidornpx cap run ios. - After the demo function executes, open Chrome DevTools → Inspect → Application → IndexedDB (Capacitor exposes SQLite via IndexedDB in the browser) or use
adb shellto pull the database file: adb shell run-as com.your.appdatabases/myapp_dbsqlite3 databases/myapp_db \"SELECT * FROM notes;\"- Confirm that the row you inserted appears in the query output.
- To test persistence, close the app, relaunch, and run a read‑only query; the data should still be present.
LocalStorage validation
- Run the app in the browser (
ionic serve) or on a device. - Open DevTools → Application → Local Storage (for the appropriate origin).
- After calling
demoLocalStorage(), verify that a key namednotesexists and its value is a JSON array containing the inserted object. - Reload the page; the key/value should remain unless you manually choose “Clear site data” in the browser settings.
Limitations and how to check the result
Both mechanisms have constraints you should verify before relying on them in production:
- LocalStorage size – If you anticipate storing more than a few hundred kilobytes, test by writing a large JSON string (e.g., 1 MB) and confirm that
setItemdoes not throw a quota‑exceeded error. If it does, consider SQLite or server‑side storage. - SQLite plugin overhead – Measure the increase in app binary size after adding the plugin (compare
ios/AppName.apporandroid/app/build/outputs/apk/debug/app-debug.apkbefore and after). Ensure the added size fits your distribution goals. - Asynchronous complexity – Wrap SQLite calls in a service that returns Promises or uses async/await, and write unit tests that mock the plugin (Capacitor provides
Capacitor.mockPlugin) to verify your logic without a native build. - Data loss on clear – For LocalStorage, deliberately clear site data in the browser/WebView and confirm the key disappears; if this is unacceptable for your data, switch to SQLite.
By following the validation steps above you can confirm that the chosen storage behaves as expected on the target platforms before shipping the app.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.