The Web Storage API is a browser interface that lets a web page store string key-value pairs on the user’s device, scoped to the page’s origin, through two objects: localStorage, which persists across browser sessions, and sessionStorage, which lasts only for the lifetime of a single tab. Among Web APIs it is the simplest way to keep small amounts of client-side data without cookies or a database.
Its strengths are a synchronous, very small API and universal browser support. Its limits are equally important: values are strings only, capacity is typically around 5 MB per origin, every call blocks the main thread, and data is readable by any script running on the page, which makes it unsuitable for secrets.
How localStorage and sessionStorage work
Both storage objects implement the same Storage interface with methods setItem(key, value), getItem(key), removeItem(key), clear() and key(index), plus a length property. Values are always converted to strings, so objects must be serialized with JSON.stringify and parsed back with JSON.parse. Storing an object directly saves the useless text [object Object].
The difference between the two lies in lifetime and sharing. localStorage is shared by all tabs and windows of the same origin and survives browser restarts. sessionStorage belongs to one top-level browsing context: each tab has its own copy, the data survives reloads and restoring the tab, and it disappears when the tab is closed. Opening a link in a new tab copies the current session storage into the new tab at that moment, after which the two copies diverge.
| Property | localStorage | sessionStorage |
|---|---|---|
| Lifetime | Until deleted by code, user or browser policy | Until the tab is closed |
| Sharing | All tabs of the same origin | One tab only |
| Survives reload | Yes | Yes |
| Survives browser restart | Yes | Only if the session is restored |
| Change notification | storage event in other tabs | No cross-tab events in practice |
Scope, capacity and the storage event
Web Storage is partitioned by origin, meaning the combination of scheme, host and port. Data written by a shop served over HTTPS is invisible to the same host served over plain HTTP and to a blog on a different subdomain. Modern browsers additionally apply storage partitioning to third-party contexts, so an embedded iframe gets storage keyed to the top-level site and cannot track users across sites through it.
Browsers set quotas per origin, commonly about 5 MB for localStorage and a similar separate amount for sessionStorage, measured in UTF-16 characters in some engines. When the quota is exceeded, setItem throws a QuotaExceededError, so robust code wraps writes in try and catch and degrades gracefully.
When localStorage changes, the browser fires a storage event on the window of every other document of the same origin, but not on the document that made the change. The event exposes key, oldValue, newValue and url, which makes it a simple tool for synchronizing state across tabs, such as logging out everywhere when the user logs out in one tab.
| Limit | Typical value or behavior | Consequence |
|---|---|---|
| Quota per origin | Around 5 MB | Unsuitable for media or large datasets |
| Data type | Strings only | Serialization required for objects |
| Execution model | Synchronous | Large reads and writes block the main thread |
| Worker access | Not available in web workers or service workers | Background code needs IndexedDB |
| Private browsing | Cleared at the end of the session | No long-term persistence |
Web Storage compared with cookies and IndexedDB
Choosing a client-side storage mechanism depends on size, structure, access pattern and whether the server needs the data. Cookies travel with every HTTP request, which is useful for sessions and costly for anything else. IndexedDB handles large, structured and asynchronous storage. Web Storage sits between them for small preferences and UI state.
| Mechanism | Capacity | API style | Sent to server | Best for |
|---|---|---|---|---|
| Cookies | About 4 KB per cookie | String header | Yes, with each request | Session identifiers |
| localStorage | About 5 MB | Synchronous | No | Preferences, small cached values |
| sessionStorage | About 5 MB | Synchronous | No | Per-tab form drafts and flow state |
| IndexedDB | Large, quota-managed | Asynchronous | No | Structured data, offline apps |
| Cache API | Large, quota-managed | Asynchronous | No | HTTP responses for service workers |
A useful rule is that anything larger than a few kilobytes, anything accessed from a worker, or anything that needs indexing belongs in IndexedDB. Anything the server must read belongs in a cookie or a request header. Web Storage covers the remaining small, page-level cases.
Security and privacy considerations
Web Storage has no protection against scripts on the same origin. Any JavaScript running on the page, including a compromised third-party library or a cross-site scripting payload, can read every stored value. For this reason authentication tokens are generally safer in HttpOnly cookies, which scripts cannot read, than in localStorage.
- Never store passwords, payment data or long-lived tokens in Web Storage.
- Treat stored values as untrusted input when reading them back, because users and extensions can edit them.
- Version stored data with a schema key so that a new release can migrate or discard old formats.
- Respect consent rules; persistent identifiers in localStorage are subject to the same privacy regulations as cookies in many jurisdictions.
Practical patterns and common mistakes
Good uses include remembering a theme or language choice, keeping a draft of a long form in sessionStorage, and caching small configuration responses with a timestamp for expiry. Because Web Storage has no built-in expiration, code must store a time value alongside the data and check it when reading.
| Mistake | Symptom | Fix |
|---|---|---|
| Storing objects without JSON | Value reads as [object Object] | Serialize and parse explicitly |
| No error handling on writes | Crash when quota is full or storage is blocked | Wrap in try and catch |
| Large data on startup | Slow first interaction | Move to IndexedDB |
| Reading during server rendering | Reference error on the server | Access only in the browser |
| Assuming permanence | Lost data after browser cleanup | Treat as a cache, not a source of truth |
Put simply
The Web Storage API is a small notebook that a website keeps inside your browser. The site can write short notes in it, such as your preferred language or whether you chose dark mode. The next time you visit, it reads those notes back.
There are two notebooks. One is kept until it is erased. The other is thrown away when you close the tab. Each website gets its own notebook and cannot read the notes of other sites. It is handy for small things, but it is not a safe place for secrets.
In practice
A municipal portal offers a long permit application with about 40 fields across five steps. Residents often lose their progress when the page reloads or when they briefly navigate to read the guidance pages.
The developers save the form state to sessionStorage as a JSON string on every field change, debounced to one write per 500 milliseconds. On page load the form checks for a saved draft, validates its schema version and restores the values. Sensitive fields such as identity numbers are excluded from the draft and must be entered again. The user’s chosen language is stored separately in localStorage so that it applies to future visits.
After the change, abandoned applications decrease noticeably, and support calls about lost form data largely disappear, without any change to the server.