completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

localStorage & sessionStorage

Learn the browser Storage API: saving strings with localStorage and sessionStorage, storing JSON safely, reacting to storage events, and knowing when not to use it.

You will be able to
  • 01
    Save and read small valuesUse setItem, getItem, removeItem, key(index), and length, and delete only your own keys.
  • 02
    Save objects safelyTurn objects into JSON text and back, handle broken saved text, and update old saved data to a new shape.
  • 03
    Pick the right storageExplain the storage event, space limits, why the page waits for storage, why secrets don't belong there, and when to use IndexedDB or a cookie.

Two places to save text in the browser

The browser can save small pieces of text for a website, so they are still there later. This is called Web Storage. It comes in two parts:

  • localStorage keeps text for a long time. It is still there after you reload the page, close the browser, and come back next week. It goes away only when your code or the user deletes it.
  • sessionStorage keeps text for one browser tab. A reload keeps it. Closing the tab deletes it.

Both save text in pairs. The key is a name, like theme. The value is the text saved under that name, like dark.

Each website gets its own storage. More exactly, each origin does. An origin is the start of a web address. It has three parts:

  • the scheme, like https;
  • the host, like example.com;
  • the port, a number that is often hidden, like 443.

https://example.com and http://example.com are two different origins. They can't read each other's storage.

Save a theme preferencePop out in the code editor (opens in a new tab)JavaScript
const theme = localStorage.getItem("theme") ?? "light";
localStorage.setItem("theme", "dark");
console.log(theme);
console.log(localStorage.getItem("theme"));

Line 1 reads the saved theme with getItem. Nothing is saved yet, so getItem gives null, which means "nothing here". The ?? operator then picks a fallback: a backup value, here "light".

Line 2 saves "dark" with setItem. Line 3 prints light. Line 4 reads the theme again and prints dark. On a real site, the next visit would start with dark.

sessionStorage also gives back textPop out in the code editor (opens in a new tab)JavaScript
sessionStorage.setItem("step", 2);
console.log(sessionStorage.getItem("step"));
console.log(typeof sessionStorage.getItem("step"));

Line 1 saves the number 2 in this tab's sessionStorage. Line 2 prints 2. Line 3 prints string: the number came back as text. The next section explains why.

Here is what happens to this tab's sessionStorage:

  • Reload the tab: the value stays.
  • Close the tab: the value is deleted.
  • Open the same page in a new tab: the new tab starts empty.
  • Duplicate the tab: in most browsers, the new tab starts with a copy. After that, the two copies change separately.
Left: localStorage holds theme dark. It is kept after a reload and after the browser restarts. Right: sessionStorage holds step 2. It is kept after a reload, but gone when the tab closes.localStoragetheme: "dark"localreload: keptrestart: keptsessionStoragestep: "2"sessionreload: kepttab closed: gone
localStorage keeps the theme after a reload and after the browser restarts. sessionStorage keeps the step after a reload, but loses it when the tab closes.
Real-life analogyDrawers and sticky notes

Imagine each website has its own small office. The office has a drawer for things that must still be there next week. Each open tab also has a sticky note on its own screen. A second tab gets its own sticky note, even though it works in the same office.

In real life: A drawer in this website's office
In JavaScript: localStorage: text this origin keeps for a long time
In real life: Only this office's staff can open the drawer
In JavaScript: Only pages from the same origin can read those keys
In real life: A sticky note on one tab's screen
In JavaScript: sessionStorage: text for one tab that stays after a reload
In real life: The note goes in the bin when the tab closes
In JavaScript: Closing the tab deletes its sessionStorage

Use Web Storage for small things that only the browser needs, such as:

  • a setting, like the theme;
  • which tab or panel was open;
  • whether the user has already seen a feature tour;
  • a short draft of a form.

Don't use it as a database, as a safe place for secrets, or as a way to send data to the server. The section on limits explains why.

Save, read, and remove values

INTERACTIVE

