Web Storage lets a page keep small pieces of data in the visitor's own browser: a chosen theme, a half-written note, a game score. It works like a tiny object that survives page reloads, and it is far simpler than cookies. This lesson covers the two kinds of storage, the handful of methods, the strings-only rule that sends everything through JSON, and the limits to know about.
A note about the examples. Your code in the Try It editor runs on this site, so the storage it uses is the same storage this site uses for your themes, notes, and saved drafts. Every example here uses keys that start with demo_ and cleans up after itself. Never call localStorage.clear() here: it would erase your saved notes and settings for this whole site.
| Storage | Lasts | Shared |
|---|---|---|
localStorage | stays until it is deleted: closing the browser or restarting the computer does not remove it | every tab and window of the same site share it |
sessionStorage | is erased when the tab is closed | each tab has its own, even for the same site |
Both have exactly the same methods, so everything below works for either. Storage belongs to one origin (the protocol, host, and port): a page on one site can never read what another site stored.
| Method or property | Does |
|---|---|
setItem(key, value) | save a value (adds the key or replaces the old value) |
getItem(key) | read a value, or null if the key does not exist |
removeItem(key) | delete one key |
key(index) | the name of the key at a position |
length | how many keys there are |
localStorage.setItem("demo_name", "Ava");
console.log(localStorage.getItem("demo_name"));
console.log(localStorage.getItem("demo_missing"));
localStorage.removeItem("demo_name");
console.log(localStorage.getItem("demo_name"));
There is also a shorthand that treats storage like an object (localStorage.demo_name = "Ava"), but setItem and getItem are clearer and safer, so use them.
Storage keeps only strings. Anything else is turned into text on the way in, and comes back as text:
localStorage.setItem("demo_count", 5);
const value = localStorage.getItem("demo_count");
console.log(typeof value);
console.log(value + 1);
console.log(Number(value) + 1);
localStorage.removeItem("demo_count");
"[object Object]". Convert objects and arrays with JSON.stringify on the way in and JSON.parse on the way out.
const settings = { theme: "dark", fontSize: 18, tags: ["a", "b"] };
localStorage.setItem("demo_settings", JSON.stringify(settings));
const loaded = JSON.parse(localStorage.getItem("demo_settings"));
console.log(loaded.theme, loaded.fontSize + 2, loaded.tags.length);
localStorage.removeItem("demo_settings");
Because getItem returns null for a missing key, and JSON.parse throws on bad text, it is worth wrapping the pattern once:
function save(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
function load(key, fallback) {
const text = localStorage.getItem(key);
if (text === null) return fallback;
try {
return JSON.parse(text);
} catch (error) {
return fallback;
}
}
console.log(load("demo_score", 0));
save("demo_score", 42);
console.log(load("demo_score", 0));
localStorage.setItem("demo_bad", "{oops");
console.log(load("demo_bad", "recovered"));
localStorage.removeItem("demo_score");
localStorage.removeItem("demo_bad");
localStorage.length and localStorage.key(i) let you loop through every key. Because this site stores its own keys too, the example filters to the demo_ ones:
localStorage.setItem("demo_b", "2");
localStorage.setItem("demo_a", "1");
const mine = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith("demo_")) mine.push(key);
}
console.log(mine.sort());
localStorage.removeItem("demo_a");
localStorage.removeItem("demo_b");
Here a button counts clicks and the count survives a reload. Close the Result pane and run it again: the number is still there. The Reset button clears it:
<p>Clicks: <strong id="n">0</strong></p>
<button id="add">Click me</button>
<button id="reset">Reset</button>
<script>
const n = document.getElementById("n");
let count = Number(localStorage.getItem("demo_clicks")) || 0;
n.textContent = count;
document.getElementById("add").addEventListener("click", function () {
count++;
n.textContent = count;
localStorage.setItem("demo_clicks", count);
});
document.getElementById("reset").addEventListener("click", function () {
count = 0;
n.textContent = 0;
localStorage.removeItem("demo_clicks");
});
</script>
Clicks: 0
sessionStorage is used the same way. The only difference is that the data disappears when the tab closes, which suits things like the step of a form the person is partway through:
sessionStorage.setItem("demo_step", "2");
console.log(sessionStorage.getItem("demo_step"));
console.log(localStorage.getItem("demo_step"));
sessionStorage.removeItem("demo_step");
QuotaExceededError, so wrap big saves in try...catchWhen one tab changes localStorage, every other tab of the same site gets a "storage" event, which lets tabs stay in sync (a theme change, a logout). The tab that made the change does not get it:
window.addEventListener("storage", function (event) {
console.log(event.key, "changed from", event.oldValue, "to", event.newValue);
});
What does this log?
localStorage.setItem("demo_n", 5);
console.log(typeof localStorage.getItem("demo_n"));
localStorage.removeItem("demo_n");
string. Storage keeps only text, so the number 5 comes back as the string "5". Convert with Number(...) before doing math.What does this log?
const user = { name: "Ava" };
localStorage.setItem("demo_u", user);
console.log(localStorage.getItem("demo_u"));
localStorage.removeItem("demo_u");
[object Object]. An object is converted with String(...), which gives that useless text. Use JSON.stringify(user) when saving.Name one difference between localStorage and cookies.
localStorage is not sent to the server with each request, it holds much more data (about 5 MB versus 4 KB), it has a simpler API, and it does not expire on its own.sessionStorage and explain the difference you see