Introduction
Modern browsers provide several mechanisms for storing data on the client side. The Web Storage API (localStorage and sessionStorage) offers a simple key-value store, while cookies provide a more traditional mechanism with server-side access. Choosing the right storage type depends on your data’s lifetime, size, and security requirements.
sessionStorage
sessionStorage stores data for the duration of the page session. Data is cleared when the tab or browser is closed.
// Store a value
sessionStorage.setItem('username', 'alice');
sessionStorage.setItem('theme', 'dark');
// Retrieve a value
const username = sessionStorage.getItem('username');
console.log(username); // => "alice"
// Remove a specific item
sessionStorage.removeItem('theme');
// Clear all session storage
sessionStorage.clear();
// Check length
console.log(sessionStorage.length); // => number of items
Storing Objects
Web Storage only stores strings. Use JSON.stringify and JSON.parse for objects:
const user = { id: 1, name: 'Alice', role: 'admin' };
// Store
sessionStorage.setItem('user', JSON.stringify(user));
// Retrieve
const stored = JSON.parse(sessionStorage.getItem('user'));
console.log(stored.name); // => "Alice"
sessionStorage Use Cases
- Multi-step form data (lost if user closes tab — intentional)
- Temporary UI state (expanded panels, scroll position)
- One-time tokens or nonces
- Shopping cart for anonymous users (session-scoped)
localStorage
localStorage persists data indefinitely — it survives tab closes, browser restarts, and system reboots. Data is only cleared by explicit code or the user clearing browser storage.
// Store a value
localStorage.setItem('theme', 'dark');
localStorage.setItem('language', 'en');
// Retrieve a value
const theme = localStorage.getItem('theme');
console.log(theme); // => "dark"
// Remove a specific item
localStorage.removeItem('language');
// Clear all localStorage for this origin
localStorage.clear();
// Iterate all keys
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
console.log(key, localStorage.getItem(key));
}
localStorage Use Cases
- User preferences (theme, language, font size)
- Cached API responses (with expiry logic)
- Draft content (auto-save)
- Feature flags
- Analytics data before sending to server
Implementing Expiry for localStorage
localStorage has no built-in TTL. Implement it manually:
function setWithExpiry(key, value, ttlMs) {
const item = {
value,
expiry: Date.now() + ttlMs
};
localStorage.setItem(key, JSON.stringify(item));
}
function getWithExpiry(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const item = JSON.parse(raw);
if (Date.now() > item.expiry) {
localStorage.removeItem(key);
return null;
}
return item.value;
}
// Store for 1 hour
setWithExpiry('cachedData', { users: [] }, 60 * 60 * 1000);
// Retrieve (returns null if expired)
const data = getWithExpiry('cachedData');
Cookies
Cookies are the oldest browser storage mechanism. Unlike Web Storage, cookies are sent to the server with every HTTP request, making them suitable for authentication tokens and server-side state.
// Set a cookie
document.cookie = 'username=alice; path=/; max-age=3600; SameSite=Lax';
// Set a secure cookie (HTTPS only)
document.cookie = 'token=abc123; path=/; Secure; HttpOnly; SameSite=Strict';
// Read all cookies (returns a single string)
console.log(document.cookie);
// => "username=alice; theme=dark"
// Parse cookies into an object
function getCookies() {
return document.cookie.split('; ').reduce((acc, pair) => {
const [key, value] = pair.split('=');
acc[decodeURIComponent(key)] = decodeURIComponent(value);
return acc;
}, {});
}
// Delete a cookie (set expiry in the past)
document.cookie = 'username=; path=/; max-age=0';
Cookie Attributes
| Attribute | Description |
|---|---|
max-age=N |
Expires after N seconds |
expires=date |
Expires at specific date |
path=/ |
Available to all paths on the domain |
domain=.example.com |
Available to subdomains |
Secure |
Only sent over HTTPS |
HttpOnly |
Not accessible via JavaScript (XSS protection) |
SameSite=Strict |
Never sent cross-site |
SameSite=Lax |
Sent on top-level navigation |
SameSite=None |
Always sent (requires Secure) |
Cookie Use Cases
- Authentication tokens (with
HttpOnlyandSecure) - Session identifiers
- CSRF tokens
- Cross-subdomain state sharing
- Server-side readable preferences
IndexedDB: For Large Data
For storing large amounts of structured data (files, blobs, complex objects), use IndexedDB:
const request = indexedDB.open('MyDatabase', 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
db.createObjectStore('users', { keyPath: 'id' });
};
request.onsuccess = (event) => {
const db = event.target.result;
// Add a record
const tx = db.transaction('users', 'readwrite');
tx.objectStore('users').add({ id: 1, name: 'Alice', email: '[email protected]' });
// Read a record
const readTx = db.transaction('users', 'readonly');
const getReq = readTx.objectStore('users').get(1);
getReq.onsuccess = () => console.log(getReq.result);
};
Comparison Table
| Feature | localStorage | sessionStorage | Cookie | IndexedDB |
|---|---|---|---|---|
| Capacity | ~5-10 MB | ~5-10 MB | ~4 KB | Hundreds of MB |
| Persistence | Permanent | Tab session | Configurable | Permanent |
| Sent to server | No | No | Yes (every request) | No |
| Accessible via JS | Yes | Yes | Yes (unless HttpOnly) | Yes |
| Structured data | No (strings only) | No (strings only) | No (strings only) | Yes |
| Expiry control | Manual | Automatic | Yes (max-age) | Manual |
| Synchronous API | Yes | Yes | Yes | No (async) |
Security Considerations
Never Store Sensitive Data in localStorage
localStorage is accessible to any JavaScript on the page. An XSS attack can steal everything in it:
// BAD: storing auth tokens in localStorage
localStorage.setItem('authToken', 'eyJhbGci...');
// GOOD: use HttpOnly cookies for auth tokens
// The server sets: Set-Cookie: token=...; HttpOnly; Secure; SameSite=Strict
// JavaScript cannot read HttpOnly cookies
Sanitize Before Storing
// Don't store raw user input without sanitization
function safeStore(key, value) {
if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {
value = JSON.stringify(value);
}
localStorage.setItem(key, String(value));
}
Storage Events
localStorage fires a storage event in other tabs when data changes — useful for cross-tab communication:
window.addEventListener('storage', (event) => {
console.log('Key changed:', event.key);
console.log('Old value:', event.oldValue);
console.log('New value:', event.newValue);
if (event.key === 'logout') {
// Another tab logged out — redirect this tab too
window.location.href = '/login';
}
});
// Trigger logout across all tabs
localStorage.setItem('logout', Date.now().toString());
Practical: A Simple Storage Wrapper
const storage = {
set(key, value, ttlMs = null) {
const item = { value, expiry: ttlMs ? Date.now() + ttlMs : null };
localStorage.setItem(key, JSON.stringify(item));
},
get(key) {
const raw = localStorage.getItem(key);
if (!raw) return null;
const { value, expiry } = JSON.parse(raw);
if (expiry && Date.now() > expiry) {
localStorage.removeItem(key);
return null;
}
return value;
},
remove(key) {
localStorage.removeItem(key);
},
clear() {
localStorage.clear();
}
};
// Usage
storage.set('user', { name: 'Alice' });
storage.set('cache', data, 5 * 60 * 1000); // 5 min TTL
const user = storage.get('user');
Comments