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

Storage Options at a Glance
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.

localStorage API
// 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.

sessionStorage Use Case
// 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.

Secure Cookie Configuration
// 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 HTTPS
  • sameSite=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.

IndexedDB Example
// 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.