Cloud Saves
Save player progress to the cloud so they can continue on any device. The SDK handles version conflicts, size validation, and local caching with offline fallback.
Functions
Section titled “Functions”save_game()
Section titled “save_game()”QuestData.save_game(data: Dictionary, callback: Callable = Callable(), conflict_strategy: int = QuestData.SAVE_CONFLICT_DEFER)| Parameter | Type | Default | Description |
|---|---|---|---|
data | Dictionary | required | Game state to save (must be JSON-serializable) |
callback | Callable | Callable() | Called with a response Dictionary |
conflict_strategy | int | SAVE_CONFLICT_DEFER | How to handle a server-side version conflict (HTTP 409). See Conflict Strategies below |
Callback response (success):
| Key | Type | Description |
|---|---|---|
success | bool | true |
version | int | New save version number |
updated_at | String | ISO timestamp |
Callback response (conflict, only with SAVE_CONFLICT_DEFER):
| Key | Type | Description |
|---|---|---|
success | bool | false |
error | String | "Version conflict" |
conflict | bool | true |
server_version | int | The server’s current version |
server_data | Dictionary | The server’s current save data |
Conflict Strategies
Section titled “Conflict Strategies”A version conflict happens when the server has a newer save than the local cache — typically because the player saved on another device, or because two save calls raced. The SDK supports multiple strategies for resolving this. Pass one as the third argument to save_game():
| Constant | Value | Behavior |
|---|---|---|
QuestData.SAVE_CONFLICT_DEFER | 0 | Default. Return the conflict to your callback so you can decide. Use this if you want to show a UI, do an auto-merge, or pick a strategy at runtime |
QuestData.SAVE_CONFLICT_OVERWRITE | 1 | Last-Write-Wins. SDK silently adopts server_version, retries the save once, and your callback fires with success=true on the retry. The server’s old data is overwritten by your local data |
Reserved for future SDK versions: SAVE_CONFLICT_SERVER_WINS (2), SAVE_CONFLICT_MERGE (3), SAVE_CONFLICT_USER_CHOICE (4).
Which to pick:
- Idle / casual / single-device games: use
SAVE_CONFLICT_OVERWRITE. Local progress is the source of truth and a conflict almost always means a stale server entry from a long-abandoned device. Silent overwrite gives the smoothest UX - Multi-device games (mobile + desktop, console crossplay): use
SAVE_CONFLICT_DEFERand show a “Cloud save is newer — Use Cloud / Use Local / Merge” dialog. Otherwise players who switch devices lose progress without warning - Single-player roguelikes / RPGs: use
SAVE_CONFLICT_DEFERso a duplicate save from a forgotten session doesn’t clobber a current 30-hour run
Example — Last-Write-Wins for an idle game:
func save_to_cloud(): var data = _serialize_player_state() QuestData.save_game(data, func(result: Dictionary): if result.get("success", false): print("Cloud save OK, version=", result.version) else: push_warning("Cloud save failed: ", result.get("error")) , QuestData.SAVE_CONFLICT_OVERWRITE)Example — Defer with user choice:
func save_to_cloud(): var data = _serialize_player_state() QuestData.save_game(data, func(result: Dictionary): if result.get("success", false): return if result.get("conflict", false): _show_conflict_dialog(data, result.server_data, result.server_version) else: push_warning("Cloud save failed: ", result.get("error")) ) # third arg defaults to SAVE_CONFLICT_DEFERload_game()
Section titled “load_game()”QuestData.load_game(callback: Callable = Callable())Callback signature: func(data: Dictionary, version: int)
If no save exists on the server, data is an empty Dictionary and version is 0.
delete_save()
Section titled “delete_save()”QuestData.delete_save(callback: Callable = Callable())Callback signature: func(deleted: bool)
get_save_cached()
Section titled “get_save_cached()”QuestData.get_save_cached() -> DictionaryReturns the last known save data from the local cache. Returns an empty Dictionary if no save has been loaded yet.
get_save_version()
Section titled “get_save_version()”QuestData.get_save_version() -> intReturns the current save version number. Returns 0 if no save has been loaded.
clear_save_cache()
Section titled “clear_save_cache()”QuestData.clear_save_cache()Clears the local save cache.
Example
Section titled “Example”# Save progress at a checkpointfunc save_at_checkpoint(): var save_data = { "level": current_level, "health": player.health, "inventory": player.get_inventory(), "playtime_seconds": get_playtime(), "achievements": unlocked_achievements }
QuestData.save_game(save_data, func(response: Dictionary): if response.get("success"): show_toast("Game saved!") elif response.get("conflict"): handle_conflict(response) else: show_toast("Save failed: " + response.get("error", "Unknown error")) )
# Load on game startfunc _ready(): QuestData.load_game(func(data: Dictionary, version: int): if data.is_empty(): start_new_game() else: restore_game_state(data) print("Loaded save v%d" % version) )
# Delete save (new game)func start_fresh(): QuestData.delete_save(func(deleted: bool): if deleted: QuestData.clear_save_cache() start_new_game() )Handling Version Conflicts
Section titled “Handling Version Conflicts”Version conflicts happen when the save was modified from another device or session. The server returns the conflicting data so you can resolve it:
func handle_conflict(response: Dictionary): var server_data = response["server_data"] var local_data = get_current_save_data()
# Strategy: keep the save with more playtime if local_data.get("playtime_seconds", 0) > server_data.get("playtime_seconds", 0): # Force overwrite with local data (clear cache to reset version) QuestData.clear_save_cache() QuestData.save_game(local_data) else: # Use server data restore_game_state(server_data)How It Works
Section titled “How It Works”save_game()sends a PUT request with the save data and the current version number- The server checks if the version matches — if not, it returns a
409 Conflictwith the server’s data - On success, the local cache is updated with the new version
load_game()fetches from the server and updates the local cache- Save data is cached to disk for offline access
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Save data size | 100 KB max |
| Serialization | Must be JSON-compatible (no Objects, no Callables) |
Dashboard
Section titled “Dashboard”View and manage saves under Players > Cloud Saves:
- Save browser — Search saves by player ID
- Save data — View the raw JSON data
- Version history — See when saves were created/updated
Best Practices
Section titled “Best Practices”- Save at meaningful moments — Checkpoints, level completions, shop exits — not on every frame
- Keep saves small — Store IDs and numbers, not full object trees. 100 KB is the limit
- Handle conflicts — Always check for
conflict: truein the callback response - Use
get_save_cached()for quick reads — It’s synchronous and doesn’t hit the network - Test offline behavior — The SDK falls back to cached data when the server is unreachable