# localStorage and sessionStorage

> The synchronous Storage interface behind localStorage and sessionStorage: syntax, the 5 MiB Firefox quota, Chromium's quota error, and the storage event.

`window.localStorage` and `window.sessionStorage` are two `Storage` objects holding string keys
and string values for one origin: `localStorage` survives tab closes and browser restarts, while
`sessionStorage` belongs to one top-level browsing context and is discarded when that tab or
window closes. Both are synchronous, main-thread only, and absent from workers and service
workers, which keeps them to small flags and preferences rather than application data.

## Syntax

```js
localStorage.setItem(key, value)
localStorage.getItem(key)
localStorage.removeItem(key)
localStorage.clear()
localStorage.key(index)
localStorage.length
```

`sessionStorage` has the identical interface. Values are stored as strings: `setItem('n', 1)`
stores `"1"`, and an object stores `"[object Object]"` unless serialised first. Supported since
Chrome 4, Firefox 3.5, and Safari 4 (BCD `api.Window.localStorage`); there is no secure-context
requirement, but the two schemes of one host are distinct origins and do not share storage.

## Parameters

| Method | Parameter | Type | Meaning |
|---|---|---|---|
| `setItem` | `key`, `value` | string, string | Create or overwrite one entry. Non-string arguments are converted with `String()`. |
| `getItem` | `key` | string | Returns the value, or `null` when the key does not exist. An empty string is a valid stored value and is returned as `""`, not `null`. |
| `removeItem` | `key` | string | Deletes one entry. Removing a missing key is not an error. |
| `clear` | none | | Deletes every entry for this origin in this storage area. |
| `key` | `index` | unsigned long | Returns the key at that position, or `null` past the end. Order is implementation-defined and changes after writes. |

Every call runs synchronously on the main thread and, in Firefox and Chromium, is backed by a
per-origin on-disk database, so a loop that writes large strings blocks rendering for the
duration.

## Availability

Web Storage is in every engine (Chrome 4, Firefox 3.5, Safari 4; BCD `api.Window.localStorage`),
including Android WebView and iOS WebViews, which makes it the one client-side store with no
support gap. The gaps are contextual rather than by browser: workers and service workers do not
expose either object, opaque origins throw on access, and the per-origin ceiling is small.

## Exceptions

| Exception | Where | When |
|---|---|---|
| `QuotaExceededError` `DOMException` | `setItem()` | The write would exceed the storage area's limit. Firefox caps `localStorage` at 5 MiB per origin (`dom.storage.default_quota`, 5 × 1024 KiB, in [StaticPrefList.yaml](https://github.com/mozilla-firefox/firefox/blob/main/modules/libpref/init/StaticPrefList.yaml), github.com). Chromium applies a similar per-origin ceiling and throws with the message `Setting the value of '<key>' exceeded the quota.` ([storage_area.cc](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/storage/storage_area.cc), chromium.googlesource.com). |
| `SecurityError` `DOMException` | reading `window.localStorage` | The origin is opaque (a sandboxed `<iframe>` without `allow-same-origin`, a `data:` URL) or the user has blocked storage for the site. The getter throws, so even `'localStorage' in window` is not enough; wrap the first access in `try`. |

A private window is not an exception path in Firefox and Chromium: writes succeed and the data
is dropped when the private session ends. Safari before version 11 instead exposed a
`localStorage` whose quota was zero, so the first `setItem()` threw `QuotaExceededError`; a
write-and-remove probe is the test that covers both behaviours.

## Examples

The examples use `localStorage`; swap in `sessionStorage` for per-tab state and the code is
otherwise unchanged.

### Storing a preference and reacting from another tab

The `storage` event fires on every other same-origin document that shares the storage area, and
not on the document that wrote. Use it to keep two open tabs in step without polling.

```js
function setTheme(theme) {
  localStorage.setItem('theme', theme);
  applyTheme(theme); // this tab gets no storage event
}

window.addEventListener('storage', (event) => {
  if (event.storageArea === localStorage && event.key === 'theme') {
    applyTheme(event.newValue ?? 'light'); // newValue is null after removeItem
  }
});
```

`event.storageArea` distinguishes `localStorage` from `sessionStorage` writes, and `event.key`
is `null` after `clear()`.

### Serialising structured data with a size guard

Because the limit is counted in characters of the serialised string, check the length before
writing and treat `QuotaExceededError` as a signal to move the data to IndexedDB rather than to
retry.

```js
function saveSettings(settings) {
  const json = JSON.stringify(settings);
  try {
    localStorage.setItem('settings', json);
    return true;
  } catch (err) {
    if (err.name === 'QuotaExceededError') return false; // too large: move to IndexedDB
    throw err;
  }
}
```

Anything above a few hundred kilobytes belongs in [IndexedDB](/reference/storage/indexeddb/),
which is asynchronous and shares the origin's much larger quota.

### Detecting a usable storage area

The probe writes and removes one key. It returns `false` for an opaque origin (the getter throws
`SecurityError`), for a blocked site, and for the zero-quota private mode of older Safari.

```js
function storageAvailable(type) {
  if (!(type in window)) {
    return false; // no Web Storage: keep state in memory for this session
  }
  try {
    const area = window[type];
    const probe = '__probe__';
    area.setItem(probe, probe);
    area.removeItem(probe);
    return true;
  } catch {
    return false; // access denied or a zero quota: fall back to memory
  }
}
```

Call it once with `'localStorage'` at startup and keep the answer; the conditions it tests do
not change while the page is open.

:::observed
In Chromium the quota failure surfaces as `Uncaught DOMException: Failed to execute 'setItem' on
'Storage': Setting the value of 'draft' exceeded the quota.`, where `draft` is the key being
written; the message is built in `StorageArea::setItem` in
[storage_area.cc](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/storage/storage_area.cc)
(chromium.googlesource.com). Chrome DevTools, Application > Storage > Local storage, lists the
origin and shows the key/value table where the oversized value is absent after the failed write.
:::

## See also

- [HTML Standard: Web storage](https://html.spec.whatwg.org/multipage/webstorage.html) (whatwg.org)
- [Using the Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API) (developer.mozilla.org)
- [IndexedDB](/reference/storage/indexeddb/)
- [StorageManager.estimate()](/reference/storage/quota-estimate/)
- [Clear-Site-Data response header](/reference/storage/clearing-data/)