Skip to content

The storage Event and Limits

When a page changes localStorage, the browser fires a storage event in every other tab and window open on the same origin — but not in the tab that made the change. This asymmetry surprises most developers the first time.

// Register this listener in Tab B
window.addEventListener('storage', (event) => {
console.log('key changed:', event.key);
console.log('old value:', event.oldValue);
console.log('new value:', event.newValue);
console.log('storage area:', event.storageArea); // localStorage object
});
// In Tab A, write to localStorage — Tab B's listener fires; Tab A's does NOT

The StorageEvent properties are:

PropertyTypeDescription
keystring | nullThe key that changed. null when clear() is called.
oldValuestring | nullThe previous value, or null if the key was newly created.
newValuestring | nullThe new value, or null if the key was removed.
urlstringThe URL of the document that made the change.
storageAreaStorage | nullThe localStorage object (never sessionStorage — that event only fires within the same tab).

The storage event fires only for localStorage. sessionStorage changes do not propagate across tabs because each tab has its own isolated session store.

sequenceDiagram
  participant A as Tab A (writer)
  participant LS as localStorage
  participant B as Tab B (listener)
  A->>LS: setItem("demo:signal", "ping")
  LS-->>B: storage event fires
  Note over B: key="demo:signal"<br/>newValue="ping"
  Note over A: No event fires in Tab A
The storage event fires in other tabs, not the writer

A common use case is broadcasting a logout across tabs:

// In any tab: signal logout
localStorage.setItem('demo:auth-event', 'logout:' + Date.now());
// In every other tab: react to it
window.addEventListener('storage', (e) => {
if (e.key === 'demo:auth-event' && e.newValue?.startsWith('logout:')) {
// redirect to login page
window.location.href = '/login';
}
});

The Web Storage specification does not mandate a specific quota size, but browsers universally implement approximately 5 MB per origin for localStorage (some give 10 MB; mobile browsers may give less).

When you exceed the quota, setItem throws a QuotaExceededError (a DOMException):

try {
const big = 'x'.repeat(6 * 1024 * 1024); // 6 MB string
localStorage.setItem('demo:big', big);
} catch (e) {
if (e instanceof DOMException && e.name === 'QuotaExceededError') {
console.error('Storage quota exceeded — cannot save data');
}
}

Always wrap writes that could be large (e.g., serialised arrays or objects that grow over time) in a try/catch to handle quota errors gracefully.

All Web Storage operations — setItem, getItem, removeItem, clear — run synchronously on the main thread. This is normally fine for small values, but it becomes a problem when:

  • You are storing large strings (hundreds of KB or more) — each call blocks the main thread while serialising and writing.
  • You are calling setItem in a tight loop — the cumulative blocking adds up.
  • You are reading a large value inside a requestAnimationFrame or event handler — frame drops become visible.

If you find yourself storing large blobs, consider IndexedDB (asynchronous, supports binary data) or the Cache API instead.

The snippet below registers a storage listener and then writes to localStorage. Because the listener only fires in other tabs, you will see the write confirmation but not the event in this window. Open the same page in a second tab to observe the cross-tab event.

Browser Storage
You call localStorage.setItem("x", "1") in Tab A. Which tabs receive the storage event?
What exception is thrown when a localStorage write exceeds the quota?
Does the storage event fire for sessionStorage changes in another tab?
Why should you avoid storing large blobs in localStorage?