Web Platform APIs · 5 / 10
lesson 5

Storage

localStorage, sessionStorage, cookies — quotas, JSON-in-a-string, and what each one is actually for.

~ 13 min read·lesson 5 of 10
0 / 10

You're saving a small piece of state — the user's chosen theme, a draft they're typing, the last filter they used. You don't need a database. You don't need a server round-trip. You just need a place to put a string that survives the page reload. The browser has three of those, with different lifetimes and different gotchas. Pick the right one and the next 200 lines of code write themselves.

This lesson is the cheap end of storage. The next one is IndexedDB — for when "a string under 5 MB" stops being enough.

localStorage

localStorage is a key-value store. Keys and values are both strings. It survives page reloads, tab closes, and browser restarts. It's scoped to the origin (the protocol + host + port), so https://shop.example.com and https://blog.example.com see different stores.

theme.js
localStorage.setItem("theme", "dark");
const saved = localStorage.getItem("theme");   /* "dark" */
localStorage.removeItem("theme");
localStorage.clear();                           /* nuclear option */

The whole API is setItem, getItem, removeItem, clear, plus length and key(i) for iteration. getItem returns null when the key is missing — handy as a default fallback.

Tip

localStorage is synchronous. Reading or writing blocks the main thread. For a few keys this is fine; for a hot loop or a large blob, reach for IndexedDB instead — it's async and built for volume.

Storing objects

You can only put strings in. So when you want to store an object, you serialize on the way in and parse on the way out.

draft.js
function saveDraft(draft) {
localStorage.setItem("draft", JSON.stringify(draft));
}

function loadDraft() {
const raw = localStorage.getItem("draft");
if (raw === null) return null;
return JSON.parse(raw);
}

A few traps in this little dance:

  • JSON.stringify quietly drops things. undefined values, functions, and Date objects become weird (dates turn into strings; you have to convert them back manually).
  • JSON.parse throws on bad data. If something else wrote a non-JSON value to that key — or your serialization code crashed mid-write — parse blows up. For non-critical data, wrap it in a try/catch.
defensive.js
function loadDraft() {
const raw = localStorage.getItem("draft");
if (raw === null) return null;
try {
  return JSON.parse(raw);
} catch {
  localStorage.removeItem("draft");   /* corrupted — drop it */
  return null;
}
}

This pattern — get, parse, fall back if it's broken — is what most "use localStorage like a small object store" libraries are doing under the hood.

check your understanding
You store localStorage.setItem("user", { name: "ada" }) directly. What's actually written to disk?

sessionStorage

sessionStorage has the exact same API as localStorage. The only difference is its lifetime:

  • localStorage persists across tabs, reloads, and browser restarts.
  • sessionStorage is per-tab — when the tab closes, it's gone. Reload doesn't clear it; closing does.

Use sessionStorage when the data only matters for the current visit: a wizard's in-progress answers, a one-off "saw the announcement banner" flag, anything you'd lose if the user opened a second tab and you don't want them to see it there.

wizard.js
sessionStorage.setItem("wizardStep", "2");
/* User closes the tab → next visit starts from step 1. */

Same API, same gotchas around strings — everything from localStorage carries over.

sessionStoragewhile tab is open localStorageforever* cookieswhatever Max-Age says
The three storage layers, by lifetime: cookies survive the longest, localStorage stays until cleared, sessionStorage dies when the tab closes.

Where cookies fit

Cookies are the third option, and they're a different shape. They're sent with every request to the same origin — which is what makes them the natural home for server-readable state like session IDs and authentication tokens. localStorage is invisible to the server; cookies are not.

Two practical takeaways:

  • For "remember the user is logged in", servers set a cookie with HttpOnly and Secure flags. Your JavaScript shouldn't (and often can't) read it. That's by design — it protects against script-based theft.
  • For everything else — UI preferences, drafts, "show me the tour again" toggles — localStorage is the right choice. Don't put data in cookies that the server doesn't need; you'll just inflate every request.
Watch out

Don't put auth tokens in localStorage. Any script on your origin (a third-party widget, a vulnerability, a misbehaving extension) can read everything in there. Cookies marked HttpOnly are safer because JavaScript literally cannot see them.

Limits and failure modes

localStorage has a quota — usually around 5 MB per origin. When you exceed it, setItem throws a QuotaExceededError. Two situations actually trigger this in real life:

  1. You're storing a lot of structured data. (At 5 MB, switch to IndexedDB.)
  2. The user is in private/incognito mode. Some browsers cap private-mode storage tightly or simulate "quota full" the moment you write.

Defensive pattern when storing anything user-facing:

quota.js
function trySave(key, value) {
try {
  localStorage.setItem(key, value);
  return true;
} catch {
  return false;   /* over quota or storage disabled */
}
}

Returning false lets the caller decide what to do — show a "couldn't save" hint, fall back to in-memory state, or trigger a cleanup. Throwing inside setItem is one of the few times this API surprises you.

check your understanding
You write data to localStorage in one tab. The user opens a second tab to the same site. What does the second tab see?

Try it yourself

check your understanding
You're saving the in-progress answers of a multi-step form. The user expects the form to be empty if they close and reopen the tab, but to survive a page reload. Which storage do you use?
check your understanding
You're storing a small JS object as JSON. JSON.parse throws because something else corrupted that key. What's the right defensive pattern?
check your understanding
Why is putting auth tokens in localStorage discouraged?
← prevnext lesson →
KeepLearningcertificate
for completing
Web Platform APIs
0 of 10 read