JavaScript Storage: localStorage, sessionStorage, and Cookies

Browser storage lets your JavaScript app remember data between page loads, tabs, or visits. This article explains the three main browser storage options—localStorage, sessionStorage, and cookies—so you can choose the right one for login state, user preferences, shopping carts, and other common tasks.

Quick answer: Use localStorage for long-lived client-side data, sessionStorage for data that should disappear when the tab closes, and cookies when the browser must send the data to the server with HTTP requests.

Difficulty: Beginner

You'll understand this better if you know: basic JavaScript variables, objects, JSON, and how the browser loads pages and makes requests.

1. What Is JavaScript Storage?

JavaScript storage is a set of browser features that let you keep small pieces of data on the client side. Unlike ordinary variables, stored data can survive a page refresh, and in some cases it can survive closing and reopening the browser.

These tools are not databases. They are simple storage mechanisms for small amounts of data, usually preferences, tokens, flags, or cached UI state.

2. Why JavaScript Storage Matters

Without browser storage, every refresh would reset your app state. Users would lose their theme choice, cart contents, draft filters, or login-related state unless the server re-sent everything each time.

Storage matters because it improves user experience and reduces repeated work. A small preference saved once can save many repeated clicks later.

It also helps you build offline-friendly or semi-persistent interfaces. For example, a form draft can survive a refresh, or a dashboard can reopen with the same filters the user selected earlier.

3. Basic Syntax or Core Idea

All three storage options are key-value based, but they do not use the same API style. localStorage and sessionStorage use the same Storage interface, while cookies are usually read and written through document.cookie.

Using localStorage

Store strings with setItem(), read them with getItem(), and remove them with removeItem().

const theme = "dark";

localStorage.setItem("theme", theme);

const savedTheme = localStorage.getItem("theme");

localStorage.removeItem("theme");

The value is always stored as a string, so numbers and objects need conversion before saving and after reading.

Using sessionStorage

sessionStorage uses the same methods, but the data is scoped to the current tab and session.

sessionStorage.setItem("step", "2");
const currentStep = sessionStorage.getItem("step");

This is useful for temporary state, such as a multi-step form that should not persist after the tab closes.

Using cookies

Cookies are set by assigning to document.cookie. Reading cookies gives you a single string that contains all accessible cookies for the current page.

document.cookie = "theme=dark; path=/; max-age=2592000";

const allCookies = document.cookie;

Cookies are often used for server-readable session identifiers, but they require more care because they can affect every request to a matching path and domain.

4. Step-by-Step Examples

Example 1: Saving a theme preference with localStorage

A theme toggle is a classic use case for persistent browser storage. The user expects the choice to survive refreshes and future visits.

function saveTheme(theme) {
  localStorage.setItem("theme", theme);
}

function loadTheme() {
  return localStorage.getItem("theme") || "light";
}

saveTheme("dark");
const theme = loadTheme();

This pattern is simple and effective because a small string is enough to represent the preference.

Example 2: Storing temporary form progress with sessionStorage

If the user should keep data only while the tab is open, sessionStorage is a better fit than localStorage.

const draft = {
  name: "Ava",
  email: "[email protected]"
};

sessionStorage.setItem("signupDraft", JSON.stringify(draft));

const savedDraft = JSON.parse(
  sessionStorage.getItem("signupDraft") || "{}"
);

This works well for drafts or onboarding flows that should reset after the session ends.

Example 3: Reading a cookie value in the browser

Sometimes you need to inspect a cookie value from JavaScript, such as a client-side preference cookie. The browser exposes accessible cookies as one string.

function getCookie(name) {
  const parts = document.cookie.split("; ");

  for (const part of parts) {
    const [cookieName, cookieValue] = part.split("=");
    if (cookieName === name) {
      return decodeURIComponent(cookieValue);
    }
  }

  return null;
}

This shows why cookie parsing is less convenient than the localStorage API.

Example 4: Removing stored data

Cleaning up old storage is important when a preference is no longer valid or a user logs out.

localStorage.removeItem("theme");
sessionStorage.removeItem("signupDraft");

document.cookie = "theme=; path=/; max-age=0";

The cookie line deletes the cookie by setting the same name and path with an expiration in the past.

5. Practical Use Cases

As a rule, choose the smallest storage type that solves the problem and avoid storing data you do not need to persist.

6. Common Mistakes

Mistake 1: Treating storage values like numbers or objects

Storage APIs only store strings. If you save an object directly, you usually end up with [object Object], which is not useful.

Problem: The object is coerced into a string instead of being stored as structured data, so reading it back does not give you the original object.

