Web Storage API Explorer
Explore, edit, and learn the Web Storage API — localStorage, sessionStorage, quota, and the storage event
You’re debugging a web app that stores auth tokens, user preferences, and cache data in localStorage. Chrome DevTools’ Application tab shows the key-value pairs, but editing values requires double-clicking tiny cells, there’s no JSON formatting for stored objects, and you can’t export/import the data for testing across environments.
Why This Explorer (Not Chrome DevTools)
Chrome DevTools shows localStorage/sessionStorage but with limited editing and no import/export. This tool is an interactive Web Storage explorer — view, add, edit, and delete items with JSON formatting, inspect quota usage, import/export as JSON, and learn the full API with live code examples. Everything runs in your browser.
What Is the Web Storage API?
The Web Storage API provides two storage mechanisms — localStorage and sessionStorage — that let web applications persist key-value string data directly in the browser, with no server round-trip.
// Store a value
localStorage.setItem("theme", "dark");
// Retrieve it
const theme = localStorage.getItem("theme"); // "dark"
// Remove it
localStorage.removeItem("theme");
Both storage objects implement the same Storage interface with identical methods (setItem, getItem, removeItem, clear, key) and the length property. The only difference is scope and persistence.
localStorage vs sessionStorage
| Feature | localStorage | sessionStorage |
|---|---|---|
| Persistence | Until explicitly cleared | Cleared when the tab closes |
| Scope | All tabs/windows for the origin | Current tab only |
| Origin isolation | Yes (protocol + host + port) | Yes |
| Cross-tab events | Yes (storage event) | No |
| Typical quota | ~5 MB per origin | ~5 MB per tab |
| Shared between tabs | Yes | No |
When to use localStorage
Use localStorage for data that should persist across browser sessions: user preferences (theme, language, font size), authentication tokens (with caution), cached API responses, or draft content.
When to use sessionStorage
Use sessionStorage for data that is only relevant to the current browsing session: form wizard state, temporary filter selections, shopping cart in a single-tab checkout, or scroll position restoration within a tab.
The Storage API Methods
setItem(key, value)
Stores a key-value pair. Both are coerced to strings — pass objects through JSON.stringify first:
// Primitive values
localStorage.setItem("count", 42); // stored as "42"
localStorage.setItem("active", true); // stored as "true"
// Objects must be serialised
localStorage.setItem("user", JSON.stringify({ id: 1, name: "Alice" }));
Throws DOMException (QuotaExceededError) when the per-origin quota is exceeded. Always wrap writes in try/catch in production.
getItem(key)
Returns the stored string value, or null if the key does not exist:
const theme = localStorage.getItem("theme"); // "dark" or null
// Parse JSON objects
const raw = localStorage.getItem("user");
const user = raw ? JSON.parse(raw) : null;
removeItem(key)
Removes a single key-value pair. Safe to call on a non-existent key — no error thrown:
localStorage.removeItem("session");
// Even if "session" doesn't exist, no error
clear()
Removes all key-value pairs for the current origin:
localStorage.clear();
console.log(localStorage.length); // 0
key(index)
Returns the key name at the given zero-based index, or null if out of range. Use it with length to iterate:
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
console.log(`${key} = ${value}`);
}
length property
Read-only. Returns the current count of stored key-value pairs:
console.log(localStorage.length); // e.g. 3
Storing Complex Data
Web Storage only stores strings. Use JSON.stringify / JSON.parse for everything else:
// Arrays
localStorage.setItem("ids", JSON.stringify([1, 2, 3]));
const ids = JSON.parse(localStorage.getItem("ids")); // [1, 2, 3]
// Nested objects
const prefs = { theme: "dark", sidebar: { collapsed: true, width: 280 } };
localStorage.setItem("prefs", JSON.stringify(prefs));
// Safe retrieval with fallback
function getJSON(key, fallback = null) {
const raw = localStorage.getItem(key);
if (raw === null) return fallback;
try { return JSON.parse(raw); } catch { return fallback; }
}
Quota Limits and Error Handling
Most browsers enforce a ~5 MB per-origin quota for localStorage. Writes that exceed the quota throw a DOMException:
try {
localStorage.setItem("bigData", largeString);
} catch (e) {
if (e instanceof DOMException && e.name === "QuotaExceededError") {
console.warn("Storage quota exceeded — clean up old data");
// evict old entries, then retry
} else {
throw e; // re-throw unexpected errors
}
}
Estimating usage: there is no standard API to query exact quota usage. A common approximation is:
function estimateUsageBytes() {
let total = 0;
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
total += (key.length + localStorage.getItem(key).length) * 2; // UTF-16
}
return total;
}
The storage Event (Cross-Tab Communication)
When localStorage changes, the browser fires a storage event on all other same-origin windows and tabs — not on the one that made the change:
window.addEventListener("storage", (event) => {
console.log("Key:", event.key); // changed key, or null on clear()
console.log("Old:", event.oldValue); // previous value (null if new)
console.log("New:", event.newValue); // new value (null if removed)
console.log("URL:", event.url); // source document URL
console.log("Area:", event.storageArea); // localStorage or sessionStorage
});
You can use this as a lightweight cross-tab message bus:
// Sender tab
function broadcast(channel, data) {
const msg = JSON.stringify({ channel, data, ts: Date.now() });
localStorage.setItem("__bus__", msg);
}
// Receiver tab
window.addEventListener("storage", (e) => {
if (e.key === "__bus__") {
const { channel, data } = JSON.parse(e.newValue);
if (channel === "userLoggedOut") handleLogout(data);
}
});
Note:
sessionStoragechanges do not fire thestorageevent across tabs.
Security and Privacy
Origin isolation
Both localStorage and sessionStorage are scoped to the origin (protocol + host + port). https://app.example.com and http://app.example.com are different origins with separate storage. Subdomains are also isolated unless you use document.domain (deprecated).
Private browsing / Incognito mode
Most browsers provide separate, ephemeral localStorage in private/incognito windows. Data written there is discarded when the private window closes. Availability and quota may differ.
Do not store sensitive data
localStorage is accessible to any JavaScript on the page, including third-party scripts. Do not store:
- Passwords or PINs
- Credit card numbers
- Unencrypted authentication tokens (use
HttpOnlycookies for session tokens) - Personal health or financial information
Content Security Policy (CSP)
CSP headers do not restrict localStorage access. Use iframe sandboxing if you need to isolate untrusted code from your storage.
Common Patterns
User preference store
const DEFAULT_PREFS = { theme: "light", fontSize: 16, language: "en" };
function loadPrefs() {
return getJSON("prefs", DEFAULT_PREFS);
}
function savePrefs(patch) {
const current = loadPrefs();
localStorage.setItem("prefs", JSON.stringify({ ...current, ...patch }));
}
// Apply dark mode on page load
const prefs = loadPrefs();
document.documentElement.dataset.theme = prefs.theme;
Expiring cache
function setCached(key, value, ttlMs) {
const entry = { value, expiresAt: Date.now() + ttlMs };
localStorage.setItem(key, JSON.stringify(entry));
}
function getCached(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
try {
const { value, expiresAt } = JSON.parse(raw);
if (Date.now() > expiresAt) {
localStorage.removeItem(key); // evict expired entry
return null;
}
return value;
} catch {
return null;
}
}
// Cache an API response for 5 minutes
setCached("weatherData", apiResult, 5 * 60 * 1000);
const cached = getCached("weatherData"); // null if expired
Session restore (form state)
const form = document.getElementById("checkout-form");
// Save on every change
form.addEventListener("input", () => {
const data = Object.fromEntries(new FormData(form));
sessionStorage.setItem("checkoutDraft", JSON.stringify(data));
});
// Restore on page load
const draft = getJSON(sessionStorage.getItem("checkoutDraft"));
if (draft) restoreForm(form, draft);
Frequently Asked Questions
What is the maximum storage size for localStorage?
Most modern browsers enforce a 5 MB per-origin quota for localStorage. Some browsers allow the user to grant more. There is no Web API to query the exact remaining quota — you must catch QuotaExceededError to detect when it is full.
Is localStorage synchronous?
Yes, all Web Storage operations are synchronous and blocking on the main thread. This is rarely a problem for small data, but serialising or deserialising very large objects can cause noticeable jank. For large data, consider the asynchronous IndexedDB API instead.
What is the difference between localStorage and cookies?
localStorage is larger (~5 MB vs ~4 KB per cookie), not sent with HTTP requests (no bandwidth overhead), purely JavaScript-accessible, and has no built-in expiry. Cookies have an expiry date, can be marked HttpOnly (inaccessible to JS), and are sent automatically with every request. Use cookies for session tokens; use localStorage for client-side-only preferences and cache.
Does localStorage work in private/incognito mode?
Yes, but data is stored in a separate, ephemeral partition that is deleted when the private session ends. Code that relies on localStorage continuing to work should handle the case where previously stored data is absent.
Can two different websites share localStorage?
No. localStorage is strictly origin-scoped. https://alice.com and https://bob.com have completely separate storage and cannot access each other’s data.
Why should I not store JWT tokens in localStorage?
JWTs in localStorage are accessible to any JavaScript on the page — including injected scripts from an XSS attack. A malicious script can exfiltrate the token and impersonate the user. Prefer HttpOnly cookies for authentication tokens, which are inaccessible to JavaScript and automatically included in requests.
When should I use IndexedDB instead of localStorage?
Use IndexedDB when you need: (1) more than ~5 MB of storage, (2) structured querying or indexing, (3) binary data (Blobs, ArrayBuffers), (4) asynchronous access to avoid blocking the main thread, or (5) storing more complex data structures natively without serialisation overhead.