Contents
Building A Cloud Save System In Godot
Plan a Godot save system with Persistly local-first slots, cloud sync boundaries, and safe scene restoration.
Godot makes local file saves straightforward, but Persistly already handles the local-first save boundary for the game. Keep scene restoration separate from sync transport so your game can save immediately, work offline, and recover cleanly.
This guide uses the public Godot addon facade shape: configure, save_data, load_data, save_slot, load_slot, and force_sync.
Save Data Contract
Save stable game facts, not node instances.
func build_save_data(player: Node) -> Dictionary:
return {
"schemaVersion": 1,
"updatedAt": Time.get_datetime_string_from_system(true),
"scene": get_tree().current_scene.scene_file_path,
"player": {
"health": player.health,
"coins": player.coins,
"checkpointId": player.checkpoint_id
},
"inventory": player.inventory.to_save_array()
}Save Data Boundaries
| Store | Best For | Avoid |
|---|---|---|
| Persistly local slot | Fast boot, offline play, pending writes | Secrets or account authority |
| Persistly cloud sync | Cross-device continuity, backup | Frame-by-frame transient data |
| Game backend | Economy authority, multiplayer data | Client-trusted progression claims |
Persistly Save First
Call the Persistly addon when the game reaches a meaningful save point. save_data and save_slot write to the addon local cache first, then force_sync_data, force_sync, sync_due_slots, or sync_due can push pending data to the cloud when the network is available.
const SAVE_SLOT := "default"
const PersistlyGameSaves = preload("res://addons/persistly/persistly_game_saves.gd")
var persistly := PersistlyGameSaves.new()
func configure_persistly() -> void:
persistly.configure({
"runtime_key": "ps_test_replace_me"
})
func save_checkpoint(save_data: Dictionary) -> void:
var saved := persistly.save_slot(SAVE_SLOT, save_data, {
"slotInfo": {
"scene": save_data.get("scene", ""),
"checkpointId": save_data.get("player", {}).get("checkpointId", ""),
"updatedAt": save_data.get("updatedAt", "")
}
})
if saved.get("status") == PersistlyGameSaves.PersistlySlotStatus.LOCAL_SAVED:
sync_status_changed.emit("saved_local")Treat client saves as player-owned cache. Validate shape and version before applying loaded data, especially if progression affects purchases, unlocks, or multiplayer access.
Cloud Sync Adapter
Keep Persistly calls behind an adapter. Scene code should ask for save operations, not construct HTTP requests directly. The adapter can save locally immediately and sync later without a second game-owned save file.
const SAVE_SLOT := "default"
func save_now(save_data: Dictionary) -> void:
save_checkpoint(save_data)
func sync_save(save_data: Dictionary) -> void:
save_checkpoint(save_data)
sync_status_changed.emit("syncing")
var result := persistly.force_sync(SAVE_SLOT)
if result.get("status") == PersistlyGameSaves.PersistlySlotStatus.SYNCED:
sync_status_changed.emit("saved")
else:
sync_status_changed.emit("saved_offline")Restoring Scenes Safely
Do not apply save data until the target scene has loaded and required nodes exist.
Restore Flow
- Parse save data.
- Validate
schemaVersion. - Load saved scene or fallback scene.
- Wait for scene-ready signal.
- Apply player and inventory data.
- Recompute derived values such as quest markers.
func apply_save(save_data: Dictionary) -> void:
if save_data.get("schemaVersion") != 1:
push_warning("Unsupported save schema")
return
var scene_path = save_data.get("scene", "res://scenes/start.tscn")
await get_tree().change_scene_to_file(scene_path)
await get_tree().process_frame
var player = get_tree().current_scene.get_node("Player")
player.health = save_data.player.health
player.coins = save_data.player.coins
player.restore_checkpoint(save_data.player.checkpointId)If you want to load the local Persistly slot first, keep the same validation boundary:
func load_from_persistly() -> void:
var loaded := persistly.load_slot(SAVE_SLOT)
var data = loaded.get("data", {})
if typeof(data) == TYPE_DICTIONARY and not data.is_empty():
apply_save(data)Production Checklist
- Save through Persistly at meaningful checkpoints, not every frame.
- Let the Persistly addon keep pending writes locally when cloud sync fails.
- Retry cloud sync with backoff or on lifecycle/network events.
- Validate dictionary keys before applying data.
- Store checkpoint IDs instead of fragile coordinates.
- Test with network disabled before and after scene transitions.