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.
Syntax
Section titled “Syntax”localStorage.setItem(key, value)localStorage.getItem(key)localStorage.removeItem(key)localStorage.clear()localStorage.key(index)localStorage.lengthsessionStorage 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
Section titled “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
Section titled “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
Section titled “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, 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.
Examples
Section titled “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
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.
Detecting a usable storage area
Section titled “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.
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.
See also
Section titled “See also”- HTML Standard: Web storage (whatwg.org)
- Using the Web Storage API (developer.mozilla.org)
- IndexedDB
- StorageManager.estimate()
- Clear-Site-Data response header
Specifications
| Specification | Status |
|---|---|
| None. | |