JS LocalStorage API Guide: A Complete Tutorial

A complete tutorial on the JavaScript LocalStorage API. Covers getItem, setItem, removeItem, clear, key enumeration, storage limits, the storage event for cross-tab synchronization, JSON serialization patterns, error handling for quota exceeded, and building a persistent settings manager with localStorage.

JavaScriptintermediate
14 min read

LocalStorage provides a synchronous key-value store in the browser that persists across page reloads and browser restarts. It stores strings only, has a ~5MB limit per origin, and is accessible from any tab on the same origin. This guide covers every method, pattern, and pitfall.

LocalStorage Methods

MethodDescriptionReturns
setItem(key, value)Store a valueundefined
getItem(key)Retrieve a valuestring or null
removeItem(key)Delete a keyundefined
clear()Remove all keys for this originundefined
key(index)Get key name by indexstring or null
lengthNumber of stored keysnumber

Basic Operations

javascriptjavascript
// Store a value (always a string)
localStorage.setItem("username", "parth");
 
// Retrieve a value
const username = localStorage.getItem("username"); // "parth"
 
// Non-existent keys return null
const missing = localStorage.getItem("nonexistent"); // null
 
// Remove a specific key
localStorage.removeItem("username");
 
// Clear everything for this origin
localStorage.clear();

Storing Non-String Values

LocalStorage stores strings only. Numbers, booleans, objects, and arrays must be serialized:

javascriptjavascript
// WRONG: stores "[object Object]"
localStorage.setItem("user", { name: "Parth" });
 
// CORRECT: serialize to JSON
localStorage.setItem("user", JSON.stringify({ name: "Parth", role: "developer" }));
 
// Read back and parse
const user = JSON.parse(localStorage.getItem("user"));
console.log(user.name); // "Parth"

Safe Read With Fallback

javascriptjavascript
function getStoredValue(key, fallback = null) {
  try {
    const raw = localStorage.getItem(key);
    if (raw === null) return fallback;
    return JSON.parse(raw);
  } catch {
    return fallback;
  }
}
 
const theme = getStoredValue("theme", "light");
const prefs = getStoredValue("preferences", { fontSize: 16, language: "en" });

See storing complex objects in JS localStorage guide for advanced serialization patterns.

Iterating All Keys

javascriptjavascript
// Method 1: for loop with key()
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  const value = localStorage.getItem(key);
  console.log(`${key}: ${value}`);
}
 
// Method 2: Object.keys
const allKeys = Object.keys(localStorage);
console.log(allKeys);
 
// Method 3: entries helper
function getAllEntries() {
  const entries = {};
  for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i);
    entries[key] = localStorage.getItem(key);
  }
  return entries;
}

Storage Limits and Quota Handling

javascriptjavascript
function safeSetItem(key, value) {
  try {
    localStorage.setItem(key, value);
    return true;
  } catch (error) {
    if (error.name === "QuotaExceededError" || error.code === 22) {
      console.error("LocalStorage quota exceeded");
      // Optionally: evict old data
      evictOldEntries();
      try {
        localStorage.setItem(key, value);
        return true;
      } catch {
        return false;
      }
    }
    return false;
  }
}
 
function getStorageUsage() {
  let total = 0;
  for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i);
    const value = localStorage.getItem(key);
    total += key.length + value.length;
  }
  return {
    usedBytes: total * 2, // UTF-16 encoding
    usedKB: Math.round((total * 2) / 1024),
    estimatedLimitKB: 5120, // ~5MB typical
  };
}

Browser Storage Limits

BrowserLocalStorage LimitPer Origin
Chrome5 MBYes
Firefox5 MBYes
Safari5 MBYes
Edge5 MBYes
Mobile Safari5 MBYes

Cross-Tab Synchronization With storage Event

javascriptjavascript
// This fires in OTHER tabs when localStorage changes
window.addEventListener("storage", (event) => {
  console.log("Storage changed in another tab:");
  console.log("Key:", event.key);
  console.log("Old value:", event.oldValue);
  console.log("New value:", event.newValue);
  console.log("URL that changed it:", event.url);
 
  // React to specific changes
  if (event.key === "theme") {
    applyTheme(event.newValue);
  }
 
  if (event.key === "authToken" && event.newValue === null) {
    // User logged out in another tab
    redirectToLogin();
  }
});

The storage event only fires in tabs other than the one that made the change. It does not fire in the tab that called setItem.

Settings Manager

javascriptjavascript
class SettingsManager {
  constructor(prefix = "app") {
    this.prefix = prefix;
    this.defaults = {};
    this.listeners = new Map();
  }
 
  setDefaults(defaults) {
    this.defaults = { ...this.defaults, ...defaults };
  }
 
  get(key) {
    const fullKey = `${this.prefix}:${key}`;
    const raw = localStorage.getItem(fullKey);
 
    if (raw === null) {
      return this.defaults[key] ?? null;
    }
 
    try {
      return JSON.parse(raw);
    } catch {
      return raw;
    }
  }
 