localStorage and sessionStorage have the same methods. A method is a function you call on an object, after a dot:

  • setItem(key, value) saves a value under a key.
  • getItem(key) reads the value saved under a key.
  • removeItem(key) deletes one key and its value.
  • length tells you how many keys are saved. It is a number, not a method, so it has no brackets.
  • key(index) gives the name of the key at a position: 0, 1, 2, and so on.
  • clear() deletes every key for this origin. Use it with care: it also deletes keys that other parts of the site saved.
Use the methods, not dots

localStorage.theme = "dark" also seems to save a value. Use setItem("theme", "dark") instead. It is clear, it matches getItem, and it can't clash with a name the storage object already has, such as length.

Real storage on this site
Save a number, a boolean, and an objectPop out in the code editor (opens in a new tab)JavaScript
const storage = localStorage; storage.setItem("count", 7);storage.setItem("darkMode", false);storage.setItem("profile", { name: "Ada" }); console.log(storage.getItem("count"));console.log(storage.getItem("darkMode"));console.log(storage.getItem("profile"));console.log(storage.getItem("missing"));
Demo keys saved now0 keys

No cf-demo keys are saved right now.

Try it yourself

Click a button to save demo keys for this site only.

This uses the real localStorage and sessionStorage of this site. Every key starts with cf-demo:, and Reset deletes those keys again. They are also deleted when you leave the page.

One rule matters most: storage keeps only strings. A string is text. Before saving, the browser turns every value into text with String(value):

  • The number 7 becomes "7".
  • The boolean false becomes "false".
  • An object like { name: "Ada" } becomes "[object Object]". The data inside is lost.

If a key was never saved, getItem gives null. That is why you often see a fallback after ??, like localStorage.getItem("tab") ?? "overview".

A number comes back as textPop out in the code editor (opens in a new tab)JavaScript
localStorage.setItem("count", 5);
const savedCount = localStorage.getItem("count");
console.log(savedCount);
console.log(typeof savedCount);

Line 1 saves the number 5. Storage turns it into the text "5". Line 3 prints 5, and line 4 prints string. Before you do math with it, turn it back into a number with Number(savedCount).

setItem count 5 puts the number 5 into localStorage. Storage keeps it as the text 5 in quotes. getItem count gives back the string 5.setItem("count", 5)5a numbercount: "5"localgetItem("count")"5"a string
setItem gets the number 5, but storage keeps the text "5". So getItem gives back a string.
A missing key and a removed keyPop out in the code editor (opens in a new tab)JavaScript
localStorage.setItem("notice", "saved");
console.log(localStorage.getItem("notice"));
localStorage.removeItem("notice");
console.log(localStorage.getItem("notice"));

Line 2 prints saved. Line 3 deletes the key. Line 4 reads the same key again and prints null. It is not an empty string, and it is not undefined.

Delete one key, or delete them allPop out in the code editor (opens in a new tab)JavaScript
localStorage.setItem("app:theme", "dark");
localStorage.setItem("app:draft", "Tea");
localStorage.setItem("other:setting", "keep");
localStorage.removeItem("app:theme");
console.log(localStorage.length);
localStorage.clear();
console.log(localStorage.length);

Here is what each line does:

  • Lines 1 to 3 save three keys. Two start with app:. One starts with other:.
  • Line 4 deletes only app:theme. Line 5 prints 2.
  • Line 6 calls clear(). It deletes app:draft, and it also deletes other:setting, which belonged to another part of the site.
  • Line 7 prints 0.

So give your keys a shared start, called a prefix, like app:. Then delete only the keys that start with it.

List every saved keyPop out in the code editor (opens in a new tab)JavaScript
localStorage.setItem("theme", "dark");
localStorage.setItem("draft", "Tea");
for (let index = 0; index < localStorage.length; index += 1) {
  console.log(localStorage.key(index));
}

The loop starts at position 0. Before each turn, it checks index < localStorage.length. On each turn, key(index) gives one key name. This prints theme, then draft.

Here the keys come out in the order they were saved. Browsers don't promise that order, so don't write code that depends on it.

Real-life analogyA locker that only fits notes

Think of storage as a locker that only fits written notes. To keep an object in it, first write the object down as a note. Later, read the note and build the object again.

