PersistlyGameSaves is the default Godot facade for normal game integrations.
Godot SDK
Godot SDK for simple game saves and advanced runtime control.
Use PersistlyGameSaves for normal Godot integrations. Drop to PersistlyClient only when you intentionally need account, slot, or raw runtime contract control.
Simple Game Saves
Start with the Godot facade, not raw account/session plumbing.
save_data and load_data use the default autosave slot for one-save games; save_slot and load_slot remain available for named slots.
The runtime client still exists for advanced account and slot integrations.
Use PersistlySlotStatus constants instead of raw status strings in game code.
Conflict responses preserve the cloud slot branch so Godot games can replace local canonical data before reconciling edits.
Advanced Runtime Client
Use this layer only when your integration needs direct account and slot control.
create_account(payload) creates one account, one first slot, and one account session token.
load_account(account_id, account_session_token) loads the account through its session.
sync_account_slot(...) accepts lightweight success responses and keeps cloud slot data separate on conflicts.
Copy-pasteable save_slot/load_slot flow using the Godot facade.
Copy one-save, multi-slot, or account + slots service wrappers before custom architecture.
Conflict recovery with cloud conflict branches and explicit details.reason handling.
Cross-device restore, shared accountData, lifecycle sync, and safe cleanup.
Install
Use the public Godot addon source, not a private checkout path.
Download the addon from the official GitHub repository and copy addons/persistly into your Godot project.Repository
Godot SDK source
Use the public Godot repository for addon source, examples, releases, and issues.
Open GitHub repoSample
Last Beacon
Validate the Godot flow through the official sample before adapting it to your game.
Open sample guideSetup
Configure once, then save and load named slots.
const PersistlyGameSaves = preload("res://addons/persistly/persistly_game_saves.gd")
var persistly := PersistlyGameSaves.new()
persistly.configure({
"runtime_key": "ps_test_replace_me",
})
var saved := persistly.save_data({
"level": 5,
"coins": 1200,
"checkpoint": "forest-gate",
}, {
"characterName": "Astra",
"slot": "main",
})
if saved.get("status") == PersistlyGameSaves.PersistlySlotStatus.LOCAL_SAVED:
print("Saved locally. Call force_sync_data or sync_due from a safe lifecycle moment.")
var loaded := persistly.load_data()
var sync := persistly.force_sync_data()Autosave and Local Storage
Write local state first, then force sync at safe lifecycle points.
persistly.save_data(current_state)
func _notification(what):
if what == NOTIFICATION_WM_CLOSE_REQUEST:
persistly.force_sync_data()Conflict Handling
Use exported status constants in facade code and explicit statuses in advanced sync results.
const SYNC_STATUS_ACCEPTED := "accepted"
const SYNC_STATUS_CONFLICT := "conflict"
const CONFLICT_REASON_BASE_VERSION_MISMATCH := "base_version_mismatch"
var result := client.sync_account_slot(account_id, account_session_token, slot_id, {
"baseVersion": local_save["version"],
"slotInfo": local_save["slotInfo"],
"data": next_data
})
if result.get("status") == SYNC_STATUS_ACCEPTED:
local_save = result["slot"]
if result.get("status") == SYNC_STATUS_CONFLICT:
var cloud_slot := result["slot"]
var details := result.get("details", {})
if details.get("reason") == CONFLICT_REASON_BASE_VERSION_MISMATCH:
# Keep cloud_slot as the baseline. If the player keeps local edits,
# retry with baseVersion = cloud_slot["version"] and the local draft data.
passAdvanced Runtime Client
Create an account and sync a slot directly when you need the lower-level contract.
var client := PersistlyClient.new(
"https://api.persistly.app",
"ps_test_..."
)
var created := client.create_account({
"playerRef": "player-184",
"accountData": { "diamonds": 1200 },
"slot": {
"slotId": "mage",
"slotInfo": {
"characterName": "Ayla",
"slotLabel": "Mage"
},
"data": {
"coins": 418,
"checkpoint": "vault"
}
}
})
var account_id := created["accountId"]
var account_session_token := created["accountSessionToken"]
var slot_id := "mage"
var slot := created["slot"]
var result := client.sync_account_slot(account_id, account_session_token, slot_id, {
"baseVersion": slot["version"],
"slotInfo": slot["slotInfo"],
"data": {
"coins": 612,
"checkpoint": "reactor"
}
})Behavior Notes
What the current Godot SDK enforces.
HTTP and HTTPS runtime requests go through HTTPClient with an explicit bearer runtime key and timeout.
Payload size checks run locally before transport: 16 KB for slotInfo and 256 KB for slot data.
A small in-memory cache stores cloud slot branchs by slotId and can provide baseVersion when the caller omits it during account slot sync.
Playable Demo
Use the public Last Beacon project to verify a real engine integration.
The official Godot repository includes Last Beacon, an endless idle sample project that creates an account, stores the session locally, and syncs a slot from a custom game UI.
Use the sample to confirm gameplay flow, accountId handling, accountSessionToken persistence, and conflict behavior before you wrap Persistly in your own project abstractions.
Validation
Validate against the public sample and your stage environment.
Run the official sample with a ps_test_ key and verify create, load, sync, and a forced conflict before using ps_live_ in production.
Keep your game UI custom. Persistly should stay behind the scenes as the save-sync layer.