Contents
Unity Cloud Save Lifecycle: Save Locally, Sync Safely, Handle Conflicts
A practical Unity cloud-save lifecycle for local-first progress, safe sync moments, conflict recovery, and authenticated saves.
Unity cloud saves should feel boring to the player. Progress should survive app pauses, network drops, low-battery shutdowns, and device switches without making gameplay wait on every request.
The simplest Persistly lifecycle is:
- Configure the Unity SDK once.
- Load local data before gameplay starts.
- Save locally whenever important state changes.
- Sync at controlled moments.
- Handle conflicts explicitly before overwriting anything.
That model works for a one-save idle game, a mobile progression game, or a slot-based RPG. The difference is mostly whether you use SaveDataAsync for one autosave or SaveSlotAsync for named slots.
Configure Once
Configure Persistly during boot or before the first save operation:
using Persistly.Unity;
using UnityEngine;
await PersistlyGameSaves.ConfigureAsync(new PersistlyGameSavesSettings("ps_test_replace_me")
{
Store = new FilePersistlyGameSavesStore(Application.persistentDataPath),
});Use a Stage runtime key while building. Switch to a Production runtime key only in release builds.
Load Local Data First
Do not block the title screen on a cloud request. Load the local copy first so the game can start when the player is offline.
var loaded = await PersistlyGameSaves.Shared.LoadDataAsync<PlayerSaveData>();
if (loaded.Status == PersistlySlotStatus.LocalFound && loaded.State != null)
{
StartGameFromState(loaded.State);
}
else
{
StartNewGame();
}If the game needs a cloud check before play, do it as a deliberate step after the local state is valid.
Save Locally When Progress Changes
SaveDataAsync writes locally first. This is the method most one-save Unity games should call when progress changes.
await PersistlyGameSaves.Shared.SaveDataAsync(new PlayerSaveData
{
Level = player.Level,
Coins = player.Coins,
Checkpoint = checkpoint.Id,
UpdatedAt = DateTimeOffset.UtcNow.ToString("O"),
});This should not be treated as a network-only operation. If cloud sync fails later, the local save still exists and can be retried.
Sync At Safe Moments
Do not sync every frame. Sync after meaningful checkpoints, manual saves, pause events, level completion, or other stable gameplay moments.
private async void OnApplicationPause(bool paused)
{
if (!paused)
{
return;
}
await PersistlyGameSaves.Shared.SaveDataAsync(currentState);
await PersistlyGameSaves.Shared.ForceSyncDataAsync();
}For longer sessions, you can also run a controlled periodic sync. Keep the interval measured in seconds or minutes, not frames.
Keep One Save Until You Need Slots
Many Unity games do not need a save-slot menu. Idle games, roguelite progression, prototypes, and casual games often start with one autosave:
await PersistlyGameSaves.Shared.SaveDataAsync(playerState);
var loaded = await PersistlyGameSaves.Shared.LoadDataAsync<PlayerSaveData>();Use named slots only when the player expects separate campaigns, characters, manual saves, or challenge runs:
await PersistlyGameSaves.Shared.SaveSlotAsync("warrior", warriorState);
await PersistlyGameSaves.Shared.SaveSlotAsync("mage", mageState);The slot key should be stable. Display names can change; slot keys should not.
Use SlotInfo For Save Menus
Keep full gameplay state in the typed save object. Use slotInfo for lightweight save-menu fields and support context.
await PersistlyGameSaves.Shared.SaveSlotAsync(
"warrior",
warriorState,
new PersistlySaveSlotOptions
{
SlotInfoJson = "{\"characterName\":\"Ayla\",\"level\":12,\"className\":\"Warrior\"}",
});The game can list slots and render a menu without loading every full save payload.
Handle Conflicts Explicitly
Conflicts are normal when multiple devices save the same slot. Do not silently overwrite one branch.
var sync = await PersistlyGameSaves.Shared.ForceSyncDataAsync();
if (sync.Status == PersistlySlotStatus.Conflict)
{
var inspection = PersistlyGameSaves.Shared.InspectData();
ShowConflictDialog(inspection);
return;
}After the player chooses, use the facade helper that matches the decision:
await PersistlyGameSaves.Shared.AcceptCloudDataAsync();
await PersistlyGameSaves.Shared.OverwriteCloudDataAsync();
await PersistlyGameSaves.Shared.KeepLocalDataForLaterAsync();For premium currency, purchases, competitive inventory, or anti-cheat-sensitive state, keep authority in a trusted backend. Cloud saves are for durable player progress, not server-authoritative economy validation.
Add Auth Bridge When The Game Already Has Sign-In
If your Unity game already signs players in with Firebase Auth, Supabase Auth, or Auth0, Persistly Auth Bridge can exchange the provider token for a Persistly account session.
await PersistlyGameSaves.ConfigureAsync(new PersistlyGameSavesSettings("ps_test_replace_me")
{
AccountMode = PersistlyAccountMode.AuthRequired,
});
var firebaseIdToken = await GetFirebaseIdTokenAsync();
await PersistlyGameSaves.Shared.SignInWithFirebaseTokenAsync(firebaseIdToken);After that, save/load/sync calls use the Persistly account session. Do not send provider tokens to normal save routes, logs, telemetry, or support screenshots.
Production Checklist
- Pin the Unity package to a stable tag.
- Use Stage keys for development and Production keys for release builds.
- Load local state before gameplay.
- Save locally on meaningful progress changes.
- Sync on checkpoints, pause, manual save, or controlled intervals.
- Add conflict UI before cross-device launch.
- Keep provider tokens and Persistly session tokens out of logs.
- Keep trusted economy and purchase validation out of client-only save data.
Next, read the Unity docs for setup, create/load/sync, and conflict handling.