Why does a page forget everything on reload?
Every variable in your script lives in memory for one page load. Reload, close the tab or follow a link and the browser throws that memory away, then runs your script from the top as if for the first time. A visitor who picked the dark theme, or typed five garden jobs into a to-do list, sees it all reset.
Web storage gives each website a small place in the browser to keep text between visits. localStorage keeps it until the code or the visitor deletes it, across reloads, closed tabs and restarts. It is synchronous and needs no server, which makes it the simplest way to remember preferences and small lists.
Storage belongs to the origin: the scheme, domain and port together. https://plainfinch.example and http://localhost:5500 each have their own separate storage, and one site can never read another's.


localStorage stays in the browser. The first visit writes the value; the next visit reads it back and rebuilds the page from it.How do setItem, getItem and removeItem work?
Data is stored as key and value pairs. localStorage.setItem("theme", "dark") saves the value "dark" under the key "theme", replacing anything already there. localStorage.getItem("theme") reads it back. localStorage.removeItem("theme") deletes that one key, and localStorage.clear() deletes every key for your site, which is rarely what you want on a shared domain.
Reading a key that was never saved gives null. That is the normal state on a first visit, so the code that reads must expect it. In the demo, the comparison localStorage.getItem("theme") === "dark" is false for null, so a first visit keeps the default theme.
Open DevTools, go to the Application panel in Chrome or Storage in Firefox, and choose Local Storage. You can see every key, edit values and delete them, which is the fastest way to test a first visit again.
localStorage.setItem("theme", "dark");
console.log(localStorage.getItem("theme")); // "dark"
localStorage.removeItem("theme");
console.log(localStorage.getItem("theme")); // nullWhy does everything come back as a string?
Storage keeps strings only. Whatever you pass to setItem is converted with String() first. The number 3 comes back as "3", true comes back as "true", and an object turns into the useless "[object Object]". An array becomes its items joined with commas, so a job containing a comma splits into two on the way back.
That conversion causes quiet bugs. if (localStorage.getItem("darkMode")) is true for "false" as well as "true", because any non-empty string is truthy. Compare against the exact string you saved, or store a clear word such as "dark" or "light", as the demo does.
localStorage.setItem("visits", 3);
console.log(typeof localStorage.getItem("visits")); // "string"
localStorage.setItem("darkMode", false);
if (localStorage.getItem("darkMode")) {
console.log("Runs, because \"false\" is a non-empty string");
}How do you save arrays and objects?
Convert them to JSON text on the way in and back on the way out. JSON.stringify(todos) turns an array of jobs into text like ["Water beans","Weed paths"], and JSON.parse turns that text back into a real array. The same works for objects, such as a settings object with several preferences.
Save after every change to the data, right where the change happens. A common pattern is a small save() function called by add, toggle and remove, so the stored copy never falls behind the array in memory.
JSON keeps strings, numbers, booleans, null, arrays and plain objects. Dates become strings, and functions and class methods are dropped. Rebuild those after loading if you need them.
const todos = ["Water beans", "Weed paths"];
localStorage.setItem("todos", JSON.stringify(todos));
const loaded = JSON.parse(localStorage.getItem("todos"));
console.log(loaded.length); // 2How do you read safely when nothing is saved yet?
Every load has to handle three cases: nothing saved, good data saved, and something broken saved, such as text from an older version of your code or a value edited by hand in DevTools. Put the reading in one function that always returns usable data.
JSON.parse(null) returns null, so ?? [] turns a missing key into an empty array. Invalid JSON makes JSON.parse throw, and the try...catch turns that into the same fallback instead of a blank page. The rest of the code can then assume todos is an array.
function loadTodos() {
try {
return JSON.parse(localStorage.getItem("todos")) ?? [];
} catch {
return [];
}
}
const todos = loadTodos();
console.log(Array.isArray(todos)); // trueWhat if storage is unavailable?
Accessing storage can throw. Some browsers block it when a visitor disables site data or cookies, some privacy modes limit it, and a page inside a sandboxed frame may have no access at all. Writing also throws a QuotaExceededError when the site's storage is full.
Wrap reads and writes in try...catch so the page keeps working without memory. The theme still switches; it is just not remembered. A visitor with storage switched off should never see a broken page because of a feature that is a convenience.
Keep the wrapping in one or two small helper functions so the rest of the code stays readable.
function save(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.warn("Could not save", key, error.name);
}
}How does sessionStorage differ?
sessionStorage has the same methods and the same string-only rule. The difference is lifetime and reach. Its data lasts as long as the tab stays open: it survives reloads, but closing the tab deletes it, and each tab gets its own separate copy.
Use it for state that belongs to one visit in one tab, such as a half-filled multi-step form or the scroll position of a long list. Use localStorage for things the visitor expects the site to remember next week, such as a theme, a language choice or a saved list.
localStorage: shared by all tabs of the site, kept until deleted.sessionStorage: one tab only, deleted when that tab closes.- Both: strings only, about 5 MB per site, synchronous, readable by any script on the page.
What should never go in storage?
Each site gets roughly 5 MB, counted in characters. That is plenty for preferences and lists, but too small for images or big datasets; use IndexedDB or a server for those. Large values also slow the page, because every read and write blocks the main thread.
Any JavaScript running on your page can read localStorage, including third-party scripts and code injected through a cross-site scripting bug. Never store passwords, payment details, or login and API tokens there. Sign-in sessions belong in cookies set by the server with the HttpOnly flag, which scripts cannot read.
Storage is also visible to anyone using the same browser profile. On a shared computer, a saved to-do list or search history is readable by the next person. Keep personal data to what the feature needs, and offer a clear way to remove it with removeItem.
How do you test and read storage code?
Test three visits on purpose. First, delete the keys in DevTools and reload to see a first visit. Second, use the page, reload, and confirm the data comes back. Third, edit a value in DevTools into nonsense such as [oops and reload; the page should fall back cleanly instead of breaking.
When an AI writes storage code, look for setItem with an object or array and no JSON.stringify, JSON.parse with no fallback or try...catch, keys spelt differently in the save and the load, and tokens or passwords being saved. Those four account for most storage bugs in generated code.
Field notes
- Page variables vanish on reload, but
localStoragekeeps data between visits. setItemsaves a value under a key andgetItemreads it back.- Storage holds strings only, so use JSON for arrays and objects.
- Always read with a fallback for when nothing has been saved yet.
Easy to get wrong
Storage converts values to strings. An array becomes comma-joined text and an object becomes "[object Object]", so the structure is lost.
On a first visit getItem returns null, JSON.parse(null) is null, and push on null throws. Always provide a fallback.
Saved false comes back as the string "false", which is truthy. Compare with the exact value instead.
Any script on the page can read storage, so a stolen token gives an attacker the visitor's account.
Questions people ask
How long does localStorage last?
Until your code removes it or the visitor clears site data. Some browsers, Safari in particular, may delete storage for sites that have not been visited for several weeks, so treat it as a convenience and never the only copy of important data.
Can I see storage from another site?
No. Each origin has its own storage, and a page can only read keys saved by its own origin.
Does localStorage work with fetch data?
Yes. You can cache a JSON response with JSON.stringify and show it instantly on the next visit, then refresh it from the server.
Why does my storage disappear on localhost?
A different port is a different origin. localhost:5500 and localhost:3000 have separate storage, so starting your server on a new port looks like an empty store.
Can other tabs see my changes?
Yes, for localStorage. Other open tabs of the same site get a storage event when a key changes, which you can listen for to keep them in sync.