Skip to content
elephantoo

localStorage & sessionStorage

Lesson 16 of 34 12 min read

Persist data in the browser, store objects as JSON and handle errors and the storage event.


Refresh a normal web page and every variable is gone. The Web Storage API gives each website a small key–value store in the browser that survives reloads — perfect for preferences, drafts, and progress tracking. (Elephantoo itself remembers your completed lessons this way!)

Two stores, same API#

localStoragesessionStorage
LifetimeUntil deleted by code or the userUntil the tab is closed
Shared between tabs?Yes, across all tabs of the same originNo — one per tab
Typical useTheme, language, progress, cached dataOne-off wizard steps, per-tab state

Both are scoped to the origin (protocol + domain + port): https://elephantoo.com can't read another site's storage. Browsers typically allow around 5 MB per origin.

The basic methods#

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

localStorage.removeItem("theme");
console.log(localStorage.length);             // number of keys stored
localStorage.clear();                         // remove everything for this origin

Open DevTools → Application (Chrome/Edge) or Storage (Firefox) → Local Storage to see and edit stored values.

Web Storage is a browser API; it doesn't exist in Node.js. Run these examples in the browser console.

Everything is a string#

JavaScript
localStorage.setItem("count", 5);
console.log(typeof localStorage.getItem("count")); // "string"

localStorage.setItem("user", { name: "Ada" });
console.log(localStorage.getItem("user")); // "[object Object]" 😱

Use JSON for anything that isn't a plain string:

JavaScript
const prefs = { theme: "dark", fontSize: 18, favourites: ["js", "react"] };
localStorage.setItem("prefs", JSON.stringify(prefs));

const saved = JSON.parse(localStorage.getItem("prefs"));
console.log(saved.favourites[1]); // "react"

A safe storage helper#

Reading storage can fail in real life: the key might be missing, the JSON might be corrupted by an old version of your app, storage can be full, and some privacy modes throw on access. Wrap it once and use the helper everywhere:

JavaScript
const storage = {
  get(key, fallback = null) {
    try {
      const raw = localStorage.getItem(key);
      return raw === null ? fallback : JSON.parse(raw);
    } catch {
      return fallback; // bad JSON or storage unavailable
    }
  },
  set(key, value) {
    try {
      localStorage.setItem(key, JSON.stringify(value));
      return true;
    } catch (err) {
      console.warn(`Could not save "${key}":`, err.name); // e.g. QuotaExceededError
      return false;
    }
  },
  remove(key) {
    try {
      localStorage.removeItem(key);
    } catch {
      /* ignore */
    }
  },
};

storage.set("progress", { javascript: ["introduction", "variables-and-types"] });
console.log(storage.get("progress").javascript.length); // 2
console.log(storage.get("nothing-here", []));           // []

Example: remember dark mode#

HTML
<button id="theme-toggle">Toggle theme</button>
JavaScript
const toggle = document.querySelector("#theme-toggle");

function applyTheme(theme) {
  document.documentElement.dataset.theme = theme; // <html data-theme="dark">
  toggle.textContent = theme === "dark" ? "☀️ Light mode" : "🌙 Dark mode";
}

const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
let theme = localStorage.getItem("theme") ?? (prefersDark ? "dark" : "light");
applyTheme(theme);

toggle.addEventListener("click", () => {
  theme = theme === "dark" ? "light" : "dark";
  localStorage.setItem("theme", theme);
  applyTheme(theme);
});

The saved choice wins; otherwise we follow the operating system's preference.

Example: auto-save a draft#

HTML
<textarea id="comment" rows="5"></textarea>
<p id="saved-note"></p>
JavaScript
const box = document.querySelector("#comment");
const note = document.querySelector("#saved-note");
const KEY = "draft:comment";

box.value = sessionStorage.getItem(KEY) ?? "";

let timer;
box.addEventListener("input", () => {
  clearTimeout(timer);
  timer = setTimeout(() => {
    sessionStorage.setItem(KEY, box.value);
    note.textContent = `Draft saved at ${new Date().toLocaleTimeString()}`;
  }, 500); // debounce: save 0.5 s after typing stops
});

Using sessionStorage means the draft survives an accidental reload but not closing the tab. Switch to localStorage if it should survive longer.

Versioning and expiry#

Data outlives your code. Add a version (and, if needed, an expiry time) so a future release can safely ignore stale data:

JavaScript
function saveWithExpiry(key, value, ttlMs) {
  localStorage.setItem(key, JSON.stringify({ v: 1, value, expires: Date.now() + ttlMs }));
}

function loadWithExpiry(key) {
  try {
    const item = JSON.parse(localStorage.getItem(key));
    if (!item || item.v !== 1 || Date.now() > item.expires) {
      localStorage.removeItem(key);
      return null;
    }
    return item.value;
  } catch {
    return null;
  }
}

saveWithExpiry("courses-cache", [{ slug: "javascript" }], 60 * 60 * 1000); // 1 hour
console.log(loadWithExpiry("courses-cache"));

Namespacing keys ("elephantoo:theme", "draft:comment") also avoids collisions with libraries.

Syncing tabs with the storage event#

When one tab changes localStorage, other tabs of the same origin receive a storage event:

JavaScript
window.addEventListener("storage", (event) => {
  if (event.key === "theme") {
    console.log(`Theme changed in another tab: ${event.oldValue} → ${event.newValue}`);
    applyTheme(event.newValue);
  }
});

The tab that made the change does not get the event.

Limits and alternatives#

  • Synchronous: every call blocks the main thread. Fine for small values; don't store megabytes or call it in tight loops.
  • Strings only, ~5 MB: for large or structured data (offline apps, files), use IndexedDB (via a helper library such as idb).
  • Not secure: any JavaScript on the page can read it. Never store passwords, credit card numbers or long-lived auth tokens. Use HttpOnly cookies for sessions — JavaScript can't read those at all.
  • Cookies are sent to the server with every request; Web Storage never is.
  • Users can clear storage anytime. Treat it as a cache or convenience, not a database.

Common mistakes#

  • Storing objects without JSON.stringify.
  • Calling JSON.parse on null or corrupt data without a try/catch or fallback.
  • Using storage in code that also runs in Node (e.g. server-side rendering) — check typeof localStorage !== "undefined".
  • Assuming the storage event fires in the same tab.

What's next#

You've finished the browser module! Next, go deeper into the language itself, starting with one of its most misunderstood features: this, plus call, apply and bind.

Check your understanding

Quick quiz

0/3 answered
  1. 1.What happens when you run localStorage.setItem("prefs", { theme: "dark" })?

  2. 2.How long does sessionStorage data last?

  3. 3.Which of these should you NOT store in localStorage?

Finished reading?

Mark this lesson complete to track your progress.