Store data

Store data with HTML5 local storage

Learn how to use HTML5 local storage to store data in the browser with JavaScript. Store and read strings, numbers and entire objects with setItem() and getItem(), and remember settings and progress between visits. Find out about the limits, the pitfalls and when to use sessionStorage instead.

Local storage is one of the easiest ways to remember something between visits. In this tutorial you'll store and read data, save entire objects and build a little game that remembers your progress. Along the way you'll find out where it can go wrong and what to do about it.

What is HTML5 local storage?

HTML5 local storage lets you store data in the browser with JavaScript. You save a piece of text under a name, the key, and read it back whenever you need it. The data stays after the page closes, and even after the browser restarts.

It's part of the Web Storage API, which came with HTML5. That's why it's often called HTML5 local storage, though in your code it's simply localStorage. Every browser supports it, and there's nothing to install or set up.

Local storage is ideal for keeping non-critical data like settings, a shopping cart or game progress on the client side, in the visitor's browser. Just remember that users can delete it, and that it's not the place for passwords. Important data still needs to be stored on a server.

Here's everything local storage can do, in one place:

// Store a value under a key
localStorage.setItem('highScore', '1200');

// Read it back (null if the key doesn't exist)
let highScore = localStorage.getItem('highScore');

// Remove one item
localStorage.removeItem('highScore');

// Remove everything
localStorage.clear();

// The number of items, and the key at an index
localStorage.length;
localStorage.key(0);

That's the whole API, and it looks simple. But there's more to it: local storage only holds strings, every page on your site shares it, saving can fail and the data can disappear. The rest of this tutorial shows you how to deal with all of that.

Local storage vs cookies

Often, when people think about storing data for a web page, cookies come to mind. There are some big differences between cookies and local storage:

  • Sent to the server - Cookies are sent to the server with every request, in the HTTP headers. Local storage stays in the browser, so it doesn't slow down your requests.
  • Size - A cookie can hold about 4 KB. Local storage has room for about 5 MB per website.
  • Easy to use - localStorage has simple methods to add, read and remove items. Cookies are one long string in document.cookie, and you'll have to split it up yourself.
  • Expiration date - A cookie can expire at a set time. Local storage never expires by itself.
  • Access from the server - The server can set and read cookies, even cookies that JavaScript can't touch. Because no script can steal that kind of cookie, it's the right place for a login session.

So if you only need the data in the browser, local storage is the way to go. It's easier to use and holds a lot more than the old-fashioned cookie.

Cookies

Who can access your data?

Local storage isn't shared between websites. Each website gets its own, and other websites can't read it. To decide which local storage a page gets, the browser looks at the origin of the page. The origin is made up of three parts of the URL: the protocol, the host and the port. Pages only share local storage when all three are the same. This is part of the same-origin policy, the security rule that keeps websites from reading each other's data.

Let's take a page at https://spicyyoghurt.com/my-page as an example. Its protocol is https, its host is spicyyoghurt.com and its port is 443. You don't see the port in the URL, because 443 is the default for https. Here's which pages share local storage with it, and which don't:

  • Same origin - https://spicyyoghurt.com/tools/scribble has a different path, but the path isn't part of the origin. So both pages have the same origin, and they use the same local storage.
  • Different protocol - http://spicyyoghurt.com/my-page uses http instead of https.
  • Different host - https://www.spicyyoghurt.com/my-page has the subdomain www, and that makes it a different host.
  • Different port - https://spicyyoghurt.com:8080/my-page uses port 8080 instead of 443.

All pages, tabs and windows with the same origin share one local storage. Save something in one tab, and another tab can read it right away. That also means everything on one origin ends up in the same place. On spicyyoghurt.com, this tutorial, the Pixi Pop game and the Scribble app all use the same local storage. That's why every Pixi Pop key starts with pixipop. and every Scribble key with scribble. Give your keys a prefix like that too, so they won't get mixed up with the keys of other apps on your site. The examples in this tutorial use short keys like highScore, just to keep them easy to read.

Store and read data

Let's get to the practical part and actually use local storage. localStorage is a property of the window object, and you can use it anywhere in your JavaScript code. This property is a Storage object, with all the methods from the code block at the top of this tutorial.

