HTML5 game publishing

Saving Player Progress in an HTML5 Game: localStorage, Iframes and Cloud Save

Why saves vanish in browsers and iframes, how to write save code that survives errors and updates, and how to add PlayKovo cloud save as a fallback-safe extra.

PlayKovo Team3 min read
Games currently available on PlayKovo. Open a title to see its current listing.

"My progress disappeared" is one of the most common complaints about browser games, and in most cases the game did save. The save just wasn't there when the player came back. If you ship HTML5 games, it helps to know why that happens and how to make your save code survive it.

Why localStorage alone is not enough

localStorage is the default choice because it takes one line of code. It is also the least reliable place to keep something a player cares about:

  • The player clears site data. Cleaning tools and "clear cookies and site data" settings wipe it along with everything else.
  • Private windows. Storage may be unavailable, throw an error, or disappear when the window closes.
  • Iframes on other sites. When your game runs inside an iframe on a page that belongs to someone else, modern browsers can block or partition third-party storage. The same game can behave differently on your own domain and on a partner's website.
  • Browser limits. Safari can remove script-written storage for sites a user hasn't interacted with for a while.
  • A new device. Local storage never follows the player to a phone or another computer.

Step 1: never let a storage error break the game

Reading or writing storage can throw. Wrap every call so the worst case is "no save", not a frozen loading screen.

function readSave() {
  try {
    const raw = localStorage.getItem("mygame.save");
    return raw ? JSON.parse(raw) : null;
  } catch (e) {
    return null; // blocked, corrupted or missing
  }
}

function writeSave(data) {
  try {
    localStorage.setItem("mygame.save", JSON.stringify(data));
  } catch (e) {
    // quota full or storage blocked: keep playing
  }
}

Step 2: version your save format

You will change your game after launch. Add a version number to the save object and migrate old saves instead of discarding them. A player who loses a 10-hour save because you renamed a field will not come back.

const save = readSave() || {};
if (!save.version) { save.version = 1; save.coins = save.coins || 0; }
if (save.version === 1) { save.version = 2; save.skins = ["default"]; }

Step 3: save at the right moments

Saving only on a "Quit" button fails, because players close tabs. Save when something meaningful happens (level finished, purchase made) and also when the page is hidden:

document.addEventListener("visibilitychange", () => {
  if (document.visibilityState === "hidden") writeSave(state);
});
window.addEventListener("pagehide", () => writeSave(state));

Do not write on every frame. Writing a large object 60 times a second wastes battery and can slow low-end phones. Save on events, or debounce to once every few seconds.

Step 4: add cloud save for signed-in players

When your game runs in the PlayKovo player, the SDK exposes a cloud save that follows the player's account across devices. It is two calls:

const result = await window.PlayKovoSDK.cloudSave.get();
if (result.ok && result.data) {
  loadFrom(result.data);
}

// later, after a level or purchase
await window.PlayKovoSDK.cloudSave.set(state);

Treat cloud save as an addition, not a replacement:

  • Both calls resolve with an object. Check ok. When it is false (no signed-in player, a timeout after about six seconds, a network error), fall back to your local save and carry on.
  • Guests do not have a cloud save. Keep the local save working for them.
  • If you have both a local and a cloud save, compare a timestamp or progress value you store yourself and keep the more advanced one. Do not silently overwrite a longer save with a shorter one.
  • Keep the payload small: IDs, counters and unlocked items, not whole level maps you can regenerate.

A quick test list

  1. Play, close the tab, reopen: is the progress there?
  2. Open the game in a private window: does it still start?
  3. Embed it in an iframe on a different domain: does the save still work, or at least fail quietly?
  4. Load a save written by your previous version: does the migration run?
  5. Hand it a corrupted value (localStorage.setItem("mygame.save", "{")): does the game start with a fresh save?

If you want to see how players run into this from the other side, read why browser games lose your progress. For the technical side of how PlayKovo hosts and loads your build, see how to prevent old cached files from breaking game updates.

We use optional cookies for site analytics and site advertising. You can change your choice later. Privacy policy.