LearnFrenzy
Sign in Take a free test

Browser Storage and Web APIs · Lesson 37 of 40

localStorage, sessionStorage and Cookies

5 min read Updated Nov 7, 2026 Saurabh Samir

Websites remember things: your dark-mode choice, a half-filled form, the items in a guest cart, which lessons you've completed. Browsers give JavaScript several places to keep that data. Choosing the right one – and knowing what must never be stored in the browser – is part of building apps that are both convenient and secure.

In this lesson

  • localStorage and sessionStorage
  • Storing objects safely as JSON, with expiry
  • Cookies and how they differ
  • IndexedDB for larger data, and security rules

The options at a glance

localStoragesessionStorageCookiesIndexedDB
Lifetimeuntil cleareduntil the tab closesuntil expiry dateuntil cleared
Size~5 MB per site~5 MB per tab~4 KB eachlarge (hundreds of MB+)
Sent to server?nonoyes, with every requestno
APIsimple, synchronoussimple, synchronousstring-basedasynchronous, more complex
Typical usepreferences, drafts, cached dataone-tab wizard statesessions, auth (set by the server)offline apps, big datasets

localStorage basics

localStorage.setItem("theme", "dark");
console.log(localStorage.getItem("theme"));     // "dark"
console.log(localStorage.getItem("missing"));   // null

localStorage.setItem("visits", 3);              // stored as the string "3"
const visits = Number(localStorage.getItem("visits")) + 1;

localStorage.removeItem("theme");
localStorage.clear();                           // removes everything for this site

Storage is per origin (protocol + domain + port): https://learnfrenzy.com can't read another site's data. sessionStorage has exactly the same methods but is cleared when the tab closes and isn't shared between tabs.

Storing objects: JSON

Storage only holds strings. Store objects with JSON.stringify and read them with JSON.parse – inside try/catch, because stored data can be missing, old or corrupted:

const cart = [{ sku: "PEN", qty: 2 }, { sku: "BAG", qty: 1 }];
localStorage.setItem("cart", JSON.stringify(cart));

function readCart() {
  try {
    return JSON.parse(localStorage.getItem("cart")) ?? [];
  } catch {
    return [];                                  // corrupted value → start fresh
  }
}

A reusable storage helper with expiry

Real apps wrap storage in a small helper: JSON handling, a key prefix, an optional expiry time, and safety when storage is full or blocked (private mode, browser settings). The helper below takes the storage object as a parameter, so here it runs with a simple in-memory stand-in; in the browser you pass localStorage.

function createStore(storage, prefix = "lf:") {
  return {
    set(key, value, ttlMs) {
      const record = { value, expires: ttlMs ? Date.now() + ttlMs : null };
      try {
        storage.setItem(prefix + key, JSON.stringify(record));
        return true;
      } catch {
        return false;                    // quota exceeded or storage blocked
      }
    },
    get(key, fallback = null) {
      try {
        const record = JSON.parse(storage.getItem(prefix + key));
        if (!record) return fallback;
        if (record.expires && Date.now() > record.expires) {
          storage.removeItem(prefix + key);
          return fallback;
        }
        return record.value;
      } catch {
        return fallback;
      }
    },
  };
}

// In-memory stand-in with the same API as localStorage
const memory = new Map();
const fakeStorage = {
  getItem: (k) => memory.get(k) ?? null,
  setItem: (k, v) => memory.set(k, String(v)),
  removeItem: (k) => memory.delete(k),
};

const store = createStore(fakeStorage);
store.set("prefs", { theme: "dark", fontSize: 18 });
store.set("otp-sent", true, 50);                 // expires in 50 ms
fakeStorage.setItem("lf:broken", "{not json");

console.log(store.get("prefs"));
console.log(store.get("otp-sent"));
await new Promise((r) => setTimeout(r, 80));
console.log(store.get("otp-sent", false));       // expired
console.log(store.get("broken", "default"));     // corrupted → fallback
console.log([...memory.keys()]);

Output

{ theme: 'dark', fontSize: 18 }
true
false
default
[ 'lf:prefs', 'lf:broken' ]

In the browser: const store = createStore(localStorage);

Syncing between tabs

// Fires in OTHER tabs of the same site when localStorage changes
window.addEventListener("storage", (event) => {
  if (event.key === "lf:prefs") applyTheme(JSON.parse(event.newValue).value.theme);
});

Cookies

Cookies are small strings that the browser sends to the server with every request to that site. That makes them the right tool for server sessions – and the wrong tool for general client-side storage.

// Set a cookie from JavaScript (non-sensitive values only)
document.cookie = "lang=en; Max-Age=31536000; Path=/; SameSite=Lax; Secure";

// Read: document.cookie is one string "a=1; lang=en"
const cookies = Object.fromEntries(
  document.cookie.split("; ").filter(Boolean).map((c) => c.split("=").map(decodeURIComponent))
);
console.log(cookies.lang);

// Delete: set it again with Max-Age=0
document.cookie = "lang=; Max-Age=0; Path=/";
Cookie attributeMeaning
HttpOnlyJavaScript can't read it – protects session cookies from XSS (set by the server)
Securesent over HTTPS only
SameSite=Lax/Strictlimits sending on cross-site requests – helps against CSRF
Max-Age / Expireslifetime; without it the cookie ends with the browser session

IndexedDB in one paragraph

For large or structured data – offline notes, cached API results, files – use IndexedDB: an asynchronous, transactional database in the browser. Its raw API is verbose, so most developers use a small wrapper library such as idb or Dexie.

Security: what never goes in browser storage

  • Any JavaScript on your page – including a compromised third-party script – can read localStorage and sessionStorage. Never store passwords, card numbers or other secrets there.
  • Prefer HttpOnly, Secure, SameSite cookies set by the server for login sessions, rather than tokens in localStorage.
  • Treat stored data as untrusted input: validate it when you read it back.

Practice

Try it yourself

  1. Save a form draft: on every input event store the textarea's value in localStorage, and restore it when the page loads.

    Show solution
    const area = document.querySelector("#message");
    area.value = localStorage.getItem("draft:message") ?? "";
    area.addEventListener("input", () => localStorage.setItem("draft:message", area.value));
    document.querySelector("#contact-form").addEventListener("submit", () => {
      localStorage.removeItem("draft:message");     // clear once sent
    });

Quick check

What type does localStorage.getItem return?

A string, or null if the key doesn't exist.

localStorage vs sessionStorage?

localStorage persists until cleared and is shared across tabs; sessionStorage is per tab and cleared when the tab closes.

Why shouldn't login tokens go in localStorage?

Any script on the page, including injected XSS code, can read it; HttpOnly cookies can't be read by JavaScript.

Summary

  • localStorage persists, sessionStorage lasts one tab; both store strings per origin.
  • Store objects as JSON, read with try/catch, add expiry in a small helper.
  • Cookies travel with every request – use server-set HttpOnly cookies for sessions; never store secrets in the browser.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links