Skip to content

What Is Local Storage? Persisting Key-Value Data in the Browser

Sep 20, 2026 · Web Development

Local storage is a browser Web Storage API that lets a website save string key-value pairs on the user’s device with no expiration, scoped to a single origin. Data written to local storage persists across page reloads, tab closes, and browser restarts until it is explicitly removed. It is the standard place to keep client-only state such as user preferences, cached data, and draft content, and it is simpler and larger than cookies while never being sent to the server automatically.

Key takeaways

  • Local storage persists string key-value data with no built-in expiry.
  • It is scoped per origin, so each site only sees its own data.
  • Values must be strings; use JSON.stringify and JSON.parse for objects.
  • Unlike cookies, it is never sent to the server with requests.
  • The API is synchronous and blocks the main thread, so keep operations small.
  • Any script on the page can read it, so never store secrets or sensitive tokens.

How do I use the local storage API?

The API is small and available on the global localStorage object. Its core methods are straightforward:

localStorage.setItem("theme", "dark");
const theme = localStorage.getItem("theme"); // "dark"
localStorage.removeItem("theme");
localStorage.clear(); // removes everything for this origin

Reading a key that does not exist returns null, so always handle that case. You can also enumerate keys with localStorage.length and localStorage.key(index), though iterating is rarely needed for typical use.

It helps to have a clear sense of what local storage is genuinely good for, because misusing it is the source of most problems. Strong fits include remembering a user’s theme or language preference, caching data that is expensive to fetch but safe to show stale, preserving an in-progress form or draft so a reload does not lose work, and storing a lightweight client-side flag such as whether a one-time tip has been dismissed. Poor fits include anything sensitive, anything the server needs on each request, and anything large or frequently updated. Matching the tool to the task this way keeps local storage fast and keeps you out of the security and performance traps discussed below.

How do I store objects and arrays?

Local storage holds strings only. If you pass any other type, it is silently coerced to a string, which produces surprises like the number 42 coming back as the string "42", or an object becoming the useless string "[object Object]". The correct approach is JSON serialization:

const settings = { theme: "dark", fontSize: 14 };
localStorage.setItem("settings", JSON.stringify(settings));

const saved = JSON.parse(localStorage.getItem("settings") || "{}");

Note the fallback to "{}": if the key is missing, getItem returns null, and calling JSON.parse(null) or parsing invalid data throws. Wrapping parses in a guard or a try/catch is a common necessity because stored data can be absent or corrupted.

JSON serialization also has limits you must design around. Only JSON-compatible data survives the round trip: objects, arrays, strings, numbers, booleans, and null. Anything else is dropped or transformed. A Date becomes a string and does not turn back into a date on read, so you must reconstruct it yourself. Functions and undefined values are omitted entirely, and special numeric values like NaN and Infinity serialize to null. Circular references cause JSON.stringify to throw outright. When you store structured data, keep it to plain serializable shapes and convert richer types explicitly on both save and load.

How is local storage different from sessionStorage and cookies?

All three persist data in the browser, but they differ in lifetime, scope, and whether they reach the server. Choosing correctly avoids both security holes and performance waste.

Feature localStorage sessionStorage Cookies
Lifetime Until removed Until tab closes Until expiry
Scope Per origin, all tabs Per tab Per domain/path
Sent to server No No Yes, every request
Capacity Several MB Several MB A few KB
Readable by JS Yes Yes Unless HttpOnly

The decisive questions are whether the server needs the data (use a cookie) and whether it should survive a tab close (use localStorage rather than sessionStorage).

What is the origin scope and why does it matter?

Local storage is partitioned by origin, which is the combination of scheme, host, and port. Data stored by https://example.com is invisible to https://other.com, and even to http://example.com because the scheme differs. This isolation is a security boundary: one site cannot read another site’s local storage.

A practical consequence is that moving a site from HTTP to HTTPS, or between subdomains, changes the origin and therefore the accessible storage, so existing data will appear to vanish. Plan migrations accordingly rather than assuming the data follows the user.

What are the performance considerations?

The Web Storage API is synchronous. Every read and write blocks the main thread until it completes, so large or frequent operations can cause visible jank in the interface. Two habits keep this in check:

  • Store compact data and avoid parsing very large JSON strings on the critical path.
  • Batch writes rather than calling setItem in a tight loop, and read a value once into a variable instead of repeatedly.

For substantial or structured client-side data, a dedicated database like IndexedDB is the better fit because it is asynchronous and designed for larger, queryable datasets.

How do I write a safe wrapper and handle quota errors?

