Save and load systems are a fundamental requirement for any game that lasts longer than a single session. Browser games have unique storage constraints — no file system access, limited local storage, and users who expect their progress to survive across devices. Here’s how to build robust save systems for the browser.
The browser gives you several places to persist data:
Design your save format before writing any save code. A good save structure:
const saveData = {
version: 3, // schema version for migrations
timestamp: Date.now(),
player: {
name: "Farmer Jo",
level: 12,
xp: 4500,
gold: 1200,
inventory: [
{ id: "wheat_seed", qty: 50 },
{ id: "iron_hoe", qty: 1, durability: 87 }
]
},
world: {
day: 45,
season: "autumn",
weather: "rain",
plots: [
{ x: 3, y: 1, crop: "pumpkin", growth: 0.8 },
{ x: 4, y: 1, crop: "corn", growth: 1.0 }
]
},
settings: {
musicVolume: 0.7,
sfxVolume: 1.0,
autoSave: true
}
};
Your save format will change as you add features. Without version handling, old saves break when players update:
function migrateSave(data) {
if (data.version === 1) {
// v1 -> v2: added inventory durability
data.player.inventory.forEach(item => {
if (item.durability === undefined) item.durability = 100;
});
data.version = 2;
}
if (data.version === 2) {
// v2 -> v3: added weather system
data.world.weather = data.world.weather || "clear";
data.version = 3;
}
return data;
}
Always migrate forward, never backward. Chain migrations so a v1 save can reach v5 by passing through v2, v3, v4 sequentially.
class AutoSaver {
constructor(game, intervalMs = 60000) {
this.game = game;
this.interval = intervalMs;
this.dirty = false;
this.timer = null;
}
markDirty() { this.dirty = true; }
start() {
this.timer = setInterval(() => {
if (this.dirty) {
this.save();
this.dirty = false;
}
}, this.interval);
// Also save when the page is about to unload
window.addEventListener('visibilitychange', () => {
if (document.hidden && this.dirty) this.save();
});
}
save() {
const data = this.game.serialize();
localStorage.setItem('farmheart_save', JSON.stringify(data));
}
}
For games that support multiple saves:
localStorage listing all save slots with metadata (name, timestamp, playtime, thumbnail)save_slot_0, save_slot_1, etc.JSON.stringify + btoa for base64 encoding, or use the CompressionStream API for gzip/deflate.Let players export their save as a downloadable file and import it on another browser or device:
function exportSave() {
const data = localStorage.getItem('farmheart_save');
const blob = new Blob([data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'farmheart-save.json';
a.click();
URL.revokeObjectURL(url);
}
function importSave(file) {
const reader = new FileReader();
reader.onload = (e) => {
const data = JSON.parse(e.target.result);
const migrated = migrateSave(data);
localStorage.setItem('farmheart_save', JSON.stringify(migrated));
};
reader.readAsText(file);
}