Local Storage & Persistence

Most apps need to remember something between launches — a logged-in user's preferences, a "don't show this again" flag, a cached list of items, or a small offline dataset. Because a Cordova app's entire UI runs inside a WebView, you actually have three realistic options for storing data on the device: the plain web localStorage API (already built into the WebView, no plugin required), the more capable browser-native IndexedDB, and a real embedded SQLite database via the cordova-sqlite-storage plugin. This page walks through all three and helps you decide which one fits a given job.

localStorage: The Simplest Option

Because Cordova apps run inside a standard WebView, the plain web localStorage API works exactly as it does in a desktop browser — no plugin needed at all. It stores simple key-value pairs as strings, persists across app restarts, and is scoped to your app's origin so other apps can't read it. For settings, flags, small cached values, or a user's last-viewed tab, it's the least amount of code you can write for a persistence need.

// Saving a simple setting
localStorage.setItem('themeMode', 'dark');
localStorage.setItem('lastSyncTime', Date.now().toString());

// Reading it back later (even after the app has been closed and reopened)
var theme = localStorage.getItem('themeMode');
console.log('Saved theme: ' + theme);

// Removing a single key, or clearing everything
localStorage.removeItem('lastSyncTime');
// localStorage.clear(); // wipes every key for this app

A slightly larger worked example — remembering whether a user has completed a first-run tutorial:

document.addEventListener('deviceready', onDeviceReady, false);

function onDeviceReady() {
    var hasSeenIntro = localStorage.getItem('hasSeenIntro');

    if (hasSeenIntro === null) {
        // First launch ever -- show the intro, then remember it was shown
        showIntroScreen();
        localStorage.setItem('hasSeenIntro', 'true');
    } else {
        showHomeScreen();
    }
}
Limitations to keep in mind: localStorage only stores strings (you must JSON.stringify() objects/arrays before saving and JSON.parse() them back), it has a practical storage ceiling of roughly 5-10MB depending on the platform and WebView version, and it has no query language — you can only fetch a value by its exact key. It is not a database; treat it strictly as a flat key-value store for small, simple data.

Storing Objects and Arrays

Since every value is stored as a string, structured data needs to be serialized first. This is a common enough pattern that it's worth seeing explicitly:

var userProfile = {
    name: 'Asha',
    age: 29,
    favorites: ['reading', 'cycling']
};

// Objects and arrays must be serialized to a string before saving
localStorage.setItem('userProfile', JSON.stringify(userProfile));

// ...and parsed back into a real object when you read it
var savedProfile = JSON.parse(localStorage.getItem('userProfile'));
console.log(savedProfile.name); // "Asha"

IndexedDB: A More Powerful Browser-Native Option

When you need to store more data than is comfortable in localStorage, or you need to store and query structured records (not just flat key-value pairs), the WebView also gives you IndexedDB — a transactional, object-oriented database built into the browser engine itself, with no plugin required. It supports much larger storage quotas, indexes for fast lookups by field, and asynchronous (non-blocking) reads and writes.

IndexedDB's API is lower-level and more verbose than localStorage — you open a database, define "object stores" (roughly like tables), and work with transactions and callback/promise-based cursors rather than simple getItem/setItem calls. It's a deep topic on its own, so this page won't cover its full API in detail — the important thing to know is that it exists as a built-in, no-plugin option when localStorage's simple string-only model isn't enough, but you don't need genuine relational SQL queries either.

cordova-sqlite-storage: A Real SQL Database

When your data is genuinely relational — multiple related tables, the need to run WHERE, JOIN, or aggregate SQL queries, or a dataset large enough that you want indexed, structured storage — the cordova-sqlite-storage plugin gives your app an actual embedded SQLite database, the same engine used natively on both Android and iOS. Install it like any other Cordova plugin:

cordova plugin add cordova-sqlite-storage

Opening a database and running basic SQL only after deviceready has fired:

document.addEventListener('deviceready', onDeviceReady, false);

var db;

function onDeviceReady() {
    // Opens (or creates) a local SQLite database file
    db = window.sqlitePlugin.openDatabase({
        name: 'myapp.db',
        location: 'default'
    });

    db.transaction(function(tx) {
        // CREATE TABLE -- only needs to run once, safe to call every launch with IF NOT EXISTS
        tx.executeSql(
            'CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, body TEXT)'
        );

        // INSERT a row using bound parameters (never string-concatenate user input into SQL)
        tx.executeSql(
            'INSERT INTO notes (title, body) VALUES (?, ?)',
            ['Shopping list', 'Milk, eggs, bread']
        );

        // SELECT rows back out
        tx.executeSql('SELECT id, title, body FROM notes', [], function(tx, results) {
            for (var i = 0; i < results.rows.length; i++) {
                var row = results.rows.item(i);
                console.log(row.id + ': ' + row.title);
            }
        });
    });
}
Note: This is correct, real cordova-sqlite-storage JavaScript — but this website's own project doesn't run a Cordova build, so you won't see it actually execute on this page. Copy it into your own project's www/js/index.js to try it after installing the plugin.

Decision Guide: Which One Should You Use?

Your SituationBest Choice
A handful of settings, flags, or small cached stringslocalStorage — simplest possible API, no plugin
A moderate amount of structured data, no SQL needed, want to stay plugin-freeIndexedDB — built-in, higher capacity, indexed lookups
Genuinely relational data, need real SQL queries (joins, filters, aggregates)cordova-sqlite-storage — an actual embedded database
Large offline dataset you need to search/filter quicklycordova-sqlite-storage — SQL indexes make this fast and predictable

Combining Persistence With Offline Awareness

Persistence and network awareness go hand in hand. As covered on the Network Information page, a well-built app should detect when the device has lost connectivity and adjust its behavior — and one of the most useful things it can do offline is fall back to locally cached data instead of showing an empty screen or an error. A typical pattern: whenever a network request succeeds, save the result to localStorage (or SQLite for larger datasets); when the app detects it's offline, read from that local cache and show it immediately, perhaps with a small "showing saved data" banner. This turns a hard failure into a graceful, still-useful experience.

Practical Tips

  • Always wrap SQLite calls in the deviceready handler — the plugin isn't available before then.
  • Wrap JSON.parse() calls on stored data in a try/catch — corrupted or missing data shouldn't crash your app.
  • Don't store sensitive data (passwords, tokens) in plain localStorage — it isn't encrypted. Look at a dedicated secure-storage plugin if you need that.
  • Keep your SQLite schema changes (adding columns, new tables) versioned with simple CREATE TABLE IF NOT EXISTS / ALTER TABLE checks run on each launch.
Next steps: Head to Splash Screen & Status Bar to control what the user sees in the first moments after launching your app.