Storage: localStorage to IndexedDB
What to use for what, cookie vs storage, persistence rules.
The browser gives you four real ways to remember data between page loads: localStorage, sessionStorage, cookies, and IndexedDB. Each has a sweet spot, and the wrong choice is a footgun (slow page, gone data, security leak). Let's sort them out.
localStorage: the simple one
// Strings only - JSON your way in and out
localStorage.setItem("theme", "dark");
localStorage.getItem("theme"); // "dark"
localStorage.removeItem("theme");
localStorage.clear(); // wipe everything
// Store objects via JSON
localStorage.setItem("user", JSON.stringify({ name: "Ada", id: 1 }));
const user = JSON.parse(localStorage.getItem("user") ?? "null");The big facts you have to know about localStorage:
- Per origin. Each site has its own bucket.
- ~5 MB limit in most browsers. Throws when full.
- Synchronous. Reads block the main thread. Avoid in tight loops or for large data.
- Strings only. Anything else is implicitly coerced.
JSON.stringifyit. - Persists forever until the user clears site data or your code deletes it.
sessionStorage: identical API, gone when the tab closes
sessionStorage.setItem("draft", "unsent message");
// Same methods as localStorage. Scoped to the tab.
// Closing the tab clears it. Refreshing keeps it.Same shape as localStorage but data dies with the tab. Great for unsent form drafts, multi-step wizards, or anything that shouldn't outlive the visit.
Cookies: the awkward grandparent
Cookies are key-value pairs the browser sends with every HTTP request to the same origin. That's their superpower and their downside:
// JS access (rarely what you want now)
document.cookie = "theme=dark; max-age=31536000; path=/; SameSite=Lax";
// Reading is gross - gives you "a=1; b=2; c=3"
const all = document.cookie;- ~4 KB per cookie. Very tight budget.
- Sent on every request. Bloats network traffic.
- Use
HttpOnlycookies (set by the server, invisible to JS) for auth tokens. That's their real job today. - Use
Secure+SameSiteattributes to protect against CSRF and leaks.
localStorage because cookies feel old. Any third-party script that runs on your page can read localStorage. HttpOnly cookies are invisible to JS by design. Use cookies for credentials.IndexedDB: real database in the browser
When you need megabytes (or gigabytes) of structured data, querying, indexes, and async access, that's IndexedDB. The native API is famously clunky, so most people use a tiny wrapper like idb-keyval or Dexie:
// Conceptual sketch (real code uses callbacks and events)
const db = await openDB("notes", 1, {
upgrade(db) { db.createObjectStore("items", { keyPath: "id" }); },
});
await db.put("items", { id: 1, title: "hi", body: "..." });
const item = await db.get("items", 1);
const all = await db.getAll("items");Use IndexedDB when you have offline support, file caching, large datasets, or anything beyond "remember a few preferences."
Decision flowchart
- A few KB of UI prefs that should persist? localStorage
- Data scoped to one tab/visit? sessionStorage
- Auth tokens or session IDs? HttpOnly cookie (server-set)
- Megabytes, structured, offline-capable? IndexedDB
- Server-side cache? Not the browser's job
A reusable localStorage helper
const store = {
get(key, fallback = null) {
try {
const raw = localStorage.getItem(key);
return raw === null ? fallback : JSON.parse(raw);
} catch {
return fallback;
}
},
set(key, value) {
try { localStorage.setItem(key, JSON.stringify(value)); }
catch (err) { console.warn("localStorage full or blocked:", err); }
},
remove(key) { localStorage.removeItem(key); },
};
store.set("user", { name: "Ada" });
store.get("user", {}); // { name: "Ada" }
store.get("missing", "default"); // "default"Try it: remember the name
A name input that remembers you across refreshes. Type a name, refresh the playground, watch it persist. Bonus: try emptying it and refreshing.
Quiz
Roughly how much can you store in localStorage per origin?
Recap
localStorage: 5 MB, synchronous, strings only, persists forever, per-origin.sessionStorage: same API, dies with the tab.- Cookies: tiny (4 KB), sent on every request, use
HttpOnlyfor auth. - IndexedDB: real database, async, big, structured. Use a wrapper.
- Never store auth tokens in
localStorage. XSS will steal them.