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:
- End of round / level
- Major purchase or unlock
- Every 30 seconds (timer-based, for long sessions)
beforeunloadevent (when player closes tab)
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:
- 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.
- 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:
- Saves don't sync across devices. A player who plays on their laptop and then their phone gets two separate saves. For most casual browser games this is fine; for anything serious, you need server-backed cloud saves.
- Clearing browser data wipes saves. Players who clear cookies/site data lose their progress. There's nothing you can do about it without a server.
- Incognito mode. Some browsers' incognito modes give you localStorage that wipes when the window closes. Surprises players who assume saves persist.
- Cheating. Anyone can open dev tools and edit their save. For competitive games this matters; for single-player browser games, it usually doesn't.
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.