PureDevTools

Web Storage API Explorer

Explore, edit, and learn the Web Storage API — localStorage, sessionStorage, quota, and the storage event

All processing happens in your browser. No data is sent to any server.
Estimated usage (UTF-16)0 B / 5.00 MB
0.000% used
No items stored
🗄️
No items in localStorage
Click "+ Add" to create your first entry.

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

FeaturelocalStoragesessionStorage
PersistenceUntil explicitly clearedCleared when the tab closes
ScopeAll tabs/windows for the originCurrent tab only
Origin isolationYes (protocol + host + port)Yes
Cross-tab eventsYes (storage event)No
Typical quota~5 MB per origin~5 MB per tab
Shared between tabsYesNo

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: sessionStorage changes do not fire the storage event 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:

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.

Related Tools

More JavaScript Tools