To store data, call setItem() on the Storage object and pass a key and value. A new key creates a new item. If the key already exists, the new value replaces the old one.

localStorage.setItem('myKey', 'myValue');

To read the item back, call getItem() and pass the key. It returns a string, or null if the key doesn't exist.

let myValue = localStorage.getItem('myKey');

That null will show up on the first visit, when nothing is stored yet. Most of the time you'll want a default value instead. The nullish coalescing operator ?? gives you one in a single line:

let playerName = localStorage.getItem('playerName') ?? 'Player 1';

If there's no playerName in local storage yet, the player is called 'Player 1'.

Let's put it to the test and eat a cookie

This little example stores data in local storage. You start out with a whole cookie, and you can click Eat cookie to take a bite.

A freshly baked cookie A cookie with a bite taken out

The Eat cookie button calls localStorage.setItem('eatCookie', 'true'). When the page loads, the script checks getItem('eatCookie') and shows the eaten cookie if the value is 'true'. Refresh the page and the cookie stays eaten. Close the browser and come back tomorrow, and it's still eaten.

To reset the cookie, press Bake cookie. It removes the item with removeItem(), and your cookie is whole again! When there's no data on the client, the cookie goes back to its default state, a whole cookie.

Store numbers and booleans

Local storage can only hold strings. You can pass a number or a boolean to setItem(), but it's turned into a string on the way in. And that's where things can go wrong. Here's an example:

localStorage.setItem('score', 10);

let score = localStorage.getItem('score');
score = score + 5;
// score = '105'

What's happening here? getItem() gives back the string '10', and adding 5 to a string glues the two together. Booleans have the same problem:

localStorage.setItem('muted', false);

if (localStorage.getItem('muted')) {
    // This runs, even though muted is false
}

The stored value is the string 'false'. Any string that isn't empty counts as true in an if statement, so this check always passes.

To fix this, convert the value back when you read it:

let score = Number(localStorage.getItem('score'));
let muted = localStorage.getItem('muted') == 'true';

That's how the mute button in the JavaScript game audio tutorial remembers its setting.

Or let JSON.parse() do the work for you. It reads JSON, a text format for data, and turns '10' back into a number and 'false' back into a boolean:

let score = JSON.parse(localStorage.getItem('score'));
let muted = JSON.parse(localStorage.getItem('muted'));

Watch out for missing keys here. Number(null) is 0 and JSON.parse(null) is null. If you want a different value for a missing key, add a default with ?? before you convert it: Number(localStorage.getItem('score') ?? 10).

How to store entire JavaScript objects

Let's say you don't just want to store a simple value, but an entire JavaScript object, like a user's settings. How do you get it into local storage? Passing it to setItem() doesn't work:

let settings = {volume: 0.8, difficulty: 'hard'};
localStorage.setItem('settings', settings);

localStorage.getItem('settings');
// '[object Object]'

The object gets turned into a string, and that string is '[object Object]'. All your values are gone.

You'll need to stringify the object first. JSON.stringify() turns it into a string in JSON format, with all the values in it:

let settings = {volume: 0.8, difficulty: 'hard'};
localStorage.setItem('settings', JSON.stringify(settings));
// Stored: '{"volume":0.8,"difficulty":"hard"}'

When you read it back, you get that same string. Parse it with JSON.parse() to put the object back together:

let settings = JSON.parse(localStorage.getItem('settings'));
// settings.difficulty = 'hard'

This works for arrays too. Here's a list of high scores. On the first visit there's nothing saved yet, so the default is '[]', the JSON for an empty array:

let highScores = [1200, 950, 870];
localStorage.setItem('highScores', JSON.stringify(highScores));

let savedScores = JSON.parse(localStorage.getItem('highScores') ?? '[]');

JSON can't hold everything, though. Some values come back different after you stringify and parse them:

  • Functions - Disappear. The methods of an object aren't stored.
  • Dates - Become a string like '2026-10-11T09:30:00.000Z'. Turn it back into a date with new Date(), or store Date.now(), which is a plain number.
  • Class instances - Become plain objects without the methods of the class. Store the data, and create a new instance from it when you load it back.
  • Map and Set - Become an empty object {}. Turn them into an array with Array.from() first, and back with new Map() or new Set() when you load them.

