Web Storage
Web Storage lets a page save small amounts of data in the browser, so settings, drafts and preferences survive a page reload. localStorage keeps data until it's deleted; sessionStorage keeps it only for the current tab. Both are simple key–value stores of strings.
Saving and reading
localStorage.setItem("theme", "dark"); const theme = localStorage.getItem("theme"); // "dark" const missing = localStorage.getItem("nope"); // null localStorage.removeItem("theme"); localStorage.clear(); // removes everything for this site console.log(localStorage.length); // number of stored keys
| Method | Does |
|---|---|
setItem(key, value) | Saves a value (converted to a string) |
getItem(key) | Returns the string, or null if not found |
removeItem(key) | Deletes one key |
clear() | Deletes all keys for this origin |
key(i), length | Enumerate stored keys |
A remembered preference
Pick a colour, then press Reset on the editor (or reload the page) — the choice is remembered:
The try/catch blocks matter: storage can be unavailable — in some private-browsing modes, when the user blocks site data, or inside sandboxed frames — and accessing it then throws an error. Your page should still work without it.
Storing objects with JSON
Storage only holds strings. Storing an object directly saves the useless text "[object Object]", so convert with JSON (lesson 41):
const cart = [{ id: 1, qty: 2 }, { id: 7, qty: 1 }]; localStorage.setItem("cart", JSON.stringify(cart)); const restored = JSON.parse(localStorage.getItem("cart") ?? "[]"); console.log(restored[1].id); // 7
A small helper keeps this tidy and handles missing or corrupted data:
const store = { get(key, fallback) { 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)); return true; } catch { return false; // unavailable, or quota exceeded } }, }; const prefs = store.get("prefs", { fontSize: 16 }); store.set("prefs", { ...prefs, fontSize: 18 });
localStorage vs sessionStorage
localStorage | sessionStorage | |
|---|---|---|
| Lifetime | Until deleted by code or the user | Until the tab is closed |
| Shared between tabs? | Yes, across all tabs of the same site | No, each tab has its own |
| Good for | Theme, language, dismissed banners, drafts | Multi-step form progress, one-off state |
Both are scoped to the origin (protocol + domain + port), so one site can't read another's data.
Syncing between tabs
When localStorage changes in one tab, every other tab of the same site receives a storage event:
window.addEventListener("storage", event => { if (event.key === "theme") { document.documentElement.dataset.theme = event.newValue; } });
Limits and cautions
- Size: typically about 5 MB per origin. Writing beyond the limit throws a
QuotaExceededError. - Synchronous: reads and writes block the page briefly. Fine for small values; for large data use IndexedDB.
- Not secure: any script on the page can read it, including a malicious one injected through an XSS bug. Never store passwords, and think carefully before storing authentication tokens — HTTP-only cookies are safer for those.
- Not guaranteed: users clear site data, and browsers may evict storage for rarely visited sites.
Other storage options
| API | Use for |
|---|---|
| Cookies | Small values the server needs on every request, like session IDs |
| IndexedDB | Large or structured data, offline apps; asynchronous |
| Cache API | Storing network responses for offline use (with service workers) |