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:
- localStorage JSON Editor - Manage localStorage
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
- Manage storage with localStorage Editor
- Learn about Browser Storage APIs
- Explore Security Best Practices