Skip to content

localStorage and sessionStorage

Published Updated

In one line: localStorage and sessionStorage are synchronous, string-only, same-origin key-value stores exposed on Window; localStorage persists across tabs and browser restarts, while sessionStorage is scoped to one tab and cleared when that tab closes.

localStorage.setItem("theme", "dark");
const theme = localStorage.getItem("theme"); // "dark"
localStorage.removeItem("theme");
localStorage.clear(); // removes every key for this origin
// sessionStorage has the identical method set, scoped to the current tab:
sessionStorage.setItem("draftId", "42");

Per MDN’s usage guide, both interfaces store only strings — non-string data such as objects or arrays must be converted explicitly, for example with JSON.stringify().

Both localStorage and sessionStorage are long-standing, widely implemented parts of the Web Storage API across current browsers. Per MDN, the two are separate Storage objects that “function and are controlled separately.” Per MDN’s usage guide, writes can still fail once all available storage for the origin has been used, throwing a QuotaExceededError DOMException rather than failing silently.

window.addEventListener("storage", (event) => {
console.log(event.key, event.oldValue, event.newValue);
});

The storage event fires on other Documents that share the same storage area when localStorage changes — never on the document that made the change itself. Per MDN, this means a same-origin document elsewhere in the same tab, such as an iframe, can still receive the event even though the writing document does not. Use it to keep other open documents in sync.

function storageAvailable() {
if (!('localStorage' in window)) {
// localStorage property not present — fall back to an in-memory store for this session.
return false;
}
try {
const probe = "__storage_test__";
window.localStorage.setItem(probe, probe);
window.localStorage.removeItem(probe);
return true;
} catch {
// Present but unusable — e.g. a private-browsing mode that reports a 0-byte
// quota — so treat it as unavailable and fall back the same way.
return false;
}
}

This mirrors MDN’s own feature-detection guidance in “Using the Web Storage API,” which also probes with a real write — though MDN’s version treats a QuotaExceededError on an already non-empty store as evidence storage is still available, rather than unconditionally reporting failure the way the simplified probe above does.

  • Values are always strings — JSON.stringify() before setItem() and JSON.parse() after getItem() for anything structured.
  • Both stores are same-origin: scheme, host, and port must all match, so https://example.com and http://example.com do not share storage.
  • The storage event only fires on other documents sharing the storage area, never on the document that made the write — do not wait for it locally. Per MDN, a same-origin document elsewhere in the same tab (e.g. an iframe) can still receive it.
  • Private/incognito behavior is not uniform across browsers. Per MDN’s Web Storage guide, data written in a private window is generally treated like sessionStorage and cleared when the last private tab closes — but MDN also notes that some browsers instead expose a localStorage object with a fixed 0-byte quota, so writes throw immediately rather than surviving until the tab closes. Feature-detect with a real write/remove probe rather than assuming either behavior.
  • All writes and reads are synchronous and run on the main thread; large values or frequent writes can block rendering — prefer IndexedDB for anything sizable.
  • sessionStorage is partitioned by both origin and top-level browsing context, per MDN — not simply one store per tab. Per MDN, a newly opened page that has an opener reference back to the page that opened it initially receives a copy of that opener’s sessionStorage; a context opened without an opener does not.