A dark theme picked yesterday, a cart abandoned last week, a banner already dismissed: this information has to survive the browser closing, without an account and without a server.
Browsers set aside a small space per site for exactly that, with a deliberately tiny interface: read, write, wipe, and nothing more.
Definition
localStorage is a key-value store attached to an origin, meaning the trio of protocol, domain and port. Data stays there with no expiry date, until a script or the visitor wipes it.
localStorage.setItem("theme", "dark");
console.log(localStorage.getItem("theme")); // "dark"
console.log(localStorage.getItem("unknown")); // null
localStorage.removeItem("theme");
localStorage.clear(); // the whole space for the originA missing key returns null, never undefined: the distinction matters as soon as the value you read is used as a condition.
Everything becomes text
The store only holds strings. A value of any other type is converted silently, which produces surprises when you read it back.
localStorage.setItem("loggedIn", true);
console.log(localStorage.getItem("loggedIn") === true); // false
console.log(typeof localStorage.getItem("loggedIn")); // "string"
localStorage.setItem("cart", JSON.stringify({ lines: 3 }));
const cart = JSON.parse(localStorage.getItem("cart"));
console.log(cart.lines + 1); // 4An object therefore goes through JSON on the way in and on the way out. The string "false" read back stays on the Truthy and falsy true side, which is enough to flip an entire condition.
Access is synchronous: every read and every write blocks the main thread. Storing a large object on each keystroke is enough to make an interface stutter.
What does not belong in it
Every script the page loads can read this space, including a third-party script or one injected through a vulnerability. Three categories should stay out.
- An authentication token: readable by any script, where a Cookie marked
HttpOnlyis not. - Personal data: it stays on the machine long after the visit, sometimes on a shared computer.
- Large volumes: the limit sits around five megabytes per origin, and a local database belongs in IndexedDB.
Writing can also fail: quota exceeded, private browsing, storage turned off. A call wrapped in a try block keeps a whole page from dying on that one line.
Frequently asked questions
How do I know another tab changed a value?
The browser fires a storage event on the window, carrying the old and the new value. One useful quirk: it only fires in the other documents of the same origin, never in the one that just wrote. That is what lets you propagate a sign-out across every open tab.
Does the data ever disappear?
There is no expiry date, but nothing guarantees permanence either. Visitors can clear their browser, and some browsers wipe script-written storage on their own after a long stretch without a visit. Treat this space as a convenience, never as the only copy of a piece of data.
localStorage or sessionStorage?
Both share the exact same interface, only the lifetime changes. Pick the first one for a preference that has to hold from one visit to the next, and sessionStorage for data that only makes sense inside the current tab, such as the step of a multi-page form.