ESC

Type to search the knowledge base.

Browser Storage Comparison

Cookies, localStorage, sessionStorage, IndexedDB, Cache Storage — capacity, persistence, and when each is the wrong tool.

beginner3 min read
  • browser
  • storage
  • localStorage
  • indexeddb
  • cookies

Picking browser storage is an API choice and a security/product choice. Putting session tokens in localStorage “because it’s easy” is a classic XSS footgun. Using cookies for megabytes of offline data is the opposite mistake.

Docs: MDN Client-side storage, web.dev Storage.

Comparison table

API Typical size Persistence JS access Sent to server
Cookies ~4KB each Configurable Unless HttpOnly Yes (matching requests)
localStorage ~5MB / origin Until cleared Yes (sync) No
sessionStorage ~5MB / origin Tab/session Yes (sync) No
IndexedDB Large (quota) Until cleared Async No
Cache Storage Large (quota) Until cleared Async (Request/Response) No
Memory only RAM Page lifetime Yes No

Quotas and eviction policies vary by browser; treat “5MB” as a rule of thumb, not a contract.

Cookies

Best for small auth session identifiers the server must see.

Set-Cookie: session=abc; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=86400
  • HttpOnly — JS cannot read (mitigates token theft via XSS).
  • Secure — HTTPS only.
  • SameSite — CSRF posture (see Secure cookie flags).

Do not stuff JWTs with PII into giant cookies. Watch total header size.

localStorage / sessionStorage

localStorage.setItem('theme', 'dark');
const theme = localStorage.getItem('theme');

Sync API — large reads/writes on the main thread can jank. No structured clone of complex graphs beyond JSON. Any XSS can read it — never store long-lived access tokens here if you can use HttpOnly cookies instead.

sessionStorage is per-tab: good for wizard draft state that shouldn’t leap tabs.

IndexedDB

Async, transactional, large structured data (offline apps, drafts, media metadata).

// sketch with idb-style API mental model
const tx = db.transaction('notes', 'readwrite');
await tx.objectStore('notes').put({ id: '1', body: text });

Use for offline-first product data, not for session secrets.

Cache Storage

Part of the service worker ecosystem: store Request/Response pairs for offline shells and asset precache — Cache Storage API.

Decision cheat sheet

  1. Auth session for same-site API → HttpOnly + Secure + SameSite cookie.
  2. User preference (theme) → localStorage or cookie if SSR needs it.
  3. Multi-MB offline data → IndexedDB.
  4. App shell / offline assets → Cache Storage + SW.
  5. Ephemeral tab state → sessionStorage or memory.

Footguns

  • Storage is origin-scoped (scheme+host+port).
  • Private mode and user settings can clear data aggressively.
  • localStorage throws in some blocked-storage contexts — wrap writes.
  • Quota errors on IDB/Cache need user-facing handling.

Interview out-loud

“Cookies for small server-sent session IDs with HttpOnly Secure SameSite. localStorage for non-sensitive prefs. IndexedDB for large structured offline data. Cache Storage for Request/Response caching with service workers. Never put access tokens in localStorage if XSS is in your threat model.”

Security x storage matrix

Data Store
Session id HttpOnly cookie
Theme preference localStorage or cookie if SSR needs it
Offline drafts IndexedDB
Access token (prefer not) Memory only, short TTL
App shell assets Cache Storage

If product insists on “remember me” for months, the refresh capability must still be revocable server-side. Storage choice doesn’t replace server session hygiene.

Further reading

Related guides