Because the raw API stores only strings, can throw on parse, and can throw when full, most projects wrap it in a small helper that centralizes serialization and error handling:

const store = {
  get(key, fallback) {
    try {
      const raw = localStorage.getItem(key);
      return raw === null ? fallback : JSON.parse(raw);
    } catch {
      return fallback;
    }
  },
  set(key, value) {
    try {
      localStorage.setItem(key, JSON.stringify(value));
      return true;
    } catch {
      return false; // quota exceeded or storage disabled
    }
  }
};

Two failure modes make this wrapper worthwhile. First, writing can throw a quota error when the per-origin limit is reached, so a robust app checks the result rather than assuming the write succeeded. Second, local storage can be unavailable entirely, for example in private-browsing modes or when a user has disabled site data, in which case even reading throws. Guarding every access in try/catch lets the app degrade gracefully instead of crashing, and returning a fallback keeps calling code simple.

What are the security risks of local storage?

The most important rule is that any JavaScript running on the page can read all of the origin’s local storage. There is no equivalent of the cookie HttpOnly flag. This has a direct consequence:

  • Do not store authentication tokens or secrets. If the site has a cross-site scripting (XSS) vulnerability, an injected script can read the entire store and exfiltrate tokens. Session credentials are safer in HttpOnly cookies that scripts cannot touch.
  • Treat stored data as untrusted on read. A user or another script may have altered it, so validate values rather than assuming they are well-formed.
  • Avoid storing personal data beyond what is necessary, since it persists on the device indefinitely.

How do I detect changes across tabs?

Because local storage is shared across all tabs of an origin, the browser fires a storage event in other tabs when a value changes, which lets you keep tabs in sync:

window.addEventListener("storage", (event) => {
  if (event.key === "theme") {
    applyTheme(event.newValue);
  }
});

A key detail that trips people up: the storage event fires only in other tabs, not in the tab that made the change. The tab performing the write already knows about it, so if you need to react locally you call your handler directly after writing.

Frequently asked questions

Does local storage data expire?

No. Data in local storage persists indefinitely until the site’s code removes it, the user clears browser data, or the browser evicts it under storage pressure. There is no built-in expiry.

What is the difference between localStorage and sessionStorage?

Both share the same API, but localStorage persists across sessions and tabs of the same origin, while sessionStorage is cleared when the tab closes and is scoped to that single tab.

Can local storage hold objects?

Only indirectly. It stores strings only, so you serialize objects with JSON.stringify to save and JSON.parse to read them back. Non-string values are coerced to strings.

Is local storage sent to the server?

No. Unlike cookies, local storage never travels with requests. The server cannot read it unless your JavaScript explicitly sends the data in a request body or header.

Is local storage safe for authentication tokens?

It is risky. Because any JavaScript on the page can read it, a cross-site scripting flaw can steal tokens from local storage, which is why HttpOnly cookies are often preferred for sessions.

How much data can local storage hold?

Browsers typically allow several megabytes per origin, far more than cookies, but the exact limit varies and is not guaranteed, so do not rely on a specific size.

Is the local storage API synchronous?

Yes. All reads and writes are synchronous and block the main thread, so storing large amounts of data or parsing big JSON strings can cause noticeable jank.

Frequently asked questions

Does local storage data expire?

No. Data in local storage persists indefinitely until the site's code removes it, the user clears browser data, or the browser evicts it under storage pressure. There is no built-in expiry.

What is the difference between localStorage and sessionStorage?

Both share the same API, but localStorage persists across sessions and tabs of the same origin, while sessionStorage is cleared when the tab closes and is scoped to that single tab.

Can local storage hold objects?

Only indirectly. It stores strings only, so you serialize objects with JSON.stringify to save and JSON.parse to read them back. Non-string values are coerced to strings.

Is local storage sent to the server?

No. Unlike cookies, local storage never travels with requests. The server cannot read it unless your JavaScript explicitly sends the data in a request body or header.

Is local storage safe for authentication tokens?

It is risky. Because any JavaScript on the page can read it, a cross-site scripting flaw can steal tokens from local storage, which is why HttpOnly cookies are often preferred for sessions.

How much data can local storage hold?

Browsers typically allow several megabytes per origin, far more than cookies, but the exact limit varies and is not guaranteed, so do not rely on a specific size.

Is the local storage API synchronous?

Yes. All reads and writes are synchronous and block the main thread, so storing large amounts of data or parsing big JSON strings can cause noticeable jank.

Contact: contact@quikconsolecom.net