In real life: A written note fits in the locker
In JavaScript: A string, like dark, fits in storage
In real life: Write a big object down as a note first
In JavaScript: JSON.stringify(settings) turns the object into a string
In real life: Read the note to build the object again
In JavaScript: JSON.parse(saved) turns the string back into an object

Save objects as JSON text

INTERACTIVE

To save an object, first turn it into text. JSON is a text format for data. Two tools do the work:

  • JSON.stringify(value) turns an object into JSON text.
  • JSON.parse(text) turns JSON text back into an object.

Saved text can go wrong. It can be:

  • missing, because nothing was saved yet;
  • old, saved by an older version of your app;
  • broken, for example after someone changed it by hand in the browser's developer tools.

JSON.parse throws an error when the text is broken. So read saved JSON inside try/catch. The try block runs the code. If it throws, the catch block runs instead, and the page doesn't crash.

Read saved JSON safelyPop out in the code editor (opens in a new tab)JavaScript
function readJSON(key, fallback) {
  const text = localStorage.getItem(key);
  if (text === null) return fallback;
  try {
    return JSON.parse(text);
  } catch {
    return fallback;
  }
}

console.log(readJSON("missing", { theme: "light" }).theme);
localStorage.setItem("settings", "{broken json");
console.log(readJSON("settings", { theme: "light" }).theme);

Here is what happens:

  • Line 11 reads the key "missing". getItem gives null, so line 3 returns the fallback right away. It prints light.
  • Line 12 saves broken text under the key "settings".
  • Line 13 reads it. JSON.parse on line 5 throws, so the catch on lines 6 and 7 returns the fallback. It prints light again, and the page keeps working.
JSON lab: save, read safely, and upgrade old settings
Read safely, then upgrade old settingsPop out in the code editor (opens in a new tab)JavaScript
function readJSON(key, fallback) {  const text = localStorage.getItem(key);  if (text === null) return fallback;  try {    return JSON.parse(text);  } catch {    return fallback;  }} const oldSettings = {  version: 1,  data: { theme: "dark", compact: true }};localStorage.setItem("cf-demo:settings", JSON.stringify(oldSettings)); const loaded = readJSON("cf-demo:settings", { version: 2, data: {} });const current = loaded.version === 1  ? { version: 2, data: { ...loaded.data, textSize: "normal" } }  : loaded; localStorage.setItem("cf-demo:settings", JSON.stringify(current));
Save and read backupgraded
Upgraded settings{"version":2,"data":{"theme":"dark","compact":true,"textSize":"normal"}}
Saved text{"version":2,"data":{"theme":"dark","compact":true,"textSize":"normal"}}
A Date after JSON is astring
Safe read gives{"version":2,"data":{"theme":"dark","compact":true,"textSize":"normal"}}
Try it yourself

The version 1 settings were read safely, changed into the version 2 shape, and saved back as JSON text.

The code shown uses localStorage in a browser. The results come from a pretend storage with the same getItem and setItem methods.
Save and restore a small draftPop out in the code editor (opens in a new tab)JavaScript
const draft = { title: "Tea", body: "Buy leaves" };
localStorage.setItem("draft", JSON.stringify(draft));
const restored = JSON.parse(localStorage.getItem("draft"));
console.log(restored.title);

Line 1 makes a small draft object. Line 2 turns it into JSON text and saves it. Line 3 reads the text and turns it back into an object. Line 4 prints Tea.

A form could do this while the user types. In a real app, read with the safe reader above, in case the saved text is old or broken.

The draft object, with title Tea, goes through JSON.stringify and becomes saved text. JSON.parse turns the saved text back into an object called restored, with title Tea.saved text{"title":"Tea",…}title: "Tea"draftJSON.stringifyJSON.parsetitle: "Tea"restored
JSON.stringify turns the whole draft object into one string, and storage saves it. JSON.parse turns the string back into an object, so restored.title is "Tea".

Once real users have saved data, a later version of your app may need it in a new shape. So save a version number with the data, like { version: 2, data: settings }. When you read version 1 data, change it into the version 2 shape first. This is called a migration. Then the rest of your app only ever sees the newest shape.

The table shows four kinds of saved value. Read each row as: what you have, what storage holds, and what to do about it.