  set(key, value) {
    const fullKey = `${this.prefix}:${key}`;
    const serialized = JSON.stringify(value);
 
    try {
      localStorage.setItem(fullKey, serialized);
      this.notifyListeners(key, value);
      return true;
    } catch (error) {
      console.error(`Failed to save setting "${key}":`, error.message);
      return false;
    }
  }
 
  remove(key) {
    localStorage.removeItem(`${this.prefix}:${key}`);
    this.notifyListeners(key, this.defaults[key] ?? null);
  }
 
  onChange(key, callback) {
    if (!this.listeners.has(key)) {
      this.listeners.set(key, []);
    }
    this.listeners.get(key).push(callback);
 
    // Return unsubscribe function
    return () => {
      const cbs = this.listeners.get(key);
      const idx = cbs.indexOf(callback);
      if (idx > -1) cbs.splice(idx, 1);
    };
  }
 
  notifyListeners(key, value) {
    const cbs = this.listeners.get(key) || [];
    cbs.forEach((cb) => cb(value));
  }
 
  getAll() {
    const entries = {};
    for (let i = 0; i < localStorage.length; i++) {
      const fullKey = localStorage.key(i);
      if (fullKey.startsWith(this.prefix + ":")) {
        const key = fullKey.slice(this.prefix.length + 1);
        entries[key] = this.get(key);
      }
    }
    return { ...this.defaults, ...entries };
  }
 
  clearAll() {
    const keys = [];
    for (let i = 0; i < localStorage.length; i++) {
      const fullKey = localStorage.key(i);
      if (fullKey.startsWith(this.prefix + ":")) {
        keys.push(fullKey);
      }
    }
    keys.forEach((k) => localStorage.removeItem(k));
  }
}
 
// Usage
const settings = new SettingsManager("runehub");
settings.setDefaults({ theme: "light", fontSize: 16, language: "en" });
 
settings.set("theme", "dark");
console.log(settings.get("theme")); // "dark"
console.log(settings.get("fontSize")); // 16 (default)
 
const unsub = settings.onChange("theme", (newTheme) => {
  document.documentElement.dataset.theme = newTheme;
});

Checking for LocalStorage Availability

javascriptjavascript
function isLocalStorageAvailable() {
  try {
    const testKey = "__storage_test__";
    localStorage.setItem(testKey, "1");
    localStorage.removeItem(testKey);
    return true;
  } catch {
    return false;
  }
}
 
if (!isLocalStorageAvailable()) {
  console.warn("LocalStorage is not available. Using in-memory fallback.");
}
Rune AI

Rune AI

Key Insights

  • Strings only: Always JSON.stringify before storing and JSON.parse after reading; direct object storage produces "[object Object]"
  • ~5MB per origin: Monitor usage with a size calculator and evict old entries when approaching the quota
  • storage event for cross-tab sync: Fires in other tabs when localStorage changes, enabling theme sync, auth state propagation, and cart updates
  • Not secure for sensitive data: Any JavaScript on the same origin can read localStorage; use httpOnly cookies for auth tokens
  • Always wrap in try/catch: Safari private mode and quota limits can throw errors; provide fallbacks for unavailable storage
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between localStorage and sessionStorage?

localStorage persists until explicitly cleared. sessionStorage is cleared when the tab closes. Both share the same API. See [JS sessionStorage API guide complete tutorial](/tutorials/programming-languages/javascript/js-sessionstorage-api-guide-complete-tutorial) for sessionStorage details.

Is localStorage secure for storing tokens?

No. LocalStorage is accessible to any JavaScript on the same origin, making it vulnerable to XSS attacks. Store sensitive tokens in httpOnly cookies instead. Use localStorage only for non-sensitive preferences and UI state.

Does localStorage work in incognito/private mode?

Yes, but with caveats. In Safari private mode, localStorage has a 0-byte quota (writes throw QuotaExceededError). Other browsers allow localStorage in incognito but clear it when the session ends.

Can localStorage trigger the storage event in the same tab?

No. The `storage` event only fires in other tabs/windows on the same origin. To react to changes in the current tab, use a wrapper class with event listeners (like the SettingsManager above).

How do I handle cookies vs localStorage for user preferences?

Use localStorage for client-only preferences (theme, font size, sidebar state). Use cookies for preferences that need to be sent to the server with every request (language, currency). See [how to manage cookies in JS complete tutorial](/tutorials/programming-languages/javascript/how-to-manage-cookies-in-js-complete-tutorial) for cookie management.

Conclusion

LocalStorage provides persistent, synchronous key-value storage with a ~5MB limit per origin. Always serialize objects with JSON.stringify, handle QuotaExceededError gracefully, use prefixed keys to avoid collisions, and leverage the storage event for cross-tab synchronization. For storing complex nested objects, see storing complex objects in JS localStorage guide. For the session-scoped alternative, see JS sessionStorage API guide complete tutorial.