🌾 FarmHeart
Cloud SaveGame DevBackend

Cloud Saves for Browser Games: Syncing Progress Across Devices

2026-10-05 · 10 min read

The Problem With Local-Only Saves

Browser games store progress in localStorage by default. It works well enough on a single device, but it has real limits that bite you the moment players care about their progress. localStorage is device-bound — a player who builds a 200-hour farm on their laptop has zero access to it on their phone. It's volatile — clearing browser data wipes everything. And it's small — most browsers cap it at 5-10MB per origin.

If your game has any retention at all, players will eventually ask: "Can I play on my other device?" or worse, they'll lose their save and never come back. Cloud saves solve both problems, but building a sync system for a browser game introduces challenges you won't find in native game development.

localStorage Gotchas You Should Know

Before you build cloud sync, understand exactly how localStorage fails. These aren't edge cases — they're common enough that your players will hit them.

IndexedDB as a Better Local Store

IndexedDB solves most of localStorage's pain points. It's asynchronous, so reads and writes don't block the game loop. It supports structured data natively — you can store objects, arrays, and binary blobs without serializing to strings. And the storage limits are dramatically higher: most browsers allow hundreds of megabytes, sometimes gigabytes, per origin.

The trade-off is complexity. IndexedDB has a callback-heavy API that feels archaic compared to modern JavaScript. Wrap it in a Promise-based helper or use a library like idb (by Jake Archibald, under 2KB gzipped) that makes it feel like a normal async key-value store.

IndexedDB also survives Safari's 7-day purge if you request persistent storage via navigator.storage.persist(). Browser support is excellent — every modern browser has had IndexedDB since 2015. Use it as your primary local store, with localStorage as a fallback for settings and small metadata.

FeaturelocalStorageIndexedDB
APISynchronousAsynchronous
Capacity5-10 MBHundreds of MB+
Data typesStrings onlyObjects, blobs, arrays
Safari purge riskHigh (7 days)Low (with persist())
Performance impactBlocks main threadNon-blocking

When You Need Cloud Sync

Not every browser game needs a backend. If your game is a quick 10-minute puzzle, localStorage is fine. But you need cloud sync when:

Server-Side Architecture

A cloud save backend can be surprisingly simple. At its core, you need three things: an identity, a storage endpoint, and versioned save slots.

Identity. For casual browser games, anonymous device IDs work well. Generate a UUID on first launch, store it in both localStorage and IndexedDB, and use it as the player's identity. If you want account linking later, let players attach an email to their anonymous ID. Don't force signup to save — that kills retention.

REST endpoint. Two routes cover the basics: PUT /saves/:slotId to upload a save and GET /saves/:slotId to download one. Authenticate requests with the device ID token in a header. Add a GET /saves list endpoint so the game can show available save slots on the load screen.

Versioned slots. Store each save with a monotonically increasing version number. When the client saves, it sends its current version number. The server increments the version on write and returns the new version. This lets you detect conflicts: if the client sends version 5 but the server is on version 6, someone else (or another device) wrote in between.

Conflict Resolution Strategies

Conflicts happen when a player plays offline on two devices and then both come online. You have three options:

For most indie browser games, player choice with a last-write-wins fallback (if the player dismisses the dialog) is the best balance of simplicity and correctness.

Data Format and Versioning

Your cloud save format should be a superset of your local save format with additional metadata for sync. Include a schema version, a sync version (server-assigned), a client ID, and a checksum.

When you add new game features, bump the schema version and write a migration. The server should store saves as opaque blobs — it doesn't need to understand the save format. The client handles all migration logic on load.

Design your schema for backward compatibility from day one. New fields should have defaults so that an old save loaded without migration still produces a playable state. Never rename or remove fields in the same version — add a new field and deprecate the old one.

Compression and Bandwidth

Save data grows as players progress. A farming game save might start at 2KB and grow to 200KB after 100 hours. Uploading 200KB every 30 seconds burns mobile data and hits rate limits.

Security Considerations

Never trust client data. A player can open DevTools and send any JSON to your save endpoint. Server-side validation is essential:

The Free Stack: Cloudflare Workers KV + anonymous device IDs = cloud saves with zero monthly cost for indie games. Workers gives you 100,000 free requests per day and KV gives you 1,000 free writes per day. For a game with under 500 daily active players syncing every 2 minutes, that's comfortably within the free tier. The Worker is 30 lines of code: authenticate the device ID, read/write the KV key, return the save. Total cost: $0/month.

Offline-First Design

Cloud saves should enhance local saves, not replace them. Design your system offline-first:

The golden rule: if the internet goes down mid-session, the player should notice nothing. They keep playing, saves keep writing locally, and everything syncs when the connection comes back. Your game should never show a "connection required" error for save functionality.

Play FarmHeart Free

A cozy 3D farming game that runs right in your browser. No download, no signup.

Play Now