Save progress between visits

Now let's put it all together. A browser game is the perfect place for local storage. You can stop playing at any time and expect to pick up where you left off. Your progress could be a high score, the levels you unlocked or the upgrades you bought. Our game Pixi Pop also uses local storage to remember the stars you collected in each stage. Making a game of your own? The tutorial about JavaScript game states shows how to save a high score.

Here's a little cookie clicker game. Click the cookie to bake cookies. Then spend them on ovens and grandmas to bake even more cookies. Every move is saved in local storage. Refresh the page, or come back next week, and your bakery is still there.

Live demo The code updates as you play around
// In local storage
null

An oven adds an extra cookie to every click. A grandma bakes a cookie every second, even while you're away. How does that work?

The whole game is in one object, the game state. That makes saving easy: one key, one string. Here's the game state and the function that saves it:

const saveKey = 'cookie-clicker-save';

let gameState = {
    cookies: 0,
    ovens: 0,
    grandmas: 0,
    lastSaved: Date.now()
};

function saveGame() {
    gameState.lastSaved = Date.now();
    localStorage.setItem(saveKey, JSON.stringify(gameState));
}

When the page loads, the game reads the save and puts it in the game state:

function loadGame() {
    let savedGame = JSON.parse(localStorage.getItem(saveKey));

    if (savedGame != null) {
        // The saved values overwrite the defaults
        gameState = {...gameState, ...savedGame};
    }
}

The three dots are called spread syntax. This line copies the defaults into a new object first, and then copies the saved values over them. Why not just use the saved object? Imagine you add a factory to your game next month. Old saves don't have factories yet. With the defaults underneath, an old save still starts with factories: 0 instead of undefined.

Now, the grandmas. They keep baking while the page is closed. Of course, the game can't run while you're away, but it doesn't need to. The save contains lastSaved, the time of the last save. When you come back, right after loadGame(), the game calculates how long you were gone:

function addOfflineCookies() {
    let secondsAway = (Date.now() - gameState.lastSaved) / 1000;
    let cookiesBaked = Math.floor(secondsAway * gameState.grandmas);

    gameState.cookies += cookiesBaked;
}

Date.now() returns the current time in milliseconds. That's a plain number, so it survives JSON without any trouble. Saving the time and letting the game catch up when you come back is a common trick in idle games: games that keep going while you're away, like this one.

When should you save? After every click and every purchase, and once per second for the grandmas. Not in every frame of your game loop, though, which runs 60 times per second on most screens. setItem() is synchronous, so the page waits until it's done. For a small save like this, that's quick, but turning the game state into JSON that often is a waste. Save once per second instead:

function gameLoop(timeStamp) {
    // Once per second, the grandmas bake and the game is saved
    if (timeStamp - lastSaveTime > 1000) {
        gameState.cookies += gameState.grandmas;
        saveGame();
        lastSaveTime = timeStamp;
    }

    // ...
    window.requestAnimationFrame(gameLoop);
}

requestAnimationFrame() passes timeStamp to gameLoop(). timeStamp is the time in milliseconds since the page loaded. lastSaveTime holds the timeStamp of the last save. You can download the complete game at the end of this tutorial, and use it as a starting point for your own.

Put local storage into practice
Create your own HTML5 game

Use local storage to save the player's progress in a browser game. This tutorial series shows you how to build one in HTML5 and JavaScript.

How to check if a key exists

Sometimes you'll want to know whether a key already exists in local storage. Is there a saved game, for example? Call getItem() with the key. It returns null if the item doesn't exist.

Here's how to check whether a key exists:

if (localStorage.getItem('myKey') != null) {
    // Data item exists
} else {
    // Data item doesn't exist
}

Check if local storage is in use or empty

You can also check if local storage contains any items at all, with the length property of the Storage object. A length of 0 means the storage is empty. Anything above that means local storage is in use.

if (localStorage.length > 0) {
    // Items are stored in local storage
} else {
    // Local storage is empty
}

Get all keys and values

Sometimes you want to read the data items in local storage, but you don't know the exact keys. You can still get them with the key() method. It takes an index and returns the name of the key at that position.

