HTML5 game publishing
Cloud Save With a Local Fallback in the PlayKovo SDK
When to use cloudSave and when storage, why you must check ok instead of catching errors, how to save at checkpoints and a design that reads whichever save is newer.
Players expect to come back to a game and find their progress. In a browser, that means saving somewhere. The PlayKovo SDK offers two places: cloudSave, tied to the player's PlayKovo account, and storage, tied to the device. Using only one of them is a mistake. This post shows the pattern the documentation recommends: try the cloud, fall back to the device, never block play.
What each one is
cloudSave | storage | |
|---|---|---|
| Tied to | The signed-in player's PlayKovo account | The current device |
| Follows the player across devices | Yes, when signed in | No |
| Works for guests | No | Yes |
| Data type | JSON-serializable data | String key/value (compatibility alias: localStorage) |
| Failure behaviour | Resolves to a result with ok, does not throw | Standard storage behaviour |
The documentation describes storage as the right default for anything that does not need to follow the player, and as the fallback when a player is not signed in.
Check ok, not just the data
Both cloudSave.get() and cloudSave.set() resolve to a result object instead of throwing. That is friendly, but it means a try/catch will not catch a failed save. You have to read the result:
const save = await window.PlayKovoSDK.cloudSave.get();
if (save.ok && save.data) {
applySaveData(save.data);
} else {
applySaveData(loadLocalFallback());
}
Note the second part of the condition. ok can be true with no data, which simply means a new player with nothing saved yet. Treat that the same as a fresh start.
Writing: after checkpoints, not every frame
async function onLevelComplete(levelId, score) {
const result = await window.PlayKovoSDK.cloudSave.set({
levelId, score, updatedAt: Date.now()
});
if (!result.ok) {
// guest, offline, or no PlayKovo session
window.PlayKovoSDK.storage.setItem(
"save-slot", JSON.stringify({ levelId, score }));
}
}
The documentation says to write after a meaningful checkpoint, not every frame. Level complete, shop purchase, settings changed and a new record are all good moments. A save inside your update loop is wasteful and gives you nothing a checkpoint save would not.
Always write local too?
There are two reasonable designs:
- Cloud first, local on failure. What the example above does. Simple, with the risk that a signed-in player's local copy is stale.
- Write both, read the newest. Save to local every time and to the cloud when you can. On boot, read both and use the one with the later
updatedAt.
The second design covers a player who played offline on a train and then opened the game at home. The updatedAt field in the example is there for exactly this comparison. Keep it as a number you set yourself, since you cannot rely on the order in which the stores answer.
What to put in a save
- A version number. When you change the format later, old saves need to be recognised and upgraded.
- Only what you need. Level, currency, unlocks, settings. Not the entire world state if you can rebuild it.
- No personal data. The save is for the game. Do not store emails or names in it.
{ "v": 2, "levelId": 14, "coins": 320, "unlocked": ["blue", "gold"], "updatedAt": 1767225600000 }
Do not block the game
Two sentences from the documentation define the rule: do not block gameplay waiting on a cloud save, and fall back to storage so the run is not lost. Start the menu while the save loads if you can, and show a short "loading save" state instead of freezing. If the cloud does not answer promptly, start from local data.
Our post on the boot sequence, init() and the offline boot, shows where this step sits. Notice that init() rejects after about eight seconds, so a boot that depends on a cloud save should have its own fallback.
Testing
- Play as a guest: the cloud call should return
ok: falseand the game should use storage. - Block the network and reload: the game should start.
- Corrupt the saved string by hand and reload: the game should not crash, only start fresh.
- Change the version number and check that old saves upgrade.
More on testing without an ad account is in testing on localhost, and the full reference is in the JavaScript SDK docs. A game can be submitted without the SDK, as the FAQ confirms, but then it has no cloud save.