const user = { name: "Mina", id: 42 };
localStorage.setItem("user", user);

Fix: Use JSON.stringify() when saving and JSON.parse() when reading.

const user = { name: "Mina", id: 42 };
localStorage.setItem("user", JSON.stringify(user));

const savedUser = JSON.parse(localStorage.getItem("user") || "null");

The corrected version works because JSON preserves the object structure.

Mistake 2: Using localStorage for tab-only state

Some data should disappear when the tab closes, such as an in-progress wizard or a temporary code entry. Saving that data in localStorage makes it persist longer than intended.

Problem: The data survives future visits, so old temporary state can confuse the user and reopen an outdated flow.

localStorage.setItem("checkoutStep", "3");

Fix: Use sessionStorage when the data should be limited to the current tab session.

sessionStorage.setItem("checkoutStep", "3");

The corrected version matches the lifetime of the data to the user experience.

Mistake 3: Assuming cookies are automatically secure

Cookies can be sent with requests, which is useful, but it also means they need careful settings. Leaving out attributes can expose them more broadly than intended.

Problem: A cookie without appropriate attributes may be available in more contexts than necessary, increasing the risk of misuse.

document.cookie = "sessionId=abc123";

Fix: Add attributes such as path, max-age, SameSite, and Secure when appropriate for your app.

document.cookie = "sessionId=abc123; path=/; max-age=3600; SameSite=Lax; Secure";

The corrected version is more explicit about how the cookie should behave in the browser.

7. Best Practices

Practice 1: Store only small, non-sensitive data

Browser storage is easy to access from client-side code, so it is not the right place for secrets. Keep passwords, private keys, and highly sensitive data off the client whenever possible.

// Better: store a preference, not a secret
localStorage.setItem("theme", "dark");

This reduces risk and keeps storage fast and simple.

Practice 2: Wrap JSON parsing in a safe fallback

Reading storage can fail in practice if the value is missing or malformed. A fallback prevents the app from crashing on startup.

function loadSettings() {
  try {
    return JSON.parse(localStorage.getItem("settings") || "{}");
  } catch {
    return {};
  }
}

This pattern keeps bad stored data from breaking the page.

Practice 3: Use explicit names and cleanup rules

Clear key names help you understand what data is stored and when to remove it. Short, vague names make maintenance harder later.

localStorage.setItem("app:theme", "dark");
localStorage.setItem("app:lastRoute", "/dashboard");

function clearAppStorage() {
  localStorage.removeItem("app:theme");
  localStorage.removeItem("app:lastRoute");
}

Consistent naming makes it easier to debug and delete data safely.

8. Limitations and Edge Cases

If your app needs a large or structured client-side dataset, consider using IndexedDB instead of browser storage.

9. Practical Mini Project

Here is a small preference manager that loads a stored theme on page load, lets the user switch themes, and saves the choice in localStorage.

const themeSelect = document.querySelector("#themeSelect");
const status = document.querySelector("#status");

function applyTheme(theme) {
  document.body.dataset.theme = theme;
  themeSelect.value = theme;
  status.textContent = `Theme saved: ${theme}`;
}

const storedTheme = localStorage.getItem("app:theme") || "light";
applyTheme(storedTheme);

themeSelect.addEventListener("change", event => {
  const theme = event.target.value;
  localStorage.setItem("app:theme", theme);
  applyTheme(theme);
});

This example shows the full loop: read stored data, apply it, respond to user input, and save the updated value.

10. Key Points

11. Practice Exercise

Expected output: The theme should persist across refreshes, the draft should stay only for the current tab, and the banner dismissal should survive for one day.

Hint: Use JSON.stringify() for objects and keep each storage type tied to the lifetime you want.

const noteInput = document.querySelector("#noteInput");
const saveButton = document.querySelector("#saveButton");

function saveDraft() {
  sessionStorage.setItem("draftNote", noteInput.value);
}

function loadDraft() {
  noteInput.value = sessionStorage.getItem("draftNote") || "";
}

saveButton.addEventListener("click", saveDraft);
loadDraft();

12. Final Summary

JavaScript browser storage gives you three main tools with different lifetimes and behaviors. localStorage is best for persistent client-side preferences, sessionStorage is best for tab-scoped temporary state, and cookies are best when the server needs the browser to send data automatically with requests.

The most important habit is matching the storage type to the job. If data should survive a refresh, use localStorage. If it should disappear when the tab closes, use sessionStorage. If the server must read it, use cookies and set the right attributes.

From here, the next useful topic is secure authentication storage and when to use cookies versus other browser-side persistence methods. That will help you make safer choices for login flows and user sessions.