How do I store data in the browser with localStorage?

HTML, CSS & JS 2 min read
Short answer

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.

javascript
localStorage.setItem('theme', 'dark');
localStorage.getItem('theme');       // 'dark'
localStorage.getItem('missing');     // null
localStorage.removeItem('theme');
localStorage.clear();

#Objects need JSON

javascript
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

javascript
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 closeSent to serverSize
localStorageYesNo~5 MB
sessionStorageNo — per tabNo~5 MB
CookiesConfigurableYes, 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

javascript
window.addEventListener('storage', (event) => {
  if (event.key === 'theme') applyTheme(event.newValue);
});

The event fires in other tabs, not the one that made the change.