# localStorage and sessionStorage

> How localStorage and sessionStorage differ, their same-origin and per-origin quota rules, and why private-browsing behavior actually varies by browser.

**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.

## Reading and writing

```js
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()`.

## Browser support and quota

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.

## Reacting to changes from other tabs

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

The `storage` event fires on other `Document`s 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.

## How to detect it at runtime

```js
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.

## What goes wrong

- [ ] 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.

## Where to go next

- [Storage persistence, quotas, and eviction](/reference/storage/persistence/) — the
  related reference on origin storage quotas.
- [Clearing site data](/reference/storage/clearing-data/) — the related reference on
  clearing storage for a site.