Introduction

localStorage provides persistent client-side storage in browsers. It’s simple to use but requires proper handling to avoid common pitfalls. This guide covers best practices for using localStorage effectively.

What is localStorage?

localStorage is a browser API that stores key-value pairs:

  • Persistent: Survives browser restarts
  • Domain-specific: Isolated per origin
  • Synchronous: Blocking API
  • String-only: Stores strings (JSON needs serialization)

Basic Usage

// Set item
localStorage.setItem("key", "value");

// Get item
const value = localStorage.getItem("key");

// Remove item
localStorage.removeItem("key");

// Clear all
localStorage.clear();

Storage Limits

Size Limits

  • Most browsers: 5-10 MB per origin
  • Mobile Safari: ~5 MB (may prompt user)
  • Can exceed: Browser may delete data

Check Available Space

function getStorageSize() {
  let total = 0;
  for (let key in localStorage) {
    if (localStorage.hasOwnProperty(key)) {
      total += localStorage[key].length + key.length;
    }
  }
  return total; // Bytes
}

function getAvailableSpace() {
  try {
    localStorage.setItem("test", "x".repeat(1024 * 1024)); // 1MB
    localStorage.removeItem("test");
    return "Available";
  } catch (e) {
    return "Quota exceeded";
  }
}

Working with Objects

Serialization

// ❌ Wrong - stores "[object Object]"
localStorage.setItem("user", { name: "Alice" });

// ✅ Correct - serialize to JSON
const user = { name: "Alice", age: 30 };
localStorage.setItem("user", JSON.stringify(user));

// Retrieving
const stored = localStorage.getItem("user");
const user = JSON.parse(stored);

Helper Functions

// Save object
function saveObject(key, obj) {
  try {
    localStorage.setItem(key, JSON.stringify(obj));
    return true;
  } catch (e) {
    console.error("Failed to save:", e);
    return false;
  }
}

// Load object
function loadObject(key) {
  try {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : null;
  } catch (e) {
    console.error("Failed to load:", e);
    return null;
  }
}

Error Handling

QuotaExceededError

function safeSetItem(key, value) {
  try {
    localStorage.setItem(key, value);
    return true;
  } catch (e) {
    if (e.name === "QuotaExceededError") {
      console.error("Storage quota exceeded");
      // Handle: Clear old data, compress, etc.
      return false;
    }
    throw e;
  }
}

Private Browsing Mode

function isLocalStorageAvailable() {
  try {
    const test = "__localStorage_test__";
    localStorage.setItem(test, test);
    localStorage.removeItem(test);
    return true;
  } catch (e) {
    return false;
  }
}

if (!isLocalStorageAvailable()) {
  // Fallback to in-memory storage
  console.warn("localStorage not available");
}

Best Practices

✅ Do This

1. Use consistent key naming

const STORAGE_KEYS = {
  USER: "app:user",
  SETTINGS: "app:settings",
  THEME: "app:theme",
};

localStorage.setItem(STORAGE_KEYS.USER, userData);

2. Validate data on load

function loadUser() {
  const data = localStorage.getItem("user");
  if (!data) return null;

  try {
    const user = JSON.parse(data);
    // Validate structure
    if (user.id && user.email) {
      return user;
    }
  } catch (e) {
    localStorage.removeItem("user"); // Clean invalid data
  }
  return null;
}

3. Handle expiration

function setWithExpiry(key, value, ttl) {
  const item = {
    value: value,
    expiry: Date.now() + ttl,
  };
  localStorage.setItem(key, JSON.stringify(item));
}

function getWithExpiry(key) {
  const itemStr = localStorage.getItem(key);
  if (!itemStr) return null;

  const item = JSON.parse(itemStr);
  if (Date.now() > item.expiry) {
    localStorage.removeItem(key);
    return null;
  }
  return item.value;
}

4. Clean up old data

function cleanupOldData() {
  const keys = Object.keys(localStorage);
  const prefix = "app:";

  keys.forEach((key) => {
    if (key.startsWith(prefix)) {
      const data = localStorage.getItem(key);
      const item = JSON.parse(data);

      if (item.expiry && Date.now() > item.expiry) {
        localStorage.removeItem(key);
      }
    }
  });
}

❌ Don’t Do This

1. Don’t store sensitive data

// ❌ Never store passwords, tokens, etc.
localStorage.setItem("password", userPassword);
localStorage.setItem("apiKey", secretKey);

// ✅ Use httpOnly cookies or secure storage

2. Don’t store large objects

// ❌ Large objects can exceed quota
localStorage.setItem("hugeArray", JSON.stringify(largeArray));

// ✅ Store references or use IndexedDB

3. Don’t block the main thread

// ❌ localStorage is synchronous - blocks UI
for (let i = 0; i < 10000; i++) {
  localStorage.setItem(`key${i}`, data);
}

// ✅ Batch operations or use Web Workers

Use Cases

User Preferences

const preferences = {
  theme: "dark",
  language: "en",
  notifications: true,
};

localStorage.setItem("preferences", JSON.stringify(preferences));

Form Auto-Save

function autoSave(formId, data) {
  localStorage.setItem(
    `form:${formId}`,
    JSON.stringify({
      data: data,
      timestamp: Date.now(),
    })
  );
}

function restoreForm(formId) {
  const saved = localStorage.getItem(`form:${formId}`);
  if (saved) {
    return JSON.parse(saved);
  }
}

Cache API Responses

async function fetchWithCache(url) {
  const cacheKey = `cache:${url}`;
  const cached = localStorage.getItem(cacheKey);

  if (cached) {
    const { data, expiry } = JSON.parse(cached);
    if (Date.now() < expiry) {
      return data;
    }
  }

  const response = await fetch(url);
  const data = await response.json();

  localStorage.setItem(
    cacheKey,
    JSON.stringify({
      data: data,
      expiry: Date.now() + 60 * 60 * 1000, // 1 hour
    })
  );

  return data;
}

Alternatives

sessionStorage

  • Same API as localStorage
  • Cleared when tab closes
  • Good for temporary data

IndexedDB

  • Large storage (100MB+)
  • Async API
  • Structured data
  • Good for large datasets

Cookies

  • Sent with requests
  • Size limit (~4KB)
  • Good for server-side needs

Tools

Use our tools:

Conclusion

localStorage is useful for:

Good for:

  • User preferences
  • Form auto-save
  • Small data cache
  • Non-sensitive data

Not for:

  • Sensitive information
  • Large datasets
  • Critical data (can be cleared)
  • Data needed by server

Remember:

  • Always handle errors
  • Validate stored data
  • Use expiration for cache
  • Don’t store secrets

Next Steps