Combine it with the length property to loop over all data items:

for (let i = 0; i < localStorage.length; i++) {
    let key = localStorage.key(i);
    let value = localStorage.getItem(key);
    // Do something with the key and value
}

There's a shorter way too. Object.keys() gives you all keys in an array:

let keys = Object.keys(localStorage);

// Possible output
['settings', 'highScore', 'eatCookie']

You might expect to get the keys back in the order you stored them. But they don't always come back in that order, and another browser may use a different order. So if your code needs the keys in a certain order, sort them first.

Just want to see what's in there? You don't need code for that. Open your browser's developer tools with F12 (or Cmd+Option+I on a Mac). In Chrome and Edge, you'll find local storage under Application and then Local storage, and in Firefox under Storage and then Local Storage. You can see every key and value, and change or delete them. That's very handy while you're testing your code.

A bunch of keys

Remove items and clear local storage

If you want to remove items from local storage, use removeItem() to delete a single item. Pass the key as an argument. If the key doesn't exist, nothing happens.

localStorage.removeItem('myKey');

When that's not enough and you want to empty the entire local storage, use the clear() method. It clears all data for your origin, leaving the storage completely empty.

localStorage.clear();

Be careful with clear(). It removes everything stored for your origin. If other apps or games share your origin, their data is gone too. Removing your own keys with removeItem() is the safer choice.

How much data can you store?

Local storage isn't made for large amounts of data. Most browsers give each origin about 5 MB. In Chrome that's 5,242,880 characters, keys included. For settings, a high score or a complete save game, that's plenty. The cookie clicker game above saves only about 65 characters.

What happens when it's full? setItem() throws an error called QuotaExceededError. That's not the only way it can fail. If a visitor blocks cookies and site data for your website, the browser can refuse access to the storage altogether. If your code doesn't catch that, your script stops right there.

So for anything more than a quick test, wrap your storage calls in a try...catch:

function saveItem(key, value) {
    try {
        localStorage.setItem(key, value);
        return true;
    } catch (error) {
        console.warn('Could not save ' + key, error);
        return false;
    }
}

Your code keeps running, and you can tell the user that saving didn't work. Reading can fail too, so wrap getItem() the same way and return null if it fails.

Need more space, or want to store files and images? Then take a look at IndexedDB, a database that's built into the browser. It holds a lot more and doesn't block the page, but it takes more code to use. Scribble, our to-do list and notes app, keeps its settings in local storage, but its tasks and images go into IndexedDB. A single photo from a phone can be bigger than the 5 MB that local storage gives you.

Lifetime of local storage

localStorage has no expiration date. The data stays after you close the tab, and after you restart the browser or even the computer. What you save today can still be there months later.

But not always. These things delete the data:

  • Your code - With removeItem() or clear().
  • The visitor - By clearing the cookies and site data in the browser settings. Clearing just the cache doesn't touch local storage.
  • A private window - Local storage works in a private or incognito window, but everything is deleted when the last private window closes.
  • Safari - If the visitor uses Safari for 7 days without clicking or tapping anything on a website, Safari deletes everything that website has stored with JavaScript.
  • A full disk - When a device runs out of space, the browser can delete the data of websites you haven't visited in a while.

So don't keep anything in local storage that you can't afford to lose. Data in IndexedDB can disappear in the same ways. That's why Scribble lets you save a backup to a file.

Lifetime of local storage data

Want your data to expire after a while? Local storage won't do it for you, but you can store the expiration time with the value and check it when you read it:

function setItemWithExpiration(key, value, hours) {
    let item = {
        value: value,
        expires: Date.now() + hours * 60 * 60 * 1000
    };
    localStorage.setItem(key, JSON.stringify(item));
}

function getItemWithExpiration(key) {
    let item = JSON.parse(localStorage.getItem(key));

    if (item == null) {
        return null;
    }

    if (Date.now() > item.expires) {
        localStorage.removeItem(key);
        return null;
    }

    return item.value;
}

When a player claims a daily bonus in your game, for example, store that with setItemWithExpiration('dailyBonus', true, 24). After 24 hours, getItemWithExpiration('dailyBonus') returns null and the player can claim a new one.

