Storage everywhere: comparing on-device and cloud storage on iOS, Android, and Web in 2026
A practical comparison of every meaningful way to store data in 2026 – across iOS, Android, and the Web – covering on-device system APIs, popular third-party libraries, and the cloud backends and sync engines that tie them together.
Glossary
Acronyms you’ll see throughout this post:
| Acronym | Full name | What it means |
|---|---|---|
| KV | Key-Value (store) | A simple string -> value map (preferences, flags) |
| ORM | Object-Relational Mapper | Library that maps DB rows to objects (Core Data, Room, GRDB) |
| BaaS | Backend-as-a-Service | Hosted backend bundle: DB + auth + storage + functions (Firebase, Supabase) |
| DBaaS | Database-as-a-Service | Hosted database only (Neon, Turso, PlanetScale) |
| RLS | Row-Level Security | DB-side per-row access rules (Postgres feature, Supabase relies on it) |
| CRDT | Conflict-Free Replicated Data Type | Data structure that merges concurrent edits without conflicts (Yjs, Automerge) |
| OPFS | Origin Private File System | Per-origin sandboxed file system in browsers; substrate for SQLite-WASM |
| WASM | WebAssembly | Compiled binary format that runs in browsers (and elsewhere) |
| SAF | Storage Access Framework | Android’s user-driven file picker for files outside the app sandbox |
| CHIPS | Cookies Having Independent Partitioned State | Per-top-site cookie partitioning, Chrome’s third-party-cookie successor |
| PWA | Progressive Web App | Installable web app with offline support via Service Workers |
| KMP | Kotlin Multiplatform | Sharing Kotlin code across Android/iOS/JVM/JS |
| MAU | Monthly Active Users | Common pricing unit for sync/collab services |
| EOL | End-of-Life | Software no longer maintained; migrate off |
The landscape
Storage is not one decision – it’s at least four, often layered:
| Layer | Question | Examples |
|---|---|---|
| Preferences / KV | Where do tiny settings live? | UserDefaults, DataStore, localStorage |
| Secrets | Where do tokens live? | Keychain, Keystore, HttpOnly cookies |
| Structured data | Where does the app’s domain model live? | Core Data, Room, IndexedDB, SQLite |
| Cloud / sync | How does it move between devices? | CloudKit, Firestore, Supabase, Replicache |
The big shift in 2026 is that the structured-data and cloud-sync layers are merging. Local-first sync engines (PowerSync, Triplit, Replicache, ElectricSQL) treat the on-device DB as the source of truth and let the network catch up. SQLite – compiled to WASM, mirrored to the edge, or embedded everywhere – is the substrate underneath most of them.
iOS
System (Apple-provided)
| API | What it is | Use case | Watch out for |
|---|---|---|---|
| UserDefaults | Plist KV via cfprefsd | Preferences, flags, last-selected tab | Hard ~4 MB cap on App Group suites; not for blobs |
| Keychain | Encrypted store, Secure Enclave-backed | API keys, OAuth tokens, biometric secrets | Awful C API – always wrap; never put in exports |
| FileManager / sandbox | Per-app Documents/, Library/, tmp/ |
User docs, cached blobs, Live Photos | App Group containers for sharing with widgets/extensions |
| Core Data | Object graph over SQLite | Complex models, lightweight + heavyweight migrations | NSPersistentStoreDescription.configuration is a model lookup, not a label – setting a bogus name fails store load |
| SwiftData | Macro-driven @Model persistence (iOS 17+) |
New apps, simple-to-moderate schemas | Still maturing – no merge policy API, no runtime CloudKit reconfiguration; many teams have rolled back to Core Data |
| NSPersistentCloudKitContainer | Core Data with automatic CloudKit mirroring | Family Sharing, multi-device sync without writing CloudKit code | Requires UIBackgroundModes -> remote-notification AND registerForRemoteNotifications(); debounce remote-change notifications – they republish 60+ times during catchup |
| CloudKit (direct) | CKRecord/CKAsset against private/shared/public DBs |
Public datasets, custom sync logic | Manual asset cleanup – cascade deletes don’t remove CKAssets |
| iCloud Documents | NSMetadataQuery document sync into Files.app |
Pages-style document apps | Niche in 2026; most apps use CloudKit + custom UI |
| App Group containers | Shared sandbox for app + extensions | Widgets, share extensions, intents | Same 4 MB UserDefaults cap; use file URLs for blobs |
| Plain SQLite (libsqlite3) | Direct C API | Basically never anymore | Use GRDB or SQLite.swift instead |
Third-party (iOS)
| Library | What it is | Use case | Trade-offs |
|---|---|---|---|
| GRDB | Swift-first SQLite wrapper | The default modern alternative to Core Data/SwiftData | Best-in-class docs, type-safe, reactive observers, FTS5, SQLCipher; you write your own sync |
| SQLite.swift | Type-safe query builder over SQLite | Lightweight schemas | Less feature-rich than GRDB |
| Realm | Object DB with optional Atlas sync | Local Realm still works fine | Atlas Device Sync sunset September 2025 – new projects should not adopt it |
| FMDB | Objective-C SQLite wrapper | Legacy Obj-C codebases only | Maintenance mode |
| Firestore | Google NoSQL with offline cache | Cross-platform iOS+Android+web apps | ~5 MB SDK weight; pricing scales with reads |
| Supabase Swift | Postgres BaaS client | Teams that want SQL + self-hostable escape | Less polished than the JS SDK; offline story is DIY |
iOS quick picks (2026)
- Settings/flags -> UserDefaults (under 4 MB)
- Secrets -> Keychain
- Local-only relational -> GRDB (modern) or Core Data (mature)
- CloudKit sync -> Core Data +
NSPersistentCloudKitContainer. SwiftData still has merge-policy gaps - Cross-platform backend -> Supabase (SQL) or Firestore (NoSQL)
Android
System (Google-provided)
| API | What it is | Use case | Watch out for |
|---|---|---|---|
| SharedPreferences | XML-backed sync KV | Tiny prefs | Effectively legacy – main-thread I/O, no transactions |
| DataStore | Coroutine/Flow KV (Preferences) or schema-typed (Proto) | New code’s KV layer | Async-only; not a queryable DB |
| EncryptedSharedPreferences | KV with at-rest encryption | – | Deprecated April 2025, no first-party successor. Roll your own with Keystore + Tink |
Internal storage (filesDir, cacheDir) |
App-private sandbox | Caches, DB files, downloaded models | cacheDir is reclaimed without notice |
| Scoped storage + MediaStore | Shared media via collection APIs | Photos, videos, downloads | Migration pain is the #1 Android storage complaint – WRITE_EXTERNAL_STORAGE is a no-op on API 30+ |
| Storage Access Framework (SAF) | System file picker returning persistable URIs | Cloud providers, USB, SD card | UX friction – every read goes through a picker |
| Raw SQLite | SQLiteOpenHelper |
Almost never anymore | Use Room |
| Room | Jetpack ORM with codegen, now KMP | Relational app data, offline cache | Don’t wrap suspend DAOs in withContext(Dispatchers.IO) – Room manages its own dispatcher |
| ContentProvider | Cross-process data interface | FileProvider for sharing, system providers |
Overkill for in-app use |
| Android Keystore | Hardware-backed key container | Wrapping symmetric keys, biometric-gated keys | Keys can be invalidated on biometric enrollment changes |
| Credential Manager | Unified passkey/password/federated auth | All new auth flows | Requires Play Services – fallback needed on Huawei, etc. BiometricPrompt still used for in-app gating |
| Auto Backup | Free 25 MB cloud backup to user’s Drive | Restore-on-reinstall | Exclude secrets via backup_rules.xml |
Third-party (Android)
| Library | What it is | Use case | Trade-offs |
|---|---|---|---|
| Realm Kotlin | Live-object DB | KMP, offline-capable | Atlas sync deprecated – local only |
| ObjectBox | NoSQL object DB | Fastest benchmarks, vector search | Smaller community, commercial sync |
| SQLDelight | Codegen from .sq files |
KMP-first, type-safe | More boilerplate than Room; no auto-migrations |
| Supabase Kotlin | Postgres BaaS client | SQL + self-hostable | Realtime less mature than Firestore |
| AWS Amplify | DataStore + Auth + Storage | Deep AWS integration | Heavy SDK, AWS lock-in |
| PowerSync | SQLite-based sync over Postgres/MongoDB/MySQL (SQL Server in alpha) | Offline-first, BYO backend | Newer, smaller ecosystem |
Major Android shifts to call out
- SharedPreferences -> DataStore is now the official path for new code
- Scoped storage is fully enforced;
MANAGE_EXTERNAL_STORAGEis policy-restricted on Play - Credential Manager consolidates passkeys, passwords, federated sign-in
- EncryptedSharedPreferences has no first-party successor – teams roll their own
- Room is KMP since the stable 2.7.0 release in April 2025, narrowing SQLDelight’s main differentiator
Web
Standards-based
| API | What it is | Use case | Watch out for |
|---|---|---|---|
| Cookies | ~4 KB/cookie, sent on every request | Auth tokens, CSRF, sessions | SameSite=Lax default, HttpOnly/Secure for auth, CHIPS (Partitioned) for cross-site embeds |
| localStorage | Sync string KV, ~5-10 MB | Tiny prefs, flags | Blocks main thread; Safari ITP evicts after 7 days without user engagement |
| sessionStorage | Same API, tab-scoped | Wizard state | Lost on tab close |
| IndexedDB | Async transactional structured store | The real browser DB | Verbose API; quotas in 100s of MB to GB |
| Cache API | Request/Response storage in Service Workers | PWA offline shells | Counts against same origin quota |
| Storage API | navigator.storage.estimate(), persist() |
Resist eviction | Always call persist() for local-first apps |
| OPFS | Per-origin sandboxed file system, sync access in workers | Substrate for SQLite-WASM | Basic API universal since 2023 (Chrome 102, Safari 15.2, Firefox 111); FileSystemSyncAccessHandle (the perf-critical sync API) lagged in Firefox |
| File System Access API | showOpenFilePicker, etc. |
Real user-visible files | Chromium-only in 2026 |
| WebSQL | – | – | Removed. Deprecated in Chromium 119 (Nov 2023), gone everywhere by early 2024 |
| Storage Buckets API | Per-bucket eviction policies | Future-proofing | Chrome-stable; Firefox positive signal but not shipped; Safari has no signal |
Library / wrapper layer
| Library | What it is | Use case | Trade-offs |
|---|---|---|---|
| localForage | Promise KV with IDB fallback | Drop-in localStorage upgrade | Mature, low-churn |
| idb | Thin promise wrapper over IDB | Raw IDB without ceremony | Default when you don’t need an ORM |
| Dexie.js | IDB ORM with queries, hooks, live queries | Most popular IDB layer | Sync addon available |
| PouchDB | CouchDB-compatible, master-master sync | Apps with CouchDB backend | Feels dated |
| RxDB | Reactive schema-based DB, multiple adapters | Offline-first apps | Strong 2026 choice; multiple storage backends including OPFS-SQLite |
| WatermelonDB | Lazy-loaded reactive DB | React Native + web | RN-first |
| TanStack Query / SWR | In-memory server cache | Server-state caching | Optional persistQueryClient to IDB |
| Apollo Client / Relay | GraphQL normalized stores | GraphQL apps | apollo3-cache-persist for persistence |
| Zustand persist / redux-persist | State persistence middleware | Mirror store to localStorage/IDB | Trivial to add |
| Yjs | CRDT library | Collaborative text/structured docs | Default CRDT pick; tiny, fast, huge ecosystem |
| Automerge | JSON-shaped CRDT | Richer document model | v3 (Rust core) closed perf gaps with Yjs |
| sqlite-wasm | Official SQLite WASM build with OPFS VFS | Serious local-first apps | Near-native perf in modern browsers |
| Drizzle (SQLite-WASM adapter) | Typed SQL ORM | TypeScript SQL in the browser | Pairs with sqlite-wasm |
Sync engines (where 2026 actually moved)
| Engine | What it is | Trade-offs |
|---|---|---|
| Replicache / Reflect | Push-pull mutator protocol; you bring the backend | Battle-tested, commercial license, free under $200K ARR |
| ElectricSQL | Postgres <-> local SQLite. Pivoted 2024 to read-only “Electric Next” sync; writes are app-owned now | OSS Apache-2.0 |
| PowerSync | Postgres/MongoDB/MySQL/SQL Server (alpha) <-> on-device SQLite | Production-grade, strong mobile SDKs, commercial with free tier |
| Triplit | TS-first sync engine + relational DB | Momentum among indie web devs; OSS AGPL/commercial |
| InstantDB | Firebase-shaped client-side reactive DB | Triple-store model; web/RN |
| Liveblocks | Presence + collab document primitives | Per-MAU pricing |
| Yjs + Hocuspocus | CRDT + production-grade server | Open-source collab stack |
Cross-platform cloud
These are the cloud backends that work across iOS, Android, and Web – the ones you reach for when “where does this sync to?” matters more than “what platform am I on?”
Backend-as-a-Service (BaaS)
| Service | Stack | Best for | Trade-offs |
|---|---|---|---|
| Firebase (Firestore/RTDB/Storage/Auth) | Google NoSQL + auth + blob | Default for hobbyists and indie mobile | Generous free tier, vendor lock-in, costs scale unpredictably with reads |
| Supabase | Postgres + PostgREST + Realtime + Auth + Storage | Open-source Firebase alternative; SQL-first | Real Postgres, RLS, self-hostable; mobile SDKs less polished than web |
| Convex | TS-first reactive DB | End-to-end TypeScript apps | Live queries free, transactional; mobile-native is second-class |
| Appwrite | Self-hostable BaaS | Teams wanting full control | Smaller community than Supabase |
| PocketBase | Single Go binary, embedded SQLite | Solo devs, side projects | Single-node by design; not for high-scale |
| AWS Amplify | DataStore + AppSync + S3 | AWS-committed shops | Notoriously complex DX; conflict-resolution model has burned teams |
| Parse Platform | Legacy Facebook-era BaaS | Maintenance-mode apps | Community-maintained |
Object / blob storage (S3-compatible is the lingua franca)
| Service | Notable | Why pick it |
|---|---|---|
| Cloudflare R2 | Zero egress fees | New default for serving user content cheaply |
| Backblaze B2 | Cheapest per-GB-stored | Free egress via Bandwidth Alliance with Cloudflare |
| AWS S3 | The reference | Pick when you have a reason to pay AWS egress |
| Google Cloud Storage | Integrates with GCP/Firebase | Same shape as S3, similar pricing |
| Azure Blob Storage | Microsoft equivalent | Pick if you’re already in Azure |
| Wasabi | Flat-rate, no egress fees | Backups, media archives |
Database-as-a-Service
| Service | What it is | Status |
|---|---|---|
| Neon | Serverless Postgres with branching, scale-to-zero | Generous free tier, healthy |
| Turso | libSQL (SQLite fork) at the edge | Insanely fast reads, cheap; OSS |
| Cloudflare D1 | SQLite at the edge inside Workers | Tightly coupled to CF stack |
| MongoDB Atlas | Hosted Mongo | Mature, expensive at scale |
| PlanetScale | Vitess-backed MySQL with branching | Killed free tier in 2024; paid-only |
| Upstash | Serverless Redis/Kafka/QStash | Best for caches/queues, not primary store |
| Fauna | Serverless distributed DB with FQL | Public service shut down May 2025 – effectively dead for indie devs |
Deep dive: mobile clients
The previous tables list the options. This section answers the practical questions: what does the code actually look like? how does sync actually work? what are the failure modes?
Persistence: same model, three iOS implementations
Imagine a Pet entity with id, name, species. Here’s the same fetch in each major option.
Core Data (object graph, mature, verbose):
1 | let request = Pet.fetchRequest() |
SwiftData (concise, but still maturing):
1 | class Pet { |
GRDB (SQL-first, type-safe via Codable):
1 | struct Pet: Codable, FetchableRecord, PersistableRecord { |
The trade-off triangle: Core Data is the most powerful but the most verbose; SwiftData is the most concise but has gaps (no NSMergePolicy equivalent, awkward CloudKit reconfiguration); GRDB gives you full SQL with Swift ergonomics but you write your own sync.
Persistence: same model, two Android implementations
Room (JetPack ORM, the default):
1 | data class Pet( |
SQLDelight (codegen from .sq files, KMP-friendly):
1 | -- src/commonMain/sqldelight/Pet.sq |
1 | val dogs: Flow<List<Pet>> = database.petQueries.selectDogs().asFlow().mapToList() |
Room won the Android-only fight thanks to deeper Jetpack integration; SQLDelight’s edge is sharing the schema with iOS in KMP projects (and Room’s KMP support, while now stable, still feels less mature for non-Android targets).
Sync: three architectural shapes
What “sync” actually means changes a lot depending on which model you pick.
1. Apple’s CloudKit model (NSPersistentCloudKitContainer)
1 | [Device A: Core Data] <-> [iCloud Private DB] <-> [Device B: Core Data] |
- Mirroring is automatic; you keep using normal
NSManagedObjectAPIs. - Conflict resolution is last-writer-wins with no override (unlike Core Data’s standalone
NSMergePolicy). - Free for end users, no backend cost for you, Apple-only.
- Real gotcha:
NSPersistentStoreRemoteChangerepublishes 60+ times during boot catchup – debounce any whole-store consumer (Spotlight reindex, exports), or you’ll churn pointlessly.
2. Firebase Firestore model
1 | [Device A: Firestore SDK + IDB cache] ─┐ |
- Offline cache is LRU, ~100 MB by default on mobile; lossy across logout/reinstall.
- Server-authoritative – the cloud is truth, the device is a cache.
- Pricing scales with read count, not data size: a per-document listener that fires on every change can be expensive at scale.
3. PowerSync / Triplit / Replicache (local-first)
1 | [Device A: SQLite (truth)] [Device B: SQLite (truth)] |
- Local SQLite is the source of truth – the UI never waits on the network.
- Mutations are queued locally and uploaded asynchronously.
- The server applies them, then broadcasts deltas to other clients.
- Conflict resolution is the engine’s responsibility (CRDT-flavored or transactional, depending).
- The trade: setup complexity. You’re operating a sync server (or paying someone to).
Secret storage: what to actually use
| Platform | Primitive | Practical pattern |
|---|---|---|
| iOS | Keychain | Wrap the C API (KeychainHelper); kSecAttrAccessControl for biometric-gated tokens; never put secrets in UserDefaults or exports |
| Android | Keystore | Keystore stores keys, not arbitrary secrets. Generate an AES-GCM key with KeyGenParameterSpec.Builder.setUserAuthenticationRequired(true), encrypt the secret, store ciphertext in DataStore or a file |
| Both | Tink | Google’s crypto library; the recommended successor for the deprecated EncryptedSharedPreferences. Wraps Keystore-backed AES-GCM with sensible defaults |
iOS Keychain wrapper sketch:
1 | SecItemAdd([ |
Android Keystore + Tink sketch:
1 | val aead = AeadConfig.register() |
Schema migrations: what happens when the model changes
| Stack | Migration model | Reality check |
|---|---|---|
| Core Data | Lightweight (auto) for additive changes; NSMappingModel for everything else |
Lightweight handles most real-world changes; heavyweight is painful but works |
| SwiftData | VersionedSchema + SchemaMigrationPlan with MigrationStage.lightweight / .custom |
Newer, fewer war stories; large schemas have hit edge cases |
| Room | @Database(version = N) + named Migration(from, to) with raw SQL |
Predictable; you write SQL by hand. Auto-migrations exist but only for trivial changes |
| GRDB | DatabaseMigrator with named, ordered, idempotent steps |
Best-in-class – migrations are first-class citizens, easy to test |
| Realm | migrationBlock with old/new schema versions |
Rigid; missing-field migrations require manual handling |
Binary footprint reality check
| SDK | Approximate APK / IPA cost |
|---|---|
| GRDB | ~800 KB |
| SQLDelight | <1 MB |
| Realm Kotlin | 3-4 MB |
| ObjectBox | 1-2 MB |
| Firebase Firestore (modular) | 4-6 MB |
| AWS Amplify | 5+ MB |
| Room / DataStore | bundled with Jetpack (negligible) |
| Core Data / SwiftData / UserDefaults / Keychain | system-provided (zero) |
Footprint matters more than people remember on Android, where Play store size limits and download conversion rates put pressure on every megabyte.
Deep dive: web
The web stack changed more than mobile in the last two years. Here’s what the local-first architecture actually looks like, and the patterns that come with it.
The local-first stack, in one diagram
1 | [React / Svelte / etc.] |
Three things to internalise:
- The local DB is the truth. UI reads and writes it directly; queries are live (re-fire on changes).
- Sync is asynchronous. Mutations are queued locally; the engine handles upload, conflict resolution, and downstream rebroadcast.
- The server is for durability and fan-out. You can lose the network for hours and the app keeps working.
sqlite-wasm + OPFS, the modern substrate
The official SQLite WASM build runs in a Web Worker and stores its database file in OPFS. With FileSystemSyncAccessHandle, perf is within shouting distance of native SQLite.
1 | import sqlite3InitModule from '@sqlite.org/sqlite-wasm' |
Layer Drizzle on top for typed queries and you have the same DX as a server SQL ORM, all in the browser.
IndexedDB without the pain
If you don’t need SQL, IndexedDB is still fine – but use a wrapper. Raw IDB looks like 1990s C; Dexie reads like a 2020s ORM.
1 | // Dexie |
Dexie’s “live queries” (useLiveQuery for React, etc.) re-run automatically when the underlying data changes – the same pattern that local-first sync engines popularised.
Collaboration: Yjs in twelve lines
CRDTs sound exotic until you see how little code they take. Here’s a shared text doc that syncs over WebSocket and persists to IndexedDB:
1 | import * as Y from 'yjs' |
That’s the entire collaborative-editing primitive. Bind it to a textarea or a Prosemirror/Tiptap/Monaco binding, plug in a Hocuspocus server, and you have Google Docs-shaped collab.
The persistence dance: never assume your data sticks
Browsers can evict your data under storage pressure. The contract you actually want is:
1 | // On app boot |
- Chrome auto-grants
persist()based on engagement signals (bookmarks, install, frequent use). - Safari / Firefox require explicit user action – typically a PWA install prompt.
- Treat unpersisted storage as a cache, never as a database. Local-first apps that don’t request
persist()will lose user data on memory-pressured devices.
The Service Worker + Cache API offline shell
For PWAs and offline-capable web apps, the durable pattern is cache-first for shell, network-first for data.
1 | // sw.js -- runs in service worker |
The Cache API is one of the great unsung wins of the modern web. It’s a Request->Response store, scoped to the origin, accessible from both the page and the service worker, with no eviction policy beyond the global storage quota.
Sync engine model in pseudocode
What does a sync engine like Replicache or Triplit actually do? At its core:
1 | on user action: |
The clever part is the push log and the cookie: both client and server agree on a position in the change history, so reconnects are cheap and ordering is preserved. You write mutator functions once on the client and once on the server – everything else falls out.
When to pick which web storage
| Need | Pick |
|---|---|
| Tiny prefs, sync access, no async hassle | localStorage (under 1 MB; expect Safari ITP eviction) |
| Auth tokens | HttpOnly+Secure cookie (server session) or in-memory + refresh token in IDB |
| Structured data, no SQL needed | Dexie over IndexedDB |
| Real SQL, large datasets | sqlite-wasm + OPFS (+ Drizzle for typed queries) |
| Sharing with other apps / files | File System Access API (Chromium) – otherwise download fallback |
| Offline shell for PWA | Cache API + Service Worker |
| Collaboration / multi-user | Yjs (text-shaped) or Automerge (JSON-shaped) |
| Offline-first with server backend | Triplit, Replicache, PowerSync, or ElectricSQL (read-only sync) |
Master comparison matrix
A wide-angle view of major options, sorted roughly by category. “Sync” means built-in cross-device sync without you writing it.
| Option | Platform(s) | Type | Sync | OSS | Best for |
|---|---|---|---|---|---|
| UserDefaults | iOS | KV | iCloud KV opt-in | – | Tiny prefs |
| Keychain | iOS | Secrets | iCloud opt-in | – | Tokens, secrets |
| Core Data | iOS | ORM/SQLite | via CloudKit | – | Mature relational data |
| SwiftData | iOS | ORM/SQLite | via CloudKit (limited) | – | New simple schemas |
| GRDB | iOS | SQLite | No | OSS | Modern SQLite control |
| Realm (local) | iOS/Android | Object DB | Sync sunset | OSS | Existing Realm apps |
| DataStore | Android | KV | No | OSS | New KV layer |
| Room | Android (KMP) | ORM/SQLite | No | OSS | Default Android relational |
| SQLDelight | Android (KMP) | SQLite codegen | No | OSS | KMP-shared schemas |
| ObjectBox | Android | Object DB | Commercial | Mixed | Performance-critical |
| localStorage | Web | KV (string) | No | – | Tiny prefs |
| IndexedDB | Web | Async DB | No | – | Real browser DB |
| OPFS | Web | File system | No | – | SQLite-WASM substrate |
| Dexie.js | Web | IDB ORM | Optional addon | OSS | Most popular IDB layer |
| RxDB | Web | Reactive DB | Pluggable | OSS | Offline-first |
| sqlite-wasm | Web | SQL DB | No | OSS | Serious local-first |
| Yjs | Web (any) | CRDT | via transports | OSS | Collaborative editing |
| Firebase | iOS/Android/Web | NoSQL BaaS | Yes | Mixed | Indie cross-platform |
| Supabase | iOS/Android/Web | Postgres BaaS | Yes (Realtime) | OSS | SQL + self-host escape |
| Convex | Web (mobile via REST) | TS reactive DB | Yes | Source-available | TS apps |
| PocketBase | All (REST) | Embedded SQLite BaaS | Yes (Realtime) | OSS | Solo devs |
| AWS Amplify | All | AWS BaaS | Yes | OSS clients | AWS shops |
| Replicache | Web/RN | Sync engine | Yes | Commercial | Bring-your-own backend |
| PowerSync | iOS/Android/Web | Sync engine | Yes | Commercial | Postgres/Mongo offline |
| Triplit | Web/RN | DB + sync | Yes | OSS/AGPL | TS-first local-first |
| ElectricSQL | Web | Read-only sync | Yes (read) | OSS | Postgres -> SQLite stream |
| CloudKit | iOS/macOS | Cloud DB + assets | Yes | – | Apple-only sync |
| R2 / S3 / B2 | All | Object storage | – | Mixed | Blobs, user uploads |
| Neon / Turso / D1 | All (server) | DBaaS | – | Mixed | Cloud Postgres/SQLite |
2026 trends worth calling out
- Local-first is mainstream. SQLite-WASM + OPFS + a sync engine on the web; PowerSync/Triplit/Replicache on mobile. “Online-only Firestore listeners” feels dated for serious apps.
- Realm Atlas Device Sync is dead (sunset September 2025). PowerSync, Triplit, and ElectricSQL absorbed the migration.
- Fauna shut down, PlanetScale killed its free tier – trust in pure DBaaS plays cooled, and indie devs migrated to Neon, Turso, and self-hostable options.
- R2’s zero-egress reshaped object-storage economics. S3 is now the “you have a reason” choice.
- SQLite at the edge (Turso, D1, embedded libSQL) replaced “serverless Postgres” hype for read-heavy workloads.
- Cookies retreated to auth-only. Third-party cookies are dead in Chrome; CHIPS handles legitimate cross-site embeds. App state lives in IDB or HttpOnly server sessions.
- SwiftData still has gaps. Many production iOS teams that adopted it in 2023-2024 quietly migrated back to Core Data +
NSPersistentCloudKitContainerfor CloudKit-heavy apps – the merge-policy gap and runtime-reconfiguration limitation hurt. - Android’s encrypted-prefs successor is missing. Jetpack Security was deprecated without a replacement; teams roll their own with Keystore + Tink.
- Always call
persist()on the web. Treat unpersisted browser storage as cache, not truth – Safari especially evicts aggressively without explicit persistence. - Convex / Triplit / Instant define the TS-app default; Firebase still wins hobbyist mobile.
How to choose: quick picks
“I’m building a small iOS app for myself”
- Settings: UserDefaults
- Tokens: Keychain
- Data: GRDB or Core Data +
NSPersistentCloudKitContainerif you want sync - Backend: probably none – CloudKit is free
“I’m building cross-platform iOS + Android + Web”
- Hobbyist / fastest bring-up: Firebase. Still the default, all SDKs work, generous free tier
- SQL + self-host escape hatch: Supabase. Real Postgres, RLS, OSS
- TS-end-to-end web app: Convex. Best DX in the class
- You want SQLite locally + sync: PowerSync (mature, commercial) or Triplit (OSS, momentum)
“I need offline-first with real conflict resolution”
- Mobile-heavy: PowerSync over Postgres/Mongo
- Web-heavy: Replicache, Triplit, or sqlite-wasm + ElectricSQL
- Collaborative document editing: Yjs + Hocuspocus
“I’m uploading user photos / files”
- Object storage: Cloudflare R2 (zero egress) or Backblaze B2
- For an end-to-end bundle including auth: Supabase Storage or Firebase Storage
“I just need a database in the cloud”
- Postgres: Neon (serverless, branching)
- SQLite-at-the-edge: Turso or Cloudflare D1
- Avoid: Fauna (dead), PlanetScale (no free tier), pure Realm sync (sunset)
“I’m building a side project with a single binary backend”
- PocketBase. SQLite + auth + realtime + admin UI in one Go binary
“I’m in a regulated environment that needs self-hosting”
- Supabase, Appwrite, or PocketBase. All three self-host cleanly.
The honest summary: storage in 2026 is layered, and the right answer is usually two or three of these together – a system KV for prefs, a system or third-party DB for structured data, and a cloud sync engine that treats the device as the source of truth. The exciting movement is in that last layer; the on-device APIs have mostly stabilized.