Für Key-Value-Speicher in Capacitor verwenden @capacitor/preferences für eine Handvoll kleiner Einstellungen, einen SQLite-basierten Key-Value-Speicher für alles Größere oder Zahlreichere und den Keychain oder Keystore für Secrets. Dieser Guide vergleicht die in Capacitor 8 verfügbaren Optionen, zeigt funktionierenden code für jede und erklärt, wie man von einer zur anderen wechselt, ohne Benutzerdaten zu verlieren.
Die meisten Apps nutzen letztlich zwei dieser Optionen gleichzeitig: Preferences oder SQLite für den App-State und Secure Storage für den Auth-Token.
Die Optionen im Überblick
localStorage / IndexedDB |
@capacitor/preferences |
Schnelles SQL KeyValueStore |
@capgo/capacitor-data-storage-sqlite |
@capgo/capacitor-native-biometric setData |
|
|---|---|---|---|---|---|
| Basierend auf | WebView-Speicher | UserDefaults / SharedPreferences | SQLite-Datei | SQLite-Datei | Keychain / Keystore |
| Übersteht OS-Speicherbereinigung | Nicht garantiert | Ja | Ja | Ja | Ja |
| Werttypen | Strings (localStorage) | Strings | JSON-Werte, Zahlen, Boolesche Werte, Uint8Array |
Strings | Strings |
| Geeignete Größenordnung | Klein | Klein, wenige KB pro Wert | Große Werte, viele Keys | Viele Keys | Sehr klein (unter ca. 8 KB auf Android) |
| Verschlüsselung | Nein | Nein | Optional SQLCipher, Key aus Secure Storage | Optional SQLCipher, Passphrase in Config | Ja, hardwarebasiert |
| Namespaces | Pro Origin | group Option |
store Option |
Tabellen | Schlüsselnamen |
| Abfrage nach Präfix | Nein | Nein | List keys | filtervalues |
Nein |
| Web | Ja | localStorage |
SQLite Wasm (OPFS) | IndexedDB | Nicht für Secrets |
Warum nicht localStorage
localStorage und IndexedDB in einer Capacitor WebView funktionieren, aber die Daten gehören zur WebView, nicht zum nativen Speicher deiner App. Das Betriebssystem kann den WebView-Speicher freigeben, wenn der Speicherplatz knapp wird, und die eigene Dokumentation von Capacitor rät davon ab, sich darauf zu verlassen, wenn du Daten nicht verlieren kannst. Wenn du den Hostname oder das Schema der App in capacitor.config änderst, ändert sich auch der Origin, und die WebView zeigt auf einen anderen, leeren Speicher.
Nutze sie als Cache, der verschwinden kann. Für alles andere verwende eine der nativ unterstützten Optionen.
Option 1: Capacitor Preferences für kleine Einstellungen
@capacitor/preferences ist das offizielle Plugin für leichte Key-Value-Daten. Auf iOS schreibt es in UserDefaults, auf Android in SharedPreferences, im Web in localStorage.
bun add @capacitor/preferences
bunx cap sync
import { Preferences } from '@capacitor/preferences';
interface Settings {
theme: 'light' | 'dark' | 'system';
fontScale: number;
}
const DEFAULTS: Settings = { theme: 'system', fontScale: 1 };
export async function loadSettings(): Promise<Settings> {
const { value } = await Preferences.get({ key: 'settings' });
return value ? { ...DEFAULTS, ...JSON.parse(value) } : DEFAULTS;
}
export async function saveSettings(settings: Settings): Promise<void> {
await Preferences.set({ key: 'settings', value: JSON.stringify(settings) });
}
Nutze es für: Theme, Sprache, Onboarding-Flags, zuletzt gewählte Tab, Feature-Toggles.
Nicht verwenden für:
- Secrets. Werte werden im Klartext im App-Sandbox gespeichert und sind in Geräte-Backups enthalten.
- Große Werte.
UserDefaultsundSharedPreferencesladen ihre gesamte Datei in den Speicher. Große JSON-Blobs verlangsamen jedes Lesen und Schreiben. - Viele Keys, die du nach Präfix auflisten oder löschen musst.
keys()gibt alles zurück, sodass du in JavaScript filterst.
Preferences.configure({ group: 'MyApp' }) ändert den Namespace, unter dem die Keys gespeichert werden. Die Standardgruppe ist CapacitorStorage.
Option 2: Ein SQLite-Key-Value-Store
Sobald du gecachte API-Antworten, Entwürfe, Offline-Warteschlangen oder Hunderte von Einträgen speicherst, ist eine SQLite-Datei der bessere Ort. Sie verarbeitet große Werte, Schreibvorgänge sind atomar und du kannst sie verschlüsseln.
Schneller SQL-Key-Value-Store
@capgo/capacitor-fast-sql enthält eine KeyValueStore Klasse über seiner SQLite-Verbindung. Sie erstellt eine __kv_store Tabelle, behält den Wertetyp bei und unterstützt mehrere benannte Stores in einer Datenbank.
bun add @capgo/capacitor-fast-sql
bunx cap sync
Schnelles SQL erfordert zwei native Einstellungen (iOS-Lo Info.plistkalnetzwerk, eine Cleartext-Regel für localhost auf Android). Diese sind in So nutzt du SQLite in einer Capacitor App.
import { KeyValueStore } from '@capgo/capacitor-fast-sql';
const cache = await KeyValueStore.open({ database: 'app', store: 'api-cache' });
// Objects, arrays, numbers, booleans, null and Uint8Array are stored as-is
await cache.set('user:42', { id: 42, name: 'Ada', roles: ['admin'] });
await cache.set('lastSync', Date.now());
const user = (await cache.get('user:42')) as { id: number; name: string } | null;
const exists = await cache.has('lastSync');
const allKeys = await cache.keys();
await cache.remove('user:42');
await cache.clear(); // only clears the 'api-cache' store
await cache.close();
API zusammengefasst: open(options), fromConnection(connection, store), set, get (gibt null zurück, wenn nicht vorhanden), has, remove, clear, keys, close.
Zwei Muster, die man kennen sollte:
Mehrere Stores, eine Datenbank. Der store option namespaced Keys, sodass settings, drafts und api-cache können nebeneinander existieren und unabhängig voneinander gelöscht werden.
Key-Value neben echten Tabellen. Wenn deine App Fast SQL bereits für relationale Daten nutzt, verwende die bestehende Verbindung, statt eine zweite zu öffnen:
import { FastSQL, KeyValueStore } from '@capgo/capacitor-fast-sql';
const db = await FastSQL.connect({ database: 'app' });
const drafts = await KeyValueStore.fromConnection(db, 'drafts');
// You own `db`: drafts.close() does not disconnect it
Wenn ein Key-Value-Blob später zu etwas heranwächst, das du abfragen willst (z. B. alle Entwürfe, die älter als eine Woche sind), kannst du ihn in eine echte Tabelle in derselben Datenbank verschieben.
Verschlüsselter Key-Value-Storage
KeyValueStore.open akzeptiert dieselben Optionen wie FastSQL.connect, einschließlich encrypted und encryptionKey auf iOS und Android:
import { KeyValueStore } from '@capgo/capacitor-fast-sql';
import { NativeBiometric } from '@capgo/capacitor-native-biometric';
async function getKey(): Promise<string> {
const { isSaved } = await NativeBiometric.isDataSaved({ key: 'kv.key' });
if (isSaved) return (await NativeBiometric.getData({ key: 'kv.key' })).value;
const bytes = crypto.getRandomValues(new Uint8Array(32));
const key = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
await NativeBiometric.setData({ key: 'kv.key', value: key });
return key;
}
const secureStore = await KeyValueStore.open({
database: 'secure-kv',
encrypted: true,
encryptionKey: await getKey(),
});
Das erfordert SQLCipher in den Native Builds. Das Setup und was zu tun ist, wenn der Key nach einem Backup-Restore verloren geht, steht in So verschlüsselst du eine SQLite-Datenbank in Capacitor.
@capgo/capacitor-data-storage-sqlite
@capgo/capacitor-data-storage-sqlite ist ein dediziertes Key-Value-Plugin auf SQLite-Basis (ursprünglich von Jean Pierre Quéau, jetzt von Capgo gewartet). Die Werte sind Strings, und es bietet einige Extras: mehrere Tabellen pro Store, Prefix- und Suffix-Filterung sowie JSON-Import und -Export.
bun add @capgo/capacitor-data-storage-sqlite
bunx cap sync
import { CapgoCapacitorDataStorageSqlite as Store } from '@capgo/capacitor-data-storage-sqlite';
await Store.openStore({ database: 'appStore', table: 'settings' });
await Store.set({ key: 'locale', value: 'fr' });
await Store.set({ key: 'profile', value: JSON.stringify({ name: 'Ada' }) });
const { value } = await Store.get({ key: 'locale' });
const { result: hasProfile } = await Store.iskey({ key: 'profile' });
const { keys } = await Store.keys();
// Switch table inside the same store
await Store.setTable({ table: 'drafts' });
const { values } = await Store.filtervalues({ filter: 'note%' }); // values whose key starts with "note"
await Store.remove({ key: 'note-1' });
await Store.clear(); // clears the current table
filtervalues wendet den Filter als SQL an LIKE Muster auf den Key (note% für Präfix, %note für Suffix, Klartext für Contains). Das Muster wird in die SQL-String eingefügt, daher nur Filter übergeben, die du kontrollierst, niemals rohe Benutzereingaben.
Auf dem Web wird IndexedDB verwendet über localforage, das du separat installierst:
bun add localforage
Zu seiner Verschlüsselung: openStore({ encrypted: true, mode: 'secret' }) verwendet SQLCipher mit einem Passphrase aus capacitor.config unter plugins.CapgoCapacitorDataStorageSqlite.encryptionSecret. Dieses Passphrase ist in deinem App-Bundle enthalten, und wenn du es nicht setzt, fällt das Plugin auf einen eingebauten Standard zurück. Behandle dies als Schutz vor gelegentlicher Dateiinspektion, nicht vor jemandem, der deine App entpackt. Für echte Secrets verwende einen Key aus dem Keychain oder Keystore, wie im obigen Fast SQL-Beispiel.
Fast SQL KeyValueStore oder data-storage-sqlite?
- Wähle Schnelles SQL
KeyValueStorewenn du typisierte Werte ohne manuelles JSON, binäre Werte, einen Runtime-Verschlüsselungsschlüssel oder bereits Fast SQL für Tabellen nutzt. - Wähle data-storage-sqlite wenn du seine Tabellen- und Filterfunktionen, JSON-Export und -Import oder Electron-Unterstützung benötigst.
Option 3: Keychain und Keystore für Secrets
Auth-Tokens, Refresh-Tokens, API-Keys und Datenbank-Verschlüsselungsschlüssel gehören in den sicheren Speicher der Plattform. @capgo/capacitor-native-biometric (8.6.0 und neuer) stellt eine generische Key-Value-API über dem iOS Keychain und Android Keystore bereit:
bun add @capgo/capacitor-native-biometric
bunx cap sync
import { AccessControl, NativeBiometric } from '@capgo/capacitor-native-biometric';
// Silent read and write
await NativeBiometric.setData({ key: 'session.refreshToken', value: token });
const { value: refreshToken } = await NativeBiometric.getData({ key: 'session.refreshToken' });
// Require Face ID / Touch ID / fingerprint to read
await NativeBiometric.setData({
key: 'wallet.pin',
value: pin,
accessControl: AccessControl.BIOMETRY_ANY,
});
const { value: secretPin } = await NativeBiometric.getSecureData({
key: 'wallet.pin',
reason: 'Confirm it is you',
});
await NativeBiometric.deleteData({ key: 'session.refreshToken' });
Halte die Werte klein. Das Plugin dokumentiert, dass die Android-Keystore-basierte Verschlüsselung am besten unter etwa 8 KB pro Wert funktioniert. Wenn du viel Daten schützen musst, speichere hier einen zufälligen Schlüssel und nutze ihn, um eine SQLite-Datenbank zu verschlüsseln. Unser Beitrag zu sicherem Speicher für Offline-Tokens geht tiefer auf die Token-Verarbeitung ein.
Denke daran, dass Keychain-Einträge auf iOS auch nach einer App-Deinstallation bestehen bleiben können. Beim ersten Start nach einer Neuinstallation alte Secrets bereinigen, falls deine App einen sauberen Zustand erwartet (einen „first run done“-Flag in Preferences speichern, der bei Deinstallation entfernt wird, und Keychain-Einträge löschen, wenn er fehlt).
Auswahl: Entscheidungsliste
- Ist es ein Geheimnis? Token, Passwort, PIN, Schlüssel. Verwende Keychain- oder Keystore-Speicher.
- Sind es nur wenige kleine Einstellungen? Verwende Preferences.
- Sind es viele Einträge, große JSONs, Binärdaten oder Caches, die du ablaufen lassen und in Gruppen leeren möchtest? Verwende einen SQLite-Key-Value-Store.
- Muss gefiltert, sortiert oder verbunden werden? Das ist nicht mehr Key-Value-Daten. Erstelle eine Tabelle, siehe die SQLite-Anleitung, oder verwende eine typisierte Schicht wie Kysely.
Bestehende Daten zwischen Stores migrieren
Wechsel von localStorage oder Preferences zu einem SQLite-Store ist eine einmalige Kopie beim Start. Führe sie aus, bevor der Rest der App Daten liest, und protokolliere, dass sie ausgeführt wurde.
import { Preferences } from '@capacitor/preferences';
import { KeyValueStore, type KeyValueValue } from '@capgo/capacitor-fast-sql';
export async function migrateToSqliteStore(): Promise<KeyValueStore> {
const kv = await KeyValueStore.open({ database: 'app', store: 'settings' });
if (await kv.has('__migrated_v1')) return kv;
const { keys } = await Preferences.keys();
for (const key of keys) {
const { value } = await Preferences.get({ key });
if (value === null) continue;
let parsed: KeyValueValue = value;
try {
parsed = JSON.parse(value) as KeyValueValue;
} catch {
// keep plain strings as strings
}
await kv.set(key, parsed);
}
// Also copy from localStorage if older versions used it
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && !(await kv.has(key))) {
await kv.set(key, localStorage.getItem(key));
}
}
await kv.set('__migrated_v1', true);
// Remove the old copies only after a release or two, once you are confident
return kv;
}
Entferne Secrets aus dem alten Store, sobald sie in den sicheren Speicher kopiert wurden. Wenn ein Token nach dem Verschieben in den Keychain in Preferences bleibt, besteht die ursprüngliche Gefährdung weiter.
Da dieser code in JavaScript läuft, kannst du ihn mit einem Capgo live updateausliefern. Das SQLite-Plugin selbst ist nativ, daher benötigt das Release, das es hinzufügt, einen Store-Build. Liefere zuerst das Plugin aus, dann die Migration code.
Fehlerbehebung
Preferences-Werte sind null nach einem Update im Web. Der Origin hat sich geändert (Port, Hostname oder Scheme), daher ist localStorage ein anderer, leerer Store.
Schnelles SQL KeyValueStore.open wirft auf iOS eine Exception. Der lokale HTTP-Channel ist blockiert. Füge NSAllowsLocalNetworking zu Info.plist hin und baue die native App neu.
get gibt in data-storage-sqlite eine String zurück, wo du ein Objekt erwartet hast. Dieses Plugin speichert nur Strings. JSON.parse den Wert.
Secrets fehlen nach dem Wiederherstellen eines Backups auf einem neuen Telefon. Keystore-Keys und gerätespezifische Keychain-Einträge werden nicht auf neue Hardware übertragen. Behandle sicheren Speicher als Cache für Credentials, die du durch erneutes Einloggen erneut beziehen kannst.
Einstellungen nach Reinstallation verloren, aber Tokens auf iOS noch vorhanden. Erwartet: Preferences werden mit der App gelöscht, Keychain-Einträge können verbleiben. Verwende das oben beschriebene First-Run-Flag-Muster.
Zusammenfassung
- Preferences: kleine, nicht sensible Einstellungen.
- SQLite-Key-Value-Store (Fast SQL
KeyValueStoreoder@capgo/capacitor-data-storage-sqlite): viele oder große Werte, Cache, Entwürfe, optional verschlüsselt. - Keychain oder Keystore (
@capgo/capacitor-native-biometricsetData): Secrets und Verschlüsselungsschlüssel.
Plugin-Referenzen: Fast SQL Docs, data-storage-sqlite-Dokumentation, und die Seite des nativen Biometrie-Plugins.