What each kind of value looks like in storage
What you haveWhat storage holdsWhat to do
A plain objectJSON text, like {"theme":"dark"}.Use JSON.stringify before saving and JSON.parse after reading.
A missing keyNothing. getItem gives null.Return a fallback. Don't pass null to JSON.parse by mistake.
Broken textText like {broken.Catch the error from JSON.parse and return a fallback.
A Date objectA string, like 2026-09-25T00:00:00.000Z.JSON gives back only the string. Call new Date(text) if you need date methods again.

The storage event

INTERACTIVE

An event is a message the browser sends when something happens. When one page changes localStorage, the browser sends a storage event to the other open pages from the same origin. These can be other tabs, or an iframe: a page shown inside another page.

A change to sessionStorage sends the event too, but only to pages that share that tab's storage, such as an iframe in the same tab.

The page that made the change does not get the event. It already knows what it changed. So use the event to hear about changes from other pages, and update your own page directly.

Tab A calls setItem with the value hello. An arrow goes to Tab B, which gets a storage event with newValue hello. Tab A gets no event.Tab Acalls setItem"hello"no eventTab Bstorage eventnewValue: "hello"gets the event
Tab A saves "hello" with setItem. Only the other tab, Tab B, gets a storage event; Tab A does not hear its own change.
Real-life analogyA shout down the hallway

You change something in the office drawer and shout down the hallway, "I changed the drawer!" People in other rooms hear it and can act on it. You don't need the shout, because you made the change yourself.

In real life: You change something in the drawer
In JavaScript: One tab calls localStorage.setItem
In real life: People in other rooms hear your shout
In JavaScript: Other tabs and iframes from the same origin get a storage event
In real life: You don't need to hear your own shout
In JavaScript: The page that saved gets no event
In real life: The shout says what changed
In JavaScript: The event has key, oldValue, newValue, url, and storageArea
Storage event: another page hears the change
Listen for changes, then savePop out in the code editor (opens in a new tab)JavaScript
window.addEventListener("storage", (event) => {  console.log(event.key);  console.log(event.oldValue);  console.log(event.newValue);  console.log(event.url);  console.log(event.storageArea === localStorage);}); localStorage.setItem("cf-demo:signal", "hello");
The other pageown events: 0
    Try it yourself

    The iframe is another page from the same origin, so it reports each change. This page saved the value, so it gets no event: its own event count is 0.

    The iframe runs fixed code and only trusts messages from itself. Open this lesson in a second tab to see the same thing between two tabs.

    The event tells you what changed:

    • key: the key that changed.
    • oldValue and newValue: the text before and after the change.
    • url: the address of the page that made the change.
    • storageArea: which storage changed, localStorage or sessionStorage.

    If a page calls clear(), event.key is null. This lesson's demos never call localStorage.clear(). They delete only keys that start with cf-demo:, because this website saves its own keys too, like your theme.

    Example: remember the last tab

    STEP THROUGH

    Many sites remember the last tab, panel, or filter you picked. The code is short. It has four steps:

    1. Read the saved tab, with a fallback.
    2. The visitor picks a tab.
    3. Save the new tab name.
    4. On the next visit, read it again.

    The step-through below uses a small pretend storage, built on a Map. It has the same getItem and setItem methods. The lesson uses it because Node.js, which runs this lesson's checks, has no browser localStorage.

    Remember the last tab
    Step 0 of 7Ready
    Your turn: follow the blue line

    Step through four moves: read a saved value with a fallback, let the visitor pick, save the new value, then read it back on a later visit.

    Running in
    1. script
    Next: line 1
    const fallback = "overview";let activeTab = storage.getItem("lastTab") ?? fallback; activeTab = "examples";storage.setItem("lastTab", activeTab); const nextVisit = storage.getItem("lastTab") ?? fallback;console.log(nextVisit);
    CallStoreChangeResultBlue line: runs next. Ran: already executed.
    Recent returnsNothing yet. Start with the blue line.
    Choose the tab the visitor clicks on line 5

    Changing the tab makes a new recording. Guess the last line it prints before you step through.

    A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

    The page changes the tab on screen first. Then it saves the choice. Storage doesn't control every click. It only helps choose the first tab on the next visit.

    Limits, and when not to use it

    KNOW THE LIMITS

    Web Storage is small on purpose. Many browsers allow about 5 MB for each origin, but the real limit changes from browser to browser. A quota is the most space you are allowed.

    When storage is full, setItem throws an error named QuotaExceededError. Catch that error, so the page keeps working without the saved value.

    Keep working when storage is fullPop out in the code editor (opens in a new tab)JavaScript
    const storage = {
      setItem() {
        const error = new Error("storage is full");
        error.name = "QuotaExceededError";
        throw error;
      }
    };
    
    try {
      storage.setItem("draft", "Tea");
    } catch (error) {
      console.log(error.name);
      console.log("Keep the draft in memory");
    }

    Lines 1 to 7 make a pretend storage. Its setItem always throws an error with the same name a full browser storage uses. Line 10 tries to save. The catch block runs instead: line 12 prints QuotaExceededError, and line 13 prints Keep the draft in memory. The draft stays in a normal variable, so the form still works.

    Space check: saving can fail
    Catch a failed savePop out in the code editor (opens in a new tab)JavaScript
    try {  localStorage.setItem("cf-demo:settings", jsonText);} catch (error) {  // Storage is full or not available: keep going without it.}
    Resultdemo key

    Press the button to save one demo value, then delete it right away.

    Try it yourself

    Press the button to save one demo value, then delete it right away.

    This check is small and deletes its own value. The real space limit changes with the browser, the device, how full the disk is, and private browsing.

    Each row below is a case where Web Storage is the wrong choice. Read it as: the problem, why it matters, and what to use instead.

    When Web Storage is the wrong tool
    The problemWhy it mattersWhat to use instead
    Secrets, like login tokensAny script on the page can read Web Storage. If an attacker gets their script onto your page (an XSS attack), it can steal them.Keep secrets out of it. A cookie that the server sets with the HttpOnly flag is often safer, because page scripts can't read it.
    Large or complex dataEverything is text. Each read and write also makes the page wait until it is done.IndexedDB, a database in the browser for records, files, and lots of data.
    The server needs the valueWeb Storage is never sent to the server with requests.A cookie, for small values the server needs.
    Code in a workerA worker is a script that runs in the background. localStorage and sessionStorage don't exist there.IndexedDB, or send messages between the worker and the page.
    Private browsingSome browsers delete the data sooner, or don't keep it at all.Check that storage works, catch errors, and treat saved values as optional.
    Six checks before you ship
    • The page waits: each read and write stops the page until it is done. Keep them few and small. Don't call storage in animations or in code that runs many times a second.
    • Space can run out: about 5 MB is common, but the browser, the device, and privacy settings can change it.
    • Everything comes back as text: parse JSON, and turn saved numbers and booleans back on purpose.
    • Bigger data goes in IndexedDB: it stores real records, and the page doesn't wait for it.
    • A duplicated tab gets a copy: both tabs start with the same sessionStorage, but later changes in one don't reach the other.
    • Scripts can read it: an XSS attack can read storage, so tokens and passwords don't belong there.
    Never store secrets

    Never save passwords, login tokens, card details, or other secrets in localStorage or sessionStorage. If an attacker's script runs on your page, it can read them.

    localStorage, sessionStorage, IndexedDB, or a cookie?

    SORT IT

    The next lesson is about IndexedDB, a real database inside the browser. For now, use this simple rule:

    • Small text that only the browser needs: Web Storage.
    • A tiny value the server needs: a cookie. The browser sends cookies to the server with each request.
    • Lots of data, or data with many fields: IndexedDB.
    Choose where to store it
    • Remember the site's light or dark theme after the browser restarts
    • Keep a checkout form's current step for this tab only
    • Store hundreds of notes for offline use, each with several fields
    • Send a tiny language setting to the server with the next request
    • Save a login token that would be dangerous if a page script could read it
    • Remember the choices in a product filter panel on this site
    • Keep a different preview mode in each of two tabs of the same site
    • Keep large files or images for offline use
    Try it yourself
    0 of 8 correct

    Put each case under the storage you would try first.

    Choose a category for every card. You can change an answer at any time; Reset clears them all.

    Common misconceptions

    • "It stores JavaScript values." It stores only strings. Use JSON for plain objects. Rebuild special values, like a Date, yourself after reading.
    • "sessionStorage is deleted when I reload." A reload keeps it. Closing the tab deletes it.
    • "Every tab shares sessionStorage." Tabs from the same origin share localStorage. Each tab has its own sessionStorage.
    • "The storage event tells my page about its own change." The event goes to the other pages from the same origin, not to the page that saved.
    • "It is safe because users can't see it." Users can see it in the developer tools, and any script on the page can read it. Never put secrets there.
    • "clear() is a harmless reset." It deletes every key in that storage for this origin. Delete only your own keys, the ones with your prefix.

    Practice exercises

    5 EXERCISES
    Exercise 1 · Warm-upPredict stored strings

    This code copies what storage does, using a Map. Predict the two lines it prints.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const storage = new Map();
    storage.set("count", String(3));
    storage.set("flag", String(false));
    console.log(storage.get("count") + 2);
    console.log(typeof storage.get("flag"));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 2 · PracticeWrite a safe reader

      Read the readJSON helper. Predict the two themes it prints.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      function readJSON(text, fallback) {
        if (text === null) return fallback;
        try {
          return JSON.parse(text);
        } catch {
          return fallback;
        }
      }
      console.log(readJSON('{"theme":"dark"}', {}).theme);
      console.log(readJSON('{broken', { theme: "light" }).theme);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 3 · PracticeFind the event bug

        A developer saves a setting. Then they wait for a storage listener on the same page to update the screen. Will that listener run on the page that saved?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 4 · PracticeRemember a last tab

          This code copies storage with a Map. Predict what the last line prints.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const storage = new Map();
          const fallback = "home";
          let tab = storage.get("tab") ?? fallback;
          tab = "settings";
          storage.set("tab", tab);
          console.log(storage.get("tab") ?? fallback);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 5 · ChallengeDesign a storage plan

            You are building a note-taking app. Decide where to keep each of these, and say why:

            • the theme the user picked;
            • a preview of unsaved changes in the current tab;
            • hundreds of notes for offline use;
            • the secret that keeps the user logged in.

              Check your understanding

              7 QUESTIONS
              Web Storage quiz · 7 questionsScore: first tries count
              1. Question 1 of 7This code copies getItem with a Map. What does it print for a key that was never saved?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const storage = new Map();
                const getItem = (key) => storage.has(key) ? storage.get(key) : null;
                console.log(getItem("missing"));

                Choose an answer to see the explanation.

              2. Question 2 of 7Storage turns every value into a string, like String(42). What does this print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const value = String(42);
                console.log(typeof value);
                console.log(value + 1);

                Choose an answer to see the explanation.

              3. Question 3 of 7You want to save an object and get it back later. What should you pass to setItem?

                Choose an answer to see the explanation.

              4. Question 4 of 7One page changes localStorage. Where does the storage event go?

                Choose an answer to see the explanation.

              5. Question 5 of 7Which sentence about sessionStorage is true?

                Choose an answer to see the explanation.

              6. Question 6 of 7Storage may be full. What should your code do around setItem?

                Choose an answer to see the explanation.

              7. Question 7 of 7Which storage is best for lots of records with many fields that you need offline?

                Choose an answer to see the explanation.

              Key takeaways

              • localStorage keeps text for an origin for a long time. sessionStorage keeps text for one tab.
              • Storage keeps only strings. getItem gives null for a missing key.
              • Save objects as JSON. Read them with a fallback and try/catch, and save a version number with them.
              • The storage event goes to the other pages from the same origin, not to the page that saved.
              • Catch errors when storage is full, never save secrets, and use IndexedDB or cookies when they fit better.

              Final definition: Web Storage lets a page save small pieces of text in the browser, as key and value pairs, for one origin. localStorage keeps them across visits, and sessionStorage keeps them for one tab. Each read and write makes the page wait until it is done.

              Up next: IndexedDB.

              CompleteFrontend Clear concepts. Working examples.