Choose by lifetime, not by API

Both localStorage and sessionStorage store string keys and values for a web origin. They expose the same basic operations: setItem, getItem, removeItem, and clear. The important choice is what should happen when the user reloads a page, opens another tab, or returns later.

Use localStorage for small preferences that should usually remain available across browser sessions, such as a chosen theme. Pages from the same origin can access the same localStorage data, including pages in different tabs. The browser or user can still remove it, so it is not a permanent record. Use sessionStorage for temporary state tied to one tab, such as progress through a short, recoverable form. It normally survives a reload in that tab but is cleared when that tab’s page session ends. A newly opened tab should not be treated as a reliable way to share sessionStorage state.

Know what storage does not provide

Neither API is a general-purpose database. Calls are synchronous, so avoid using them for large amounts of data or repeated writes during rapid interface updates. Values are strings: setItem("count", 3) stores "3", not a number. To store a small structured value, use JSON.stringify when writing and JSON.parse when reading. Treat parsed data as untrusted input and check that it has the shape your interface expects.

Storage is scoped to an origin, which includes the scheme, host, and port. A page served from http://localhost:3000 does not share storage with one served from http://localhost:4000. Avoid drawing conclusions from a page opened directly as a file; file URL storage behavior can vary. Neither storage option is an appropriate place for secrets: scripts running on the origin can read its stored values. If losing an item would cause serious harm, keep an authoritative copy elsewhere and design the interface to cope with missing browser data.

Handle access and writes that fail

Storage can be unavailable because of browser settings, privacy restrictions, an unsupported context, or a storage limit. Even obtaining window.localStorage or window.sessionStorage can throw, so put property access inside the try block along with the operation. A successful write means only that this browser accepted the value at that moment; it does not promise that the value will remain indefinitely.

For a preference that may reset when storage is unavailable, the following helper falls back to memory for the current page. Try it in the console of a page served from a local development server in a modern browser. Pass () => window.sessionStorage instead if the desired lifetime is a tab session.

function createStore(getStorage) { const memory = new Map(); return { read(key) { if (memory.has(key)) return memory.get(key); try { return getStorage().getItem(key); } catch { return null; } }, write(key, value) { try { getStorage().setItem(key, value); memory.delete(key); return true; } catch { memory.set(key, value); return false; } } }; } const store = createStore(() => window.localStorage); store.write("theme", "dark"); store.read("theme");

The write method returns false when the value exists only in memory. Show a message or otherwise adjust the interface if the user needs to know their choice may disappear on reload. This fallback is deliberately small: it does not copy old stored values into memory, synchronize tabs, or make temporary data durable.

Test the behavior your interface relies on

Start with an ordinary page served from one origin. Write a localStorage preference, reload, and open the page in another tab to check that the interface reads the saved value. Repeat with sessionStorage and compare what happens after a reload and in a separate tab. Then clear the site’s storage and confirm that the interface has a sensible default. Test unavailable storage too: createStore(() => { throw new Error("blocked"); }) simulates access failure without depending on a particular browser setting.

If multiple open tabs must react to a localStorage change, listen for the storage event and update the other tabs’ interfaces. The tab making the change does not receive that event for its own write, so update its interface directly. For sessionStorage, do not rely on the event to coordinate separate tabs. These checks keep the choice grounded in user-visible behavior rather than an assumption that browser storage always works.

Back to category ↗