How do I store data in the browser with localStorage?
Use localStorage.setItem(key, value) and getItem(key). Values are always strings, so wrap objects in JSON.stringify — and wrap every call in try/catch, because storage can be disabled or full.
localStorage.setItem('theme', 'dark');
localStorage.getItem('theme'); // 'dark'
localStorage.getItem('missing'); // null
localStorage.removeItem('theme');
localStorage.clear();#Objects need JSON
localStorage.setItem('progress', JSON.stringify({ lesson: 3, done: true }));
const progress = JSON.parse(localStorage.getItem('progress') || '{}');Without stringify, an object stores as the literal string [object Object].
#Always wrap it
function read(key, fallback = null) {
try {
const raw = localStorage.getItem(key);
return raw === null ? fallback : JSON.parse(raw);
} catch {
return fallback;
}
}Two things can throw: Safari's private mode historically threw on setItem, and JSON.parse throws on corrupted data. An uncaught throw here takes down whatever script it is in.
#localStorage vs sessionStorage vs cookies
| Survives close | Sent to server | Size | |
|---|---|---|---|
localStorage | Yes | No | ~5 MB |
sessionStorage | No — per tab | No | ~5 MB |
| Cookies | Configurable | Yes, every request | ~4 KB |
Use cookies only when the server needs the value. Everything else belongs in storage, where it does not bloat every request.
#It is synchronous
Every read and write blocks the main thread. Storing a large object on every keystroke will cause jank. Debounce writes, and use IndexedDB for anything substantial.
#Never store anything sensitive
Any JavaScript on the page — including a compromised third-party script — can read it. No tokens, no personal data.
#Syncing across tabs
window.addEventListener('storage', (event) => {
if (event.key === 'theme') applyTheme(event.newValue);
});The event fires in other tabs, not the one that made the change.