Skip to content
Happy Programming Guide
Start learning
JavaScript

JavaScript localStorage Explained

How to save data in the browser so it survives a refresh: setItem and getItem, storing objects with JSON, and the corrupted-value trap that breaks apps on load.

localStorage saves small pieces of data in the browser so they survive a refresh, a closed tab, and a restarted computer.

JavaScript
localStorage.setItem("name", "Ada");
localStorage.getItem("name");        // "Ada"
localStorage.removeItem("name");

Three methods and one important limitation: it only stores strings. Everything else on this page follows from that.

Everything becomes a string#

This catches everyone once:

JavaScript
localStorage.setItem("count", 5);
const count = localStorage.getItem("count");

console.log(count);        // "5"  — a string
console.log(count + 1);    // "51" — glued, not added
console.log(Number(count) + 1);  // 6

Booleans are worse, because the mistake is silent:

JavaScript
localStorage.setItem("done", false);
if (localStorage.getItem("done")) {
  // this ALWAYS runs — "false" is a non-empty string, which is truthy
}

Storing objects and arrays#

Convert to JSON on the way in and back on the way out.

JavaScript
const tasks = [
  { id: 1, text: "Learn storage", done: false },
];

localStorage.setItem("tasks", JSON.stringify(tasks));

const loaded = JSON.parse(localStorage.getItem("tasks"));
console.log(loaded[0].text);      // "Learn storage"

Forget JSON.stringify and the browser calls toString() on your array, which quietly stores "[object Object]". It does not error. You find out later when the load produces nonsense.

The trap: loading breaks the whole app#

This is the one worth learning properly, because it fails at startup and takes the page down with it.

JavaScript
const tasks = JSON.parse(localStorage.getItem("tasks"));   // fragile

Two ways that line breaks:

  • Nothing is stored yet. getItem returns null, and JSON.parse(null) gives null — so the next line calling .map() throws “Cannot read properties of null”.
  • The stored value is not valid JSON. A half-written value, something you edited by hand while testing, or a leftover from an earlier version of your code. JSON.parse throws a SyntaxError and your script stops before rendering anything.

Once that happens, the page is broken on every load until the user clears their site data — which they will not know to do. Guard it:

JavaScript
function load(key, fallback) {
  try {
    const raw = localStorage.getItem(key);
    if (raw === null) return fallback;
    const value = JSON.parse(raw);
    return value ?? fallback;
  } catch {
    localStorage.removeItem(key);   // it is unusable — clear it
    return fallback;
  }
}

const tasks = load("tasks", []);

A save/load pair worth copying#

JavaScript
const KEY = "todo-tasks-v1";

function save(tasks) {
  try {
    localStorage.setItem(KEY, JSON.stringify(tasks));
  } catch {
    // Storage full, or blocked in this browser. Not fatal — carry on.
  }
}

function load() {
  try {
    const raw = localStorage.getItem(KEY);
    return raw ? JSON.parse(raw) : [];
  } catch {
    localStorage.removeItem(KEY);
    return [];
  }
}

Note that setItem is in a try too. It throws if storage is full, and in some browsers it throws simply because the user has blocked site data. An app that crashes on save in private browsing is a real and avoidable bug.

This is exactly the pattern in the browser to-do list project.

What localStorage is not#

  • Not secure. Anyone can open DevTools and read or edit it. Never put passwords, tokens or anything you would not show the user in there.
  • Not synced. It lives in one browser on one device. Different browser, different data. Clearing site data deletes it.
  • Not shared between sites. Each origin gets its own. Your local test copy and the deployed site have separate storage.
  • Not a database. Roughly 5MB, no querying, and every read and write is synchronous — it blocks the page. Fine for settings and a few hundred items; wrong for thousands of records.
  • Not reliable. Treat everything you read back as possibly missing or possibly wrong.

The rest of the API#

JavaScript
localStorage.setItem("theme", "dark");
localStorage.getItem("theme");       // "dark", or null if absent
localStorage.removeItem("theme");
localStorage.clear();                // everything for this site
localStorage.length;                 // how many keys
localStorage.key(0);                 // the name of the first key

// Loop over everything
for (let i = 0; i < localStorage.length; i++) {
  const k = localStorage.key(i);
  console.log(k, localStorage.getItem(k));
}

Be careful with clear() — it wipes everything your site stored, not just your key.

sessionStorage#

Identical API, different lifetime: sessionStorage is cleared when the tab closes, and it is not shared between tabs.

JavaScript
sessionStorage.setItem("step", "3");

Use it for things that should not outlive the visit — a multi-step form’s progress, a temporary filter.

Reacting to changes in another tab#

JavaScript
window.addEventListener("storage", (event) => {
  if (event.key === "todo-tasks-v1") {
    render(JSON.parse(event.newValue || "[]"));
  }
});

The storage event fires in other tabs on the same site, not the one that made the change. It is a simple way to keep two open tabs in step.

Questions people ask#

How much can I store?

Around 5MB per site in most browsers. Exceeding it throws a QuotaExceededError on setItem, which is why the save function catches.

Does localStorage work offline?

Yes — it is local to the browser and needs no network at all.

Should I use cookies instead?

Cookies are sent to the server on every request, which makes them right for sessions and authentication and wrong for app state. localStorage never leaves the browser.

What about IndexedDB?

A proper browser database — asynchronous, much larger, supports indexes. Worth reaching for when you outgrow localStorage, which for most small projects is never.

Why is my data gone?

Usual causes: the user cleared site data, they are in private browsing, you are on a different origin (localhost versus a deployed URL, or http versus https), or you changed the key name.

Can I store an image?

Only as a base64 data URL, and it will eat your quota quickly. For anything more than an icon, store a URL and let the browser cache the file.

Where to go next#

Try a projectBuild a browser to-do list

Keep reading

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *