Web applications need to store data on the client side — user preferences, authentication tokens, cached data, shopping cart contents, draft form data. Browsers offer several storage mechanisms, each with different characteristics. Choosing the wrong one can create performance bottlenecks, security vulnerabilities, or data loss.
Quick Comparison
Feature localStorage sessionStorage Cookies IndexedDB ────────────────────────────────────────────────────────────────────────────── Capacity 5-10 MB 5-10 MB ~4 KB/cookie 100MB+ Persistence Permanent Tab lifetime Configurable Permanent Sent to server No No Yes (every req) No Access Sync JS Sync JS JS + HTTP Async JS Data format String String String Any (objects) Use case Preferences Temp state Auth tokens Large datasets
localStorage
Stores key-value pairs with no expiration. Data persists until explicitly cleared by the user or your code.
// Store data (strings only)
localStorage.setItem('theme', 'dark');
localStorage.setItem('user', JSON.stringify({ name: 'Alice', id: 123 }));
// Retrieve data
const theme = localStorage.getItem('theme'); // "dark"
const user = JSON.parse(localStorage.getItem('user'));
// Remove specific item
localStorage.removeItem('theme');
// Clear all data
localStorage.clear();
// Check storage usage
const used = new Blob(Object.values(localStorage)).size;
console.log(`${(used / 1024).toFixed(1)} KB used`);
Best for: Theme preference, language choice, UI state (sidebar open/closed), non-sensitive settings.
⚠️ Never store: Authentication tokens, passwords, or any sensitive data. localStorage is accessible to all JavaScript on the page, including XSS-injected scripts.
sessionStorage
Identical API to localStorage, but data is scoped to the current browser tab and cleared when the tab is closed.
// Store form data (recoverable on accidental navigation)
const formData = { name: nameInput.value, email: emailInput.value };
sessionStorage.setItem('formDraft', JSON.stringify(formData));
// Restore on page load
window.addEventListener('load', () => {
const draft = sessionStorage.getItem('formDraft');
if (draft) {
const { name, email } = JSON.parse(draft);
nameInput.value = name;
emailInput.value = email;
}
});
Best for: Multi-step form data, temporary wizard state, per-tab data that shouldn't leak between tabs.
Cookies
The oldest storage mechanism. Unlike localStorage, cookies are sent to the server with every HTTP request — which makes them ideal for authentication but terrible for large data storage.
// Setting a cookie (server-side — Express.js)
res.cookie('session_id', 'abc123', {
httpOnly: true, // JavaScript cannot access this cookie (XSS protection)
secure: true, // Only sent over HTTPS
sameSite: 'Lax', // CSRF protection
maxAge: 86400000, // Expires in 24 hours (milliseconds)
path: '/',
});
// Setting a cookie (client-side JavaScript)
document.cookie = "theme=dark; path=/; max-age=31536000; SameSite=Lax";
// Reading cookies (client-side) — awkward API
const theme = document.cookie
.split('; ')
.find(row => row.startsWith('theme='))
?.split('=')[1]; // "dark"
Best for: Session IDs, authentication tokens (with httpOnly + secure + sameSite).
⚠️ Important cookie flags:
httpOnly— Cannot be read by JavaScript (prevents XSS token theft)secure— Only sent over HTTPSsameSite=Lax|Strict— CSRF protection
IndexedDB
A full client-side database. Stores structured data (objects, files, blobs) with indexes for querying. Asynchronous API. Capacity of hundreds of megabytes.
// Open database
const request = indexedDB.open('MyApp', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
const store = db.createObjectStore('products', { keyPath: 'id' });
store.createIndex('category', 'category', { unique: false });
};
request.onsuccess = (event) => {
const db = event.target.result;
// Add data
const tx = db.transaction('products', 'readwrite');
tx.objectStore('products').add({
id: 1,
name: 'Widget',
category: 'tools',
price: 29.99,
});
// Read data
const getTx = db.transaction('products', 'readonly');
const getReq = getTx.objectStore('products').get(1);
getReq.onsuccess = () => console.log(getReq.result);
};
Best for: Offline-capable apps, caching large datasets, storing files/blobs, progressive web apps (PWAs).
Decision Guide
- Theme/language preference? → localStorage
- Form draft data? → sessionStorage
- Auth session? → HttpOnly Secure Cookie
- Shopping cart? → localStorage (guest) or server-side (logged in)
- Offline data cache? → IndexedDB
- Large file storage? → IndexedDB
- Data needed by server? → Cookie
Try Our Free Developer Tools
Encode and decode data for client-side storage.