Store data per session

If you don't want to store data for that long, there's the sessionStorage property. It works the same as localStorage, with the same methods:

sessionStorage.setItem('currentLevel', '3');
let currentLevel = sessionStorage.getItem('currentLevel');

The difference is how long the data lives, and where you can see it:

  • Per tab - Every tab has its own session storage, even for the same page. Open the page in a second tab, and that tab starts empty.
  • Survives a refresh - Reloading the page keeps the data.
  • Gone when the tab closes - Close the tab, and the data is deleted.

Use it for things that only matter right now, like the step a visitor is at in a long form, or a game that should start over in every new tab.

Keep tabs in sync with the storage event

Is your page open in two tabs? They share the same local storage, but one tab doesn't know when the other changes something. For that, there's the storage event. The browser fires it in all other tabs of the same origin when an item changes:

window.addEventListener('storage', function (event) {
    if (event.key == 'cookie-clicker-save') {
        // Another tab saved the game, so load it again
        loadGame();
    }
});

event.key tells you which key changed, and event.oldValue and event.newValue give you the old and new values. Only the other tabs get the event, not the tab that made the change. That tab already knows what it saved. Try it with the cookie clicker game: open this page in two tabs, bake some cookies in one and switch to the other. The cookies you just baked are already there, without refreshing the page.

Is local storage safe?

Everything in local storage can be read by any script on your page. That includes scripts from other websites that you load into your page. And if someone manages to run their own script on your page, they can read it all too. That kind of attack is called cross-site scripting, or XSS.

So never store passwords, login tokens or personal data in it. A login session belongs in a cookie that only the server can read.

Also remember that users have full control over what's in local storage. Anyone can open the developer tools and give themselves a million cookies in the cookie clicker game. That's fine for settings or a game you play on your own. But a score for an online leaderboard should be checked by a server.

The complete cookie clicker code

Here's the whole cookie clicker game in one HTML file. It looks simpler than the demo above, but it has all the parts you've seen: the game state, saving and loading with JSON, the storage event that keeps your tabs in sync, and the grandmas who keep baking while you're away. Browsers pause the game loop in a hidden tab, so the game also saves when you switch tabs and lets the grandmas catch up when you come back. You can also download the file at the end of this tutorial. Save the code as an .html file and open it in your browser:

Common questions about local storage

Does local storage expire?

No. It stays until your code removes it or the visitor clears their cookies and site data. There are three exceptions, though. Everything stored in a private window is deleted when the window closes. Safari deletes the data if the visitor uses Safari for 7 days without clicking or tapping anything on your website. And a browser can delete the data when the disk is full.

How much data can local storage hold?

About 5 MB per origin in most browsers, and session storage gets the same amount on top. When it's full, setItem() throws a QuotaExceededError.

Is local storage shared between tabs?

Yes. All tabs and windows of the same origin share one local storage, and the storage event tells the other tabs when something changes. Session storage isn't shared: every tab has its own.

Can the server read local storage?

No. Local storage stays in the browser and is never sent with a request. If your server needs the data, send it yourself with fetch(), a built-in JavaScript function for making requests to a server.

Does local storage work in incognito mode?

Yes, but it's separate from your normal local storage, and everything is deleted when you close the last private window.

Does local storage work when you open an HTML file from your computer?

In Chrome it does, so the download for this tutorial works there too. But the standard doesn't say how browsers should handle files on your computer, so it can vary from browser to browser. For a real project, run it on a local web server or put it online.

Can you store images in local storage?

You can store an image as a data URL, a long string of text. But that string is about a third bigger than the image itself, and 5 MB fills up fast. For images and other files, IndexedDB is a better choice.

Conclusion

Local storage is the easiest way to keep data in the browser. With setItem(), getItem() and removeItem() you can store strings, and with JSON you can store entire objects. That's all you need for settings, a high score or a complete save game. Just keep in mind that it holds about 5 MB, that saving can fail, and that it's not the place for passwords or anything you can't afford to lose.

You can It's the complete cookie clicker game in one HTML file.

How many cookies did you bake? And what are you going to save in your own project? Let us know in the comments below. Want to build a whole game around it? Learn how to make an HTML5 game step by step.