← Back to Blog

How localStorage Saves Browser Games (And Why Most Devs Skip It)

The single biggest mistake we see in browser games — both from indie devs and studios — is skipping save files.

The pattern looks like this: you make a fun game, ship it, post it on itch.io or Reddit. Players love the gameplay. They play for 20 minutes. They close the tab. They come back the next day. Everything is gone. They start over from zero.

Most of those players never come back a third time.

The fix is roughly three lines of JavaScript. Here's the full picture of why it matters and how to do it right.

Why Save Files Change Everything

Plinko without save files is a fun 90-second experience. Plinko with save files — currency that persists, upgrades you keep, achievements that stick — is a game people come back to for weeks.

Same gameplay. Same polish. The only difference is whether progress carries between sessions. The retention difference is roughly 10x.

The reason is simple: humans have an asymmetric relationship with progress. Earning 1000 coins feels good. Losing 1000 coins because the page reloaded feels much worse than the original earning felt good. So losing progress is a much bigger negative experience than earning the same progress is positive.

If your game can't preserve progress, you're not just missing a "feature" — you're actively training players to expect frustration.

The Minimum Viable Save

The simplest possible localStorage save:

// Save
localStorage.setItem('mygame_save', JSON.stringify(gameState));

// Load
const saved = localStorage.getItem('mygame_save');
if (saved) {
  gameState = JSON.parse(saved);
}

That's the whole thing. Two functions. Five lines if you count the conditional.

For a Plinko-style game, gameState might be:

const gameState = {
  coins: 1500,
  ballsUnlocked: ['basic', 'gold', 'magnet'],
  highScore: 8420,
  lastPlayed: '2026-05-08'
};

JSON.stringify handles the serialization. JSON.parse handles the load. localStorage is synchronous, persistent, and available in every browser since IE8.

When To Save

The naive approach is to save on every state change. This is fine for simple games but produces a lot of writes.

The better approach is save on meaningful events:

The beforeunload save is the one most devs forget. Without it, you lose whatever happened since the last manual save when the player closes the tab.

window.addEventListener('beforeunload', () => {
  localStorage.setItem('mygame_save', JSON.stringify(gameState));
});

The Versioning Problem

Once your game has been live for a while, you'll want to add features. Maybe a new currency. Maybe new unlock types. The old saves won't have those fields.

You need a version number from day one:

const gameState = {
  version: 3,
  coins: 1500,
  // ...
};

function loadSave(saved) {
  let state = JSON.parse(saved);
  if (state.version < 2) state = migrate1to2(state);
  if (state.version < 3) state = migrate2to3(state);
  return state;
}

This pattern lets you safely evolve the save format. Players who've been playing since v1 don't lose progress when you ship v3.

Skipping the version field is the most common cause of "I lost my save" complaints in browser games. If you ship one thing from this post, ship the version field.

The Storage Limit

Most browsers give you 5-10MB of localStorage per domain. For a typical game, that's effectively infinite. A single save object is rarely more than 10KB even for complex games.

If you're approaching the limit, the issue is almost always one of two things:

  1. You're saving the entire history of every event ("here are the 8,000 moves the player made"). Don't. Save the resulting state, not the events that produced it.
  2. You're saving images or audio data inline. Don't. Reference asset URLs; let the browser cache the actual files.

What localStorage Doesn't Solve

Important caveats:

Should You Use IndexedDB Instead?

IndexedDB is the "real" browser database. Async, structured queries, larger storage limits.

For 95% of browser games, it's overkill. The async API alone makes simple operations 10x more code. Use localStorage until you have a specific reason to upgrade. (You probably won't.)

The Three Lines That Matter

If you take nothing else from this post, take this:

const SAVE_KEY = 'mygame_save_v1';
const save = () => localStorage.setItem(SAVE_KEY, JSON.stringify(gameState));
const load = () => { const s = localStorage.getItem(SAVE_KEY); if (s) gameState = JSON.parse(s); };

Add save() calls at meaningful events. Call load() on game start. Add a beforeunload handler. You now have save files.

The retention difference is real and measurable. Every game on Slodds that has localStorage saves keeps players an order of magnitude longer than the games that don't. There's no excuse to skip it.