Skip to content

Storage · API

localStorage and sessionStorage

Published Updated

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.

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.

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.

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.

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

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

Section titled “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.

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

Section titled “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.

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, which is asynchronous and shares the origin’s much larger quota.

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.

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.

Specifications

SpecificationStatus
None.