Knowledge Base/guides/How to Store Objects in localStorage with JSON

How to Store Objects in localStorage with JSON

Storing objects in localStorage with JSON is really a two-step conversion. localStorage only stores strings, so the object has to become text before it goes in. When you read it back, that text has to become a JavaScript value again. Skip either half and the bug is usually quiet, ugly, and shaped like "[object Object]".

The short version is this:

const settings = {
  theme: "dark",
  sidebarOpen: true,
};

localStorage.setItem("settings", JSON.stringify(settings));

const raw = localStorage.getItem("settings");
const savedSettings = raw === null ? null : JSON.parse(raw);

console.log(savedSettings.theme); // "dark"

That pattern is the whole round trip: object to JSON string, JSON string to object.

The naive approach

This is the mistake that looks reasonable the first time:

const user = {
  name: "Ada",
  role: "admin",
};

localStorage.setItem("user", user);

console.log(localStorage.getItem("user"));
// "[object Object]"

localStorage.setItem expects a string value. When you pass a plain object, JavaScript converts it with the object's default string conversion. For most plain objects, that means "[object Object]", which is almost never useful.

Arrays fail in a different way:

const ids = [10, 20, 30];

localStorage.setItem("ids", ids);

console.log(localStorage.getItem("ids"));
// "10,20,30"

That output still has the visible values, but it has lost the original array shape. Nested arrays and objects get worse. Do not let the browser guess how to stringify your data. Use JSON deliberately.

Save with JSON.stringify

JSON.stringify converts JSON-compatible JavaScript values into a string.

const cart = {
  items: [
    { id: "course", quantity: 1 },
    { id: "workbook", quantity: 2 },
  ],
  coupon: null,
};

const json = JSON.stringify(cart);

localStorage.setItem("cart", json);

The stored value is text:

console.log(localStorage.getItem("cart"));
// '{"items":[{"id":"course","quantity":1},{"id":"workbook","quantity":2}],"coupon":null}'

It looks like an object, but it is still a string. You cannot use cart.items on that stored value until you parse it.

Read with JSON.parse

localStorage.getItem returns either a string or null. If the key exists, parse the string:

const raw = localStorage.getItem("cart");

if (raw !== null) {
  const cart = JSON.parse(raw);
  console.log(cart.items.length);
}

Check for null first. A missing key is not an empty object and not an empty array. It is absence.

This is a good place to be precise. Avoid this:

const raw = localStorage.getItem("cart");
const cart = raw ? JSON.parse(raw) : [];

That works for most stored JSON strings, but it teaches the wrong habit. If you are checking whether a key is missing, say that:

const raw = localStorage.getItem("cart");
const cart = raw === null ? [] : JSON.parse(raw);

For a broader explanation of truthy and falsy checks, see Truthy and Falsy Values.

Use a fallback for first load

Most localStorage reads happen when the user opens a page. The first time they visit, nothing has been stored yet.

const defaultPreferences = {
  theme: "light",
  reducedMotion: false,
};

const raw = localStorage.getItem("preferences");
const preferences =
  raw === null ? defaultPreferences : JSON.parse(raw);

That fallback keeps the rest of your code honest. Components can use preferences.theme without pretending that a missing key is a valid preferences object.

If the stored value is old, edited by hand, or corrupted, JSON.parse can throw a SyntaxError. A small helper keeps that boundary in one place.

function loadJson(key, fallback) {
  const raw = localStorage.getItem(key);

  if (raw === null) {
    return fallback;
  }

  try {
    return JSON.parse(raw);
  } catch {
    localStorage.removeItem(key);
    return fallback;
  }
}

const preferences = loadJson("preferences", defaultPreferences);

Deleting the corrupted value is optional, but it is often the least surprising choice. Otherwise the same bad value can break the next page load too.

Save again after changes

Reading from localStorage gives you a value in memory. If you change that value, localStorage does not update automatically.

const preferences = loadJson("preferences", {
  theme: "light",
  reducedMotion: false,
});

preferences.theme = "dark";

console.log(localStorage.getItem("preferences"));
// Still the old stored string

Write the updated value back:

preferences.theme = "dark";

localStorage.setItem("preferences", JSON.stringify(preferences));

This is a common source of confusion. JSON.parse creates a JavaScript value from stored text. That value is not magically connected to storage.

A small storage helper

If you use this pattern more than once, give it a name.

function saveJson(key, value) {
  localStorage.setItem(key, JSON.stringify(value));
}

function loadJson(key, fallback) {
  const raw = localStorage.getItem(key);

  if (raw === null) {
    return fallback;
  }

  try {
    return JSON.parse(raw);
  } catch {
    return fallback;
  }
}

saveJson("recentLessons", ["arrays", "objects", "json"]);

const recentLessons = loadJson("recentLessons", []);

The helper gives your app a plain rule:

  • Save values by stringifying them.
  • Read values by parsing them.
  • Return a fallback when nothing useful was stored.

That is the part worth abstracting. Anything more elaborate starts pretending localStorage is a database. It is not.

JSON does not preserve everything

JSON is a data format, not a full clone of every JavaScript value. It handles plain objects, arrays, strings, numbers, booleans, and null well.

Some values do not round-trip the way beginners expect:

const value = {
  name: "Ada",
  createdAt: new Date("2026-05-20T12:00:00.000Z"),
  onSave() {
    console.log("saved");
  },
  missing: undefined,
};

console.log(JSON.stringify(value));
// '{"name":"Ada","createdAt":"2026-05-20T12:00:00.000Z"}'

The Date becomes a string. The function is dropped. The undefined object property is dropped. A circular reference throws. A BigInt throws unless you define custom serialization. This is the language being honest: JSON cannot represent every JavaScript value.

For localStorage, that is usually fine. Store simple state: preferences, drafts, recently viewed IDs, small cart data, feature flags, and form progress. If you need methods, class instances, binary data, large datasets, indexes, or queries, localStorage is the wrong storage tool.

Do not store secrets

Anything in localStorage is readable by JavaScript running on that origin. If an attacker gets script execution through an XSS bug, localStorage is not a vault.

Do not store passwords, API keys, private payment data, or long-lived auth tokens in localStorage. Use it for small client-side state that would be annoying to lose, not data that would be dangerous to expose.

There is another practical limit too: setItem can throw when storage is unavailable or full. For a small learning project, you may never hit that. In production UI code, storage access belongs near a boundary where failure can be handled cleanly.

localStorage JSON pattern

Use this version when you need a compact copy-paste pattern:

const STORAGE_KEY = "taskList";

function loadTasks() {
  const raw = localStorage.getItem(STORAGE_KEY);

  if (raw === null) {
    return [];
  }

  try {
    return JSON.parse(raw);
  } catch {
    return defaultTasks;
  }
}

function saveTasks(tasks) {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
}

const tasks = loadTasks();

tasks.push({ title: "Practice JSON", done: false });

saveTasks(tasks);

That is the habit to build: every read has a fallback, every write stringifies, and every parse happens at the storage boundary.

Key Takeaways

  • localStorage stores strings, not objects.
  • Use JSON.stringify before setItem and JSON.parse after getItem.
  • Check for null because missing keys are normal on first load.
  • Parsed values are regular JavaScript values; save them again after changes.
  • Keep localStorage data small, plain, and non-sensitive.
Share this article:
javascriptlocalstoragejsonbrowser