Skip to content

localStorage and sessionStorage

beginner12 min read

Remembering Things Between Page Loads

Sometimes your app needs to remember things. A user's theme preference. The contents of a half-finished form. A shopping cart. You could send this data to a server, but for many cases that's overkill. The browser gives you Web Storage — a simple key-value store that persists data right on the user's device.

There are two flavors: localStorage (persists forever, or until the user clears it) and sessionStorage (persists only for the current browser tab). Same API, different lifetimes.

Mental Model

Think of localStorage as a sticky note on the fridge — it stays there until someone deliberately removes it, even after you leave and come back. sessionStorage is a note on a whiteboard in a meeting room — it disappears as soon as you leave the room (close the tab). Both hold the same kind of information, but one survives and the other doesn't.

The API (Same for Both)

// Store a value
localStorage.setItem('theme', 'dark');

// Retrieve a value
localStorage.getItem('theme'); // "dark"

// Remove a single item
localStorage.removeItem('theme');

// Clear everything
localStorage.clear();

// Check how many items are stored
localStorage.length; // number

// Get the key at a specific index
localStorage.key(0); // first key name

sessionStorage has the exact same methods — just replace localStorage with sessionStorage.

The Critical Rule: Strings Only

Web Storage only stores strings. If you store a number, it comes back as a string. If you store a boolean, it comes back as a string. If you store an object, you get [object Object].

localStorage.setItem('count', 42);
typeof localStorage.getItem('count'); // "string" — it's "42", not 42

localStorage.setItem('active', true);
localStorage.getItem('active'); // "true" — a string, not a boolean

localStorage.setItem('user', { name: 'Ada' });
localStorage.getItem('user'); // "[object Object]" — useless!

The solution: JSON serialization.

// Storing objects and arrays
const user = { name: 'Ada', role: 'engineer' };
localStorage.setItem('user', JSON.stringify(user));

// Retrieving and parsing
const stored = localStorage.getItem('user');
const parsed = JSON.parse(stored);
// { name: 'Ada', role: 'engineer' }
Common Trap

JSON.parse(null) returns null (not an error), but JSON.parse(undefined) throws a SyntaxError. Since getItem returns null for missing keys, you're safe with a direct JSON.parse(localStorage.getItem('key')) — but if someone has stored the literal string "undefined", you'll get an error. Always validate before parsing.

A Safe Read Pattern

function getStoredJSON(key) {
  const raw = localStorage.getItem(key);
  if (raw === null) return null;
  try {
    return JSON.parse(raw);
  } catch {
    return null;
  }
}

const user = getStoredJSON('user');
Quiz
What happens when you store the number 42 in localStorage and then retrieve it?

localStorage vs sessionStorage

FeaturelocalStoragesessionStorage
LifetimeUntil explicitly deleted or user clears browser dataUntil the tab is closed
ScopeShared across all tabs on the same originIsolated to the specific tab
Survives page refreshYesYes
Survives tab closeYesNo
Survives browser restartYesNo
Storage limit~5-10 MB per origin~5-10 MB per origin

When to Use Each

localStorage — persistent preferences and non-sensitive data:

  • Theme preference (dark/light mode)
  • Language selection
  • Feature flags
  • Non-sensitive user preferences

sessionStorage — temporary, tab-specific state:

  • Form data in a multi-step wizard (so the user doesn't lose progress on refresh)
  • Scroll position to restore when navigating back
  • One-time flags ("user saw the onboarding tooltip")
// Theme preference — should persist across sessions
localStorage.setItem('theme', 'dark');

// Multi-step form data — should survive refresh but not new tabs
sessionStorage.setItem('checkout-step', JSON.stringify({
  step: 2,
  shipping: { name: 'Ada', address: '...' }
}));
Quiz
A user fills out step 1 of a checkout form, then accidentally refreshes the page. Where should you store the form data to preserve it?

Storage Events

When localStorage changes in another tab on the same origin, a storage event fires in all other open tabs. This doesn't fire in the tab that made the change.

window.addEventListener('storage', (event) => {
  event.key;        // the key that changed
  event.oldValue;   // previous value (string or null)
  event.newValue;   // new value (string or null)
  event.url;        // URL of the page that made the change
  event.storageArea; // the Storage object (localStorage or sessionStorage)
});

This is one of the simplest ways to communicate between tabs:

// Tab A: user logs out
localStorage.setItem('auth', '');

// Tab B: detects the logout
window.addEventListener('storage', (e) => {
  if (e.key === 'auth' && e.newValue === '') {
    window.location.href = '/login';
  }
});
Storage events and sessionStorage

The storage event fires for localStorage changes across tabs, but not for sessionStorage — because sessionStorage is tab-isolated. Each tab has its own sessionStorage, so there's nothing to sync.

Storage Limits and Quotas

Web Storage typically gives you 5 MB per origin (protocol + hostname + port). Some browsers allow up to 10 MB. This is per origin, not per page — all pages on example.com share the same 5 MB.

// Check approximate storage usage
function getStorageSize(storage) {
  let total = 0;
  for (let i = 0; i < storage.length; i++) {
    const key = storage.key(i);
    const value = storage.getItem(key);
    total += key.length + value.length;
  }
  return total * 2; // characters are 2 bytes in UTF-16
}

console.log(`Using ~${getStorageSize(localStorage)} bytes`);

When you exceed the quota, setItem throws a QuotaExceededError:

try {
  localStorage.setItem('data', hugeString);
} catch (e) {
  if (e.name === 'QuotaExceededError') {
    console.log('Storage is full');
  }
}

When NOT to Use Web Storage

Web Storage is not the right tool for everything. Here's when to reach for alternatives:

Cookies — When the Server Needs the Data

Cookies are sent with every HTTP request to the server. Web Storage is not. If the server needs to read a value (like an auth token), use cookies.

// Cookie: sent to server automatically with every request
document.cookie = 'session=abc123; Secure; HttpOnly; SameSite=Strict';

// localStorage: stays on the client, never sent to server
localStorage.setItem('session', 'abc123'); // server never sees this

IndexedDB — When You Need More Space or Structure

IndexedDB is a full database in the browser. Use it when:

  • You need to store more than 5 MB
  • You need to query data (search, filter, sort)
  • You're storing complex structured data (blobs, files, large datasets)
  • You need transactions (atomic read-write operations)
Never store sensitive data in Web Storage

localStorage and sessionStorage are accessible to any JavaScript running on the page. If your site has an XSS vulnerability, an attacker can read everything in storage. Never store auth tokens, passwords, credit card numbers, or any sensitive data in Web Storage. Use HttpOnly cookies for authentication tokens — they're invisible to JavaScript.

Quiz
Why should you NOT store authentication tokens in localStorage?
Key Rules
  1. 1Web Storage only stores strings — always JSON.stringify objects before storing and JSON.parse when reading
  2. 2localStorage persists forever; sessionStorage is cleared when the tab closes
  3. 3Storage events fire in OTHER tabs, not the tab that made the change
  4. 4Never store sensitive data (tokens, passwords) in Web Storage — use HttpOnly cookies
  5. 5Handle QuotaExceededError when writing — storage has a ~5 MB per-origin limit
What developers doWhat they should do
Storing objects directly: localStorage.setItem('user', userObj)
Passing an object to setItem calls toString() on it, which returns '[object Object]'. You lose all your data. Always use JSON.stringify for non-string values
Serializing first: localStorage.setItem('user', JSON.stringify(userObj))
Assuming localStorage is available everywhere
localStorage throws errors in private browsing mode in some browsers, when storage is disabled, or when the quota is exceeded. Always handle the failure case
Wrapping storage access in a try-catch
Using localStorage for auth tokens
Any script on the page can read localStorage. An XSS attack can steal tokens stored there. HttpOnly cookies are invisible to JavaScript and are the secure choice for authentication
Using HttpOnly cookies for authentication