People look up the same behavior under many names: localstorage event listener, local storage event listener, or storage event listener. Shorter queries like localstorage listener, localstorage events, and localstorage event point at the same API surface. The phrase localstorage change event describes what you care about—another document touched localStorage—while addeventlistener storage and window.addEventListener('storage', …) are the wiring. A common MDN-style question is why the storage event is not fired on the same window that called setItem; that is by design, not a bug.
This page explains native cross-tab delivery, the StorageEvent fields, and an optional synthetic dispatchEvent pattern for same-tab code reuse. For listener basics, see JavaScript addEventListener.
Verified with Node.js v20.18.2: All code samples and commands in this article were executed successfully (happy-dom 15.11.7 for the
storageevent checks).
Native rule: not fired on the same window
Per the Window: storage event documentation, a document receives the event when another same-origin document mutates the shared localStorage area. The browsing context that performed setItem, removeItem, or clear does not receive its own native storage event.
The script below registers a storage listener, writes to localStorage, and counts deliveries. In happy-dom (and real browsers), the count after setItem stays zero:
let fires = 0;
window.addEventListener("storage", () => {
fires++;
});
localStorage.setItem("loggedIn", "true");
console.log(String(fires));0Other tabs on the same origin do get the event with populated key / oldValue / newValue (subject to browser timing). Each happy-dom Window has isolated storage in typical automated setups, so a single test window cannot emulate two real tabs; it only proves the same-window rule above.
For sessionStorage, MDN limits delivery to other browsing contexts in the same tab (for example iframes), not arbitrary other tabs; see the same storage event page.
window.addEventListener("storage", handler)
Register once per page. The handler receives a StorageEvent describing what changed elsewhere.
window.addEventListener("storage", (event) => {
if (event.key === "theme") {
applyTheme(event.newValue);
}
});
function applyTheme(theme) {
document.body.className = theme ?? "";
}Useful fields on event (StorageEvent):
key— which entry changed (nullwhenclear()wiped the whole object).oldValue/newValue— previous and new string values (ornullwhen adding/removing).url— document URL reported for the initiating context.storageArea— theStorageobject (localStorageorsessionStorage) that changed.
Same-tab reuse: synthetic StorageEvent
If you want one storage handler to run in the tab that performed the write, the platform will not do that for you natively. Options:
- Call the same function you would call from the listener immediately after
setItem. - Or dispatch a synthetic
StorageEventyourself (still not a substitute for real cross-tab propagation—it only affects thiswindow).
Listener receiving an explicit event:
const lines = [];
window.addEventListener("storage", (event) => {
lines.push(`${event.key}:${String(event.oldValue)}->${String(event.newValue)}`);
});
window.dispatchEvent(
new StorageEvent("storage", {
key: "theme",
oldValue: "light",
newValue: "dark",
url: window.location.href,
storageArea: localStorage
})
);
console.log(lines.join(";"));theme:light->darkHelper that writes storage then notifies local listeners (pattern only—keep your real applyTheme / state logic in one place):
const heard = [];
window.addEventListener("storage", (e) => {
heard.push(`${e.key}=${e.newValue}`);
});
function updateLocalStorage(key, newValue) {
const oldValue = localStorage.getItem(key);
localStorage.setItem(key, newValue);
window.dispatchEvent(
new StorageEvent("storage", {
key,
oldValue,
newValue,
url: window.location.href,
storageArea: localStorage
})
);
}
updateLocalStorage("theme", "dark");
updateLocalStorage("theme", "light");
console.log(heard.join("|"));theme=dark|theme=lightFor cross-tab UX (theme, auth, cart JSON), rely on the native event in every other tab, and in the writer tab call your updater directly so UI stays in sync without pretending the browser fired storage.
Summary
If you are wiring a localstorage event listener or searching for local storage event listener examples, remember the platform rule first: the native storage event is for other same-origin documents, not for the tab that just called setItem. That is why people ask whether localstorage change event fired “here” after their own write—it will not, by design, and MDN-style questions about the event not firing on the same window are answered by that contract. Use window.addEventListener("storage", handler) (or addEventListener on window) and read key, oldValue, newValue, url, and storageArea from the StorageEvent.
When you still want one handler to run in the writer tab, the usual FAQ is whether to fake it: either call the same UI function immediately after mutating storage, or dispatch a synthetic StorageEvent for local listeners, understanding that it is not a substitute for real cross-tab synchronization. Pair this with sensible HTTP caching when you are trying to force fresh data instead of reaching for deprecated reload flags.
