What is Local Storage?
The browser's synchronous, per-origin key-value store (window.localStorage) that keeps string data until it is explicitly cleared.
Definition
localStorage is part of the Web Storage API defined in the WHATWG HTML standard. It exposes a simple object per origin (scheme, host, and port) with setItem, getItem, removeItem, clear, key, and length. Data persists across tabs, browser restarts, and reboots until code or the user removes it, unlike sessionStorage, which is scoped to a single tab and discarded when it closes. Keys and values are always strings, so objects must be serialized with JSON.stringify and parsed back. According to MDN, browsers allow up to 5 MiB of localStorage per origin; writes beyond that throw a QuotaExceededError, which code should catch. All operations are synchronous and run on the main thread, so reading or writing large values can block rendering. When one tab changes a value, other tabs on the same origin receive a storage event, a lightweight way to sync state such as logout across tabs. Storage is not available in Web Workers or service workers. Because any script running on the page can read localStorage, it offers no protection against cross-site scripting. A single XSS bug or compromised third-party script can copy everything stored there, which is why security guidance discourages keeping session tokens or personal data in it. In private browsing modes, and when browsers clear site data or apply storage partitioning for embedded third-party frames, data may be lost or isolated. For larger or structured data, IndexedDB offers asynchronous, transactional storage with much higher quotas.
When to Use Local Storage
Use localStorage for small, non-sensitive preferences that should survive a reload: theme, language, dismissed banners, a collapsed sidebar, or a draft the user can afford to lose. It is ideal when you want zero dependencies and synchronous reads during startup. Do not use it for auth tokens, personal data, or anything that must be reliable or shared across devices; that belongs on the server. Avoid it for large datasets or frequent writes, because it is synchronous and capped at a few megabytes; use IndexedDB or the Cache API instead. Wrap every access in try/catch, since it can throw when storage is full, disabled, or blocked, and namespace keys to avoid collisions between features.
Local Storage in Practice
- Persisting a preference safely, with serialization and error handling: function savePrefs(prefs) { try { localStorage.setItem('app:prefs', JSON.stringify(prefs)); } catch (e) { console.warn('Could not save preferences', e); } }
- Reading with a fallback when the key is missing or the value is corrupt: let prefs; try { prefs = JSON.parse(localStorage.getItem('app:prefs')) ?? {}; } catch { prefs = {}; }
- Cross-tab logout: when one tab removes 'session-active', the storage event fires in every other open tab, which then redirects to the login page. window.addEventListener('storage', (e) => { if (e.key === 'session-active' && e.newValue === null) location.assign('/login'); });
- A note-taking app moves from localStorage to IndexedDB after users with hundreds of notes hit QuotaExceededError and noticed input lag from large synchronous writes.
Common Mistakes
- Storing JWTs or session tokens in localStorage. Any XSS can steal them; prefer HttpOnly, Secure cookies for session credentials.
- Saving objects without JSON.stringify, which stores the string "[object Object]". Serialize on write and parse on read.
- Assuming localStorage always exists and has space. It can throw in private modes, when disabled, or when full; wrap calls in try/catch with a fallback.
- Reading localStorage during server-side rendering, where window is undefined, or during the first client render, causing hydration mismatches. Read it in an effect after mount.
- Treating it as durable storage. Users and browsers clear site data; keep anything important on the server.

