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();Player Auth
Use Auth Bridge helpers before normal save calls.
Configure the account mode, sign in with a Firebase, Supabase, or Auth0 token, then continue using SaveDataAsync and LoadDataAsync.
using Persistly.Unity;
await PersistlyGameSaves.ConfigureAsync(new PersistlyGameSavesSettings("ps_test_replace_me")
{
AccountMode = PersistlyAccountMode.AuthRequired,
});
var idToken = await GetFirebaseIdTokenAsync();
await PersistlyGameSaves.Shared.SignInWithFirebaseTokenAsync(idToken);
await PersistlyGameSaves.Shared.SaveDataAsync(new PlayerData
{
Level = 5,
Coins = 1200,
});Connect Later
Connect anonymous progress to a provider account later.
Use ConnectWithFirebaseTokenAsync, ConnectWithSupabaseTokenAsync, ConnectWithAuth0TokenAsync, or ConnectProviderAsync after your game receives the provider token.
await PersistlyGameSaves.ConfigureAsync(new PersistlyGameSavesSettings("ps_test_replace_me")
{
AccountMode = PersistlyAccountMode.AnonymousFirst,
});
await PersistlyGameSaves.Shared.SaveDataAsync(new PlayerData { Level = 7, Coins = 420 });
await PersistlyGameSaves.Shared.ForceSyncDataAsync();
var token = await GetProviderTokenAsync();
try
{
await PersistlyGameSaves.Shared.ConnectWithFirebaseTokenAsync(token);
}
catch (PersistlyApiError error) when (error.Code == PersistlyErrorCode.AccountAuthConflict)
{
ShowAuthConflictChoicesUI(error.DetailsJson);
}Auth
Firebase Auth Bridge
Configure Firebase Auth per Persistly environment, then use the Unity SDK auth helpers.
Open Firebase guideAuth
Supabase Auth Bridge
Configure Supabase Auth per Persistly environment, then use the Unity SDK auth helpers.
Open Supabase guideAuth
Account modes
Choose anonymousFirst or authRequired and define sign-out purge behavior.
Open account modesFirebase Provider Helper
Use the generic provider helper when that fits your service wrapper.
await PersistlyGameSaves.ConfigureAsync(new PersistlyGameSavesSettings("ps_test_replace_me")
{
AccountMode = PersistlyAccountMode.AuthRequired,
});
var token = await GetFirebaseIdTokenAsync();
try
{
await PersistlyGameSaves.Shared.SignInWithProviderAsync(
provider: "firebase",
token: token,
deviceLabel: "unity");
}
catch (PersistlyApiError error) when (error.Code == PersistlyErrorCode.AccountAuthConflict)
{
ShowAccountConflictUI(error.DetailsJson);
}Supabase Provider Helper
Use the generic provider helper when that fits your service wrapper.
await PersistlyGameSaves.ConfigureAsync(new PersistlyGameSavesSettings("ps_test_replace_me")
{
AccountMode = PersistlyAccountMode.AuthRequired,
});
var token = await GetSupabaseAccessTokenAsync();
try
{
await PersistlyGameSaves.Shared.SignInWithProviderAsync(
provider: "supabase",
token: token,
deviceLabel: "unity");
}
catch (PersistlyApiError error) when (error.Code == PersistlyErrorCode.AccountAuthConflict)
{
ShowAccountConflictUI(error.DetailsJson);
}Auth0 Provider Helper
Use the generic provider helper when that fits your service wrapper.
await PersistlyGameSaves.ConfigureAsync(new PersistlyGameSavesSettings("ps_test_replace_me")
{
AccountMode = PersistlyAccountMode.AuthRequired,
});
var token = await GetAuth0TokenAsync();
try
{
await PersistlyGameSaves.Shared.SignInWithProviderAsync(
new PersistlyProviderSignInRequest(PersistlyAuthProvider.Auth0, token)
{
DeviceLabel = "unity"
});
}
catch (PersistlyApiError error) when (error.Code == PersistlyErrorCode.AccountAuthConflict)
{
ShowAccountConflictUI(error.DetailsJson);
}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.