PersistlyGameSaves is the default Unity facade for normal game integrations.
Unity SDK
Unity SDK for simple game saves and advanced runtime control.
Use PersistlyGameSaves for normal Unity integrations. Drop to PersistlyClient only when you intentionally need account, slot, or raw runtime contract control.
Simple Game Saves
Start with the Unity facade, not raw account/session plumbing.
SaveDataAsync and LoadDataAsync are the fastest one-save path; SaveSlotAsync and LoadSlotAsync remain the named-slot path for slots and manual saves.
The runtime client still exists for advanced account and slot integrations.
PersistlySlotStatus and PersistlySyncStatus are real enums; do not compare magic strings.
Conflict responses preserve the cloud slot branch and typed conflict details so Unity projects can reconcile intentionally.
Advanced Runtime Client
Use this layer only when your integration needs direct account and slot control.
PersistlyClient handles account creation, account loading, slot creation/loading/sync, runtime config, and local cache coordination.
PersistlyCreateAccountRequest, PersistlyCreateAccountSlotRequest, and PersistlySyncAccountSlotRequest accept JSON object strings for accountData, slotInfo, and data.
PersistlySyncResponse models both accepted and conflict outcomes. Accepted responses are lightweight; conflicts include the cloud conflict branch.
PersistlyCreateAccountResponse returns accountId, accountSessionToken, the account state, and the first slot.
Copy-pasteable saveSlot/loadSlot flow using the Unity facade.
Copy one-save, multi-slot, or account + slots service wrappers before custom architecture.
Typed conflict recovery with PersistlySyncStatus and cloud conflict branches.
Cross-device restore, shared accountData, lifecycle sync, and safe cleanup.
Install
Use the public Unity package source, not a private checkout path.
Open Package Manager -> Add package from git URL -> https://github.com/Persistly/persistly-sdk-unity.gitRepository
Unity SDK source
Use the public Unity repository for package metadata, source, sample project, releases, and issues.
Open GitHub repoSample
Last Beacon
Validate the Unity flow through the official playable sample before adapting it to your game.
Open sample guideSetup
Configure once, then save and load game data.
using Persistly.Unity;
await PersistlyGameSaves.ConfigureAsync(new PersistlyGameSavesSettings("ps_test_replace_me")
{
PlayerRef = "player-184",
});
var saved = await PersistlyGameSaves.Shared.SaveDataAsync(new PlayerData
{
Level = 5,
Coins = 1200,
Checkpoint = "forest-gate",
});
if (saved.Status == PersistlySlotStatus.LocalSaved)
{
UnityEngine.Debug.Log("Saved locally. Call ForceSyncDataAsync or SyncDueAsync from a safe lifecycle moment.");
}
var loaded = await PersistlyGameSaves.Shared.LoadDataAsync<PlayerData>();
var sync = await PersistlyGameSaves.Shared.ForceSyncDataAsync();Autosave and Local Storage
Write local state first, then sync at safe lifecycle points.
await PersistlyGameSaves.Shared.SaveDataAsync(currentState);
private async void OnApplicationPause(bool pause)
{
if (pause)
{
await PersistlyGameSaves.Shared.ForceSyncDataAsync();
}
}Conflict Handling
Use typed statuses when you are handling raw runtime sync results.
var result = await client.SyncAccountSlotAsync(
accountId,
accountSessionToken,
slotId,
new PersistlySyncAccountSlotRequest(
dataJson: nextDataJson,
baseVersion: local.Version,
slotInfoJson: local.SlotInfoJson
)
);
if (result.Status == PersistlySyncStatus.Accepted)
{
local = result.Slot;
}
if (result.Status == PersistlySyncStatus.Conflict)
{
var cloudSlot = result.Slot;
if (result.Details?.Reason == PersistlySyncConflictReason.BaseVersionMismatch)
{
// Keep cloudSlot as the baseline. If the player keeps local edits,
// retry with baseVersion: cloudSlot.Version and the local draft data.
}
}Advanced Runtime Client
Create an account and sync a slot directly when you need the lower-level contract.
using Persistly.Unity;
var client = new PersistlyClient(new PersistlyClientOptions(
"https://api.persistly.app",
"ps_test_..."
));
var created = await client.CreateAccountAsync(new PersistlyCreateAccountRequest(
accountDataJson: "{\"diamonds\":1200}",
playerRef: "player-184",
slot: new PersistlyCreateAccountInitialSlotRequest(
"mage",
"{\"characterName\":\"Ayla\",\"slotLabel\":\"Mage\"}",
"{\"coins\":418,\"checkpoint\":\"vault\"}"
)
));
var accountId = created.AccountId;
var accountSessionToken = created.AccountSessionToken;
var slotId = "mage";
var slot = created.Slot;
var sync = await client.SyncAccountSlotAsync(
accountId,
accountSessionToken,
slotId,
new PersistlySyncAccountSlotRequest(
dataJson: "{\"coins\":612,\"checkpoint\":\"reactor\"}",
baseVersion: slot.Version,
slotInfoJson: slot.SlotInfoJson
)
);Playable Sample
Use Last Beacon to validate the full flow inside Unity.
The sample project lives under sdks/unity/SampleProject and includes a generated Assets/Scenes/LastBeacon.unity scene.
The game uses a local account store, explicit Persistly configuration fields, and the same account/session/slot contract as the JavaScript and Godot tracks.
The UI is intentionally asset-light and built with IMGUI so the sample proves network and persistence behavior without requiring art pipelines.
Behavior Notes
What is enforced by the SDK itself.
Payload sizes are validated locally before runtime requests leave the game client: 16 KB for slotInfo and 256 KB for slot data.
The client caches cloud branchs in memory and can infer baseVersion for sync if the save has already been loaded or created in-process.
Structured API errors map to typed exceptions such as invalid request, unauthorized, conflict, rate limited, and payload too large.
Validation
Use the public sample and repository checks as the verification path.
Run the Last Beacon sample scene from the official Unity repository and verify create, load, sync, and conflict handling with a ps_test_ key first.
Keep the same API origin and save contract when you move from the sample into your production game project.