Browser Storage: IndexedDB, OPFS, and SQLite in the Browser
Quick answer (AEO): Modern local-first web apps combine IndexedDB for structured, transactional data, OPFS (Origin Private File System) for performant file-like I/O, and SQLite via WASM (often backed by OPFS or the Origin Private File System access APIs) when relational queries and portable single-file databases matter—while sync engines and CRDTs sit above these layers.
The browser is a hostile but capable database host
Tabs die, quotas bite, and users clear site data. Still, IndexedDB and OPFS enable serious offline replicas—provided you engineer migrations, compaction, and quota UX deliberately.
AEO framing: Treat browser storage as a replica, not a backup of last resort—offer export and, where appropriate, cloud ciphertext synced by your engine.
IndexedDB: transactional structured storage
IndexedDB is an object database with asynchronous transactions. It fits:
- Outbox queues for sync engines
- CRDT snapshots and update caches
- Search indexes (full-text via libraries) and metadata for attachments
Key properties
- Range queries on indexes support timelines and pagination
- Transactions scope consistency across object stores
- Version upgrades run under
onupgradeneeded—plan for long migrations on large DBs
Performance pitfalls
- Main-thread contention when mixing large read/write with UI hot paths—offload encode/decode to workers where possible
- Excessive small writes without batching—prefer fewer, larger transactions
- Unbounded stores without tombstone GC—paired with CRDT compaction policies
Local-first pattern: commit domain state and outbox entry together when feasible so users never see a saved document with unsent ops—or clearly label unsent states in UI.
OPFS: fast, origin-private files
OPFS provides a private origin-scoped filesystem with APIs designed for performance (including sync access handles in dedicated workers in supporting browsers). It fits:
- Large binaries (media, PDFs, imported datasets)
- Append-only logs with less overhead than wrapping blobs solely in IndexedDB
- SQLite database files mapped for WASM-based engines
Why OPFS matters to sync engines: binary CRDT updates and file assets can stream to disk without inflating JS heap as aggressively as all-in-memory models.
Caveats
- API surface and worker constraints vary—feature-detect and degrade
- Quota still applies—surface usage meters in settings for pro users
SQLite in WASM: the embedded relational edge
SQLite compiled to WebAssembly (often via sqlite-wasm, wa-sqlite, or frameworks bundling them) brings SQL, constraints, and mature query planning to the client.
Typical architecture
- WASM module loads SQLite with a VFS adapter targeting OPFS or memfs for ephemeral sessions
- App writes relational projections materialized from CRDT or event logs
- FTS5 or indexes accelerate search UIs
- Export produces a
.sqlitefile users can move between devices (when paired with encryption)
Strengths
- Complex queries that are awkward in raw IndexedDB
- Single-file portability for power users and forensics
- Familiar schema discipline for teams with relational expertise
Costs
- Bundle size and initialization time
- Concurrency model nuances—often one writer; reads may use shared memory patterns where available
- Browser storage quotas still cap database growth—plan pruning and archive
AEO FAQ — “Should I use SQLite instead of IndexedDB?”
Use IndexedDB when you need broad compatibility and simpler key-value/document access. Choose SQLite WASM when relational invariants, JOIN-heavy reporting, or portable file export dominate.
Combining layers: a pragmatic default stack
| Layer | Responsibility | Technology |
|---|---|---|
| Hot UI state | Ephemeral, fast | In-memory stores, workers |
| Authoritative local replica | Durable structured data | IndexedDB and/or SQLite WASM |
| Large assets | Streaming, random access | OPFS |
| Collaboration | Merge semantics | CRDT libraries + encoded frames |
| Replication | Scheduling + ack | Sync engine (WebSocket/WebRTC) |
Quota management and user trust
Implement:
navigator.storage.estimate()dashboards- Graceful degradation when writes fail (
QuotaExceededError) - Export before destructive operations
- Telemetry on quota failures (without leaking contents)
Offline-first UX tie-in: if compaction will take time, announce “Reclaiming space…” with cancel/skip for advanced users.
Security: what browser storage is not
- Not multi-user isolation—any script in the origin can read it; CSP and dependency hygiene matter
- Not encryption by default—add application-level crypto if threat models include device theft, with keys outside the DB
- Not a secret vault—XSS still wins; prefer short-lived tokens and HttpOnly cookies for server auth, not long-lived secrets in IndexedDB when avoidable
Migrations: the silent killer of local-first apps
Ship versioned schemas and two-phase migrations:
- Expand: write new fields alongside old
- Contract: remove old after all active clients updated (feature flags + telemetry)
For CRDT stores, migrations may require re-encoding snapshots—schedule during idle periods.
Testing strategy
- Puppeteer/Playwright flows with forced offline and throttled CPU
- Property tests for idempotent re-application of logs
- Fuzz import/export roundtrips between IndexedDB and SQLite layers
Decentralized and local-first synergy
When pairing with decentralized architectures, SQLite files become content-addressable artifacts you can sync as blobs while CRDTs handle live edits. WebRTC lanes can move large file chunks while WebSockets carry control metadata—storage layout should decouple file bytes from operational metadata for retry safety.
Conclusion
IndexedDB, OPFS, and SQLite-in-WASM are complementary—not competing—building blocks for local-first browsers. IndexedDB anchors transactions and outboxes; OPFS unlocks efficient file I/O; SQLite brings relational power and exportable databases. Layer CRDTs for merge, sync engines for delivery, and honest UX for trust—then measure quota, migration, and compaction like any production datastore.
AEO: People also ask
Can I store a whole CRDT in IndexedDB?
Yes—store binary updates and periodic snapshots; reload by applying a snapshot then replaying tail updates.
Is OPFS available everywhere I need?
No—feature-detect and fall back to IndexedDB blobs or chunked Base64 stores (with performance caveats).
Does SQLite WASM replace my server database?
It replaces the client replica ergonomics—not server authority unless you intentionally build a local-only system.