Difficulty: Intermediate
Web storage APIs provide mechanisms for storing data in the browser. There are three main options: localStorage for persistent storage, sessionStorage for tab-scoped temporary storage, and cookies for small pieces of data that can be sent to the server with every HTTP request. Each has different characteristics regarding capacity, persistence, scope, and security, and choosing the right one depends on your use case.
localStorage provides persistent key-value storage that survives browser restarts. Data stored in localStorage has no expiration and remains until explicitly cleared by JavaScript or the user. It offers 5-10 MB of storage per origin (protocol + domain + port) and is synchronous - all operations block the main thread, though this is rarely noticeable for typical use. The API is simple: setItem(key, value), getItem(key), removeItem(key), clear(), and key(index). Values are always strings, so objects must be serialized with JSON.stringify() before storing and parsed with JSON.parse() when retrieved.
sessionStorage has the same API as localStorage but differs in scope and persistence. Data in sessionStorage is scoped to the browser tab - each tab has its own independent session storage even for the same origin. The data is cleared when the tab is closed. This makes sessionStorage ideal for temporary data like form progress, one-time tokens, or tab-specific UI state that should not persist across sessions or leak between tabs.
Cookies are the oldest browser storage mechanism, originally designed to send small pieces of data to the server with every HTTP request. They are limited to about 4KB per cookie and have additional attributes: expires or max-age for lifespan, path and domain for scope, secure to restrict to HTTPS, httpOnly to prevent JavaScript access (server-set only), and sameSite to control cross-site sending. While cookies are essential for session management and server-side authentication, localStorage is preferred for client-only data due to its larger capacity and simpler API.
Security is a critical concern with browser storage. localStorage and sessionStorage are vulnerable to Cross-Site Scripting (XSS) attacks - if an attacker injects malicious JavaScript into your page, they can read everything in storage. Never store sensitive tokens, passwords, or personal data in web storage without understanding this risk. Cookies with the httpOnly flag are safer for authentication tokens because JavaScript cannot access them, but they are vulnerable to Cross-Site Request Forgery (CSRF) unless the sameSite attribute is properly set.
When comparing the three options: use localStorage for user preferences, cached data, and non-sensitive state that should persist. Use sessionStorage for temporary data that should not survive tab closure. Use cookies for authentication tokens (with httpOnly and secure flags), server-needed data, and cross-subdomain sharing. For large amounts of structured data, consider IndexedDB, which offers asynchronous access and much larger storage limits.
// Store simple values
localStorage.setItem('theme', 'dark');
localStorage.setItem('language', 'en');
// Retrieve values
const theme = localStorage.getItem('theme');
console.log(theme);
// Store objects (must serialize)
const user = { name: 'Alice', age: 25, preferences: { notifications: true } };
localStorage.setItem('user', JSON.stringify(user));
// Retrieve and parse objects
const stored = JSON.parse(localStorage.getItem('user'));
console.log(stored.name);
console.log(stored.preferences.notifications);
// Check if key exists
if (localStorage.getItem('token') === null) {
console.log('No token found');
}
// Remove a single item
localStorage.removeItem('language');
// Get number of stored items
console.log('Items stored:', localStorage.length);
// Iterate over all keys
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
console.log(`${key}: ${localStorage.getItem(key)}`);
}
localStorage stores string key-value pairs persistently. Objects must be serialized with JSON.stringify before storing and deserialized with JSON.parse when retrieved. Data persists until explicitly removed.
// sessionStorage has the same API as localStorage
sessionStorage.setItem('formStep', '2');
sessionStorage.setItem('tempData', JSON.stringify({ draft: 'Hello...' }));
console.log(sessionStorage.getItem('formStep'));
// Key differences demonstration
console.log('=== Scope Differences ===');
// localStorage: shared across all tabs for same origin
localStorage.setItem('shared', 'visible in all tabs');
// sessionStorage: isolated per tab
sessionStorage.setItem('tabOnly', 'only in this tab');
// Opening a new tab to the same page gets:
// - localStorage.getItem('shared') => 'visible in all tabs'
// - sessionStorage.getItem('tabOnly') => null (separate session)
// Practical use: multi-step form
function saveFormProgress(step, data) {
sessionStorage.setItem('formStep', step.toString());
sessionStorage.setItem('formData', JSON.stringify(data));
}
function loadFormProgress() {
const step = parseInt(sessionStorage.getItem('formStep') || '1');
const data = JSON.parse(sessionStorage.getItem('formData') || '{}');
return { step, data };
}
saveFormProgress(3, { name: 'Alice', email: 'a@b.com' });
console.log(loadFormProgress());
sessionStorage is scoped to the individual browser tab and cleared when the tab closes. This makes it ideal for temporary state like form progress that should not persist or leak between tabs.
// Set a cookie
document.cookie = 'username=Alice; path=/; max-age=86400'; // 1 day
document.cookie = 'theme=dark; path=/; max-age=2592000'; // 30 days
// Read all cookies (returns a single string)
console.log(document.cookie);
// Helper: parse cookies into an object
function parseCookies() {
return document.cookie.split('; ').reduce((acc, pair) => {
const [key, value] = pair.split('=');
if (key) acc[key] = decodeURIComponent(value);
return acc;
}, {});
}
const cookies = parseCookies();
console.log(cookies.username);
console.log(cookies.theme);
// Delete a cookie by setting max-age to 0
function deleteCookie(name) {
document.cookie = `${name}=; path=/; max-age=0`;
}
deleteCookie('username');
console.log('After deletion:', parseCookies());
// Secure cookie (cannot set httpOnly from JS - server only)
// document.cookie = 'session=abc; path=/; secure; samesite=strict';
Cookies use a string-based API. document.cookie looks like a property but setting it adds/updates a single cookie rather than replacing all cookies. max-age sets expiration in seconds. httpOnly cookies can only be set by the server.
// Safe storage wrapper with error handling
const storage = {
get(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch {
return defaultValue;
}
},
set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (e) {
// QuotaExceededError when storage is full
console.error('Storage full or unavailable:', e.message);
return false;
}
},
remove(key) {
localStorage.removeItem(key);
},
clear() {
localStorage.clear();
}
};
// Usage
storage.set('settings', { theme: 'dark', fontSize: 16 });
console.log(storage.get('settings'));
console.log(storage.get('nonexistent', { fallback: true }));
// Listen for storage changes from other tabs
window.addEventListener('storage', (e) => {
console.log(`Key "${e.key}" changed from "${e.oldValue}" to "${e.newValue}"`);
});
A wrapper handles JSON serialization, provides default values, catches quota errors, and handles unavailable storage gracefully. The 'storage' event fires when another tab modifies localStorage, enabling cross-tab communication.
localStorage, sessionStorage, Cookies, JSON Serialization, Same-Origin Policy, XSS, CSRF