Cloud Saves
Named save slots for games that need more than one document per player, synced across devices with conflict detection.
saveGameState is the one autosave document and stays the default. Save slots are for games that need more than one: three save files, a run in progress beside a settings profile. Nothing is declared; a slot exists once it is written.
Reading and Writing
const slot = await sdk.saves.get('main') // SaveSlot | null
const result = await sdk.saves.put({
slot: 'main',
data: { level, inventory },
...(slot ? { ifMatch: slot.etag } : { ifNoneMatch: '*' }), // protect existing and first saves
})
if (result && !result.ok && result.conflict) {
// result.conflict is the slot as it is now: merge, or ask the player
}
const slots = await sdk.saves.list() // up to 100 named slots; use storage.list for larger collections
await sdk.saves.delete({ slot: 'main' })
A SaveSlot is { slot, data, etag, updatedAt, bytes }. A slot name is a stable slug you choose.
Conflicts
Pass the etag you read as ifMatch when you write. If another device saved in between, the write is refused with { ok: false, code: 'conflict' } and conflict holding the current slot. Decide then: merge the two, keep the newer, or ask the player. Omit ifMatch only for data where last write wins is fine.
Null Means Nothing to Load
Every call resolves null for guests and on hosts without saves, and get also resolves null for an empty slot or one it cannot read. Read every null the same way. Never block a boot on a save answering.
Storage Limits
Each document is at most 256 KiB of JSON. Your game chooses how many slots each player uses; there is no slot-count cap. Storage usage includes autosaves, named saves, and files across all players, plus per-file metadata overhead. The Remix Desktop dashboard shows one total for your shared creator allowance across all your games.
A write that would grow total storage beyond that allowance returns { ok: false, code: 'quota', reason }, preserving the existing save. Reads, deletions, and writes that keep or reduce storage usage remain available even if the allowance decreases. Slots hold the player's own data only. Never store other players' results or anything the platform owns, such as level unlocks or best scores.
Save files
For most games, use JSON documents through sdk.saves.get and sdk.saves.put, or keep your existing saveGameState autosave. Save at checkpoints or after a run, rather than every frame. Cloud saves can hold your economy, upgrades, internal counters and full in-game achievement checklist. When a saved number crosses a milestone worth showing outside the game, unlock one of your declared achievements; not every saved number or badge needs a platform declaration. Use the leaderboard API for anything players compete on.
Existing file integrations can continue using sdk.storage.put/get/head/list/delete. Files accept up to 1 MiB each; listings return up to 100 entries with a continuation cursor. put accepts bytes (any typed array or an ArrayBuffer) or a JSON object, and get returns bytes. Named slot main maps to saves/main.json; the autosave maps to state/autosave.json. Keep these paths as JSON. Files share the same allowance, with 16 KiB of metadata overhead per file. sdk.saves.list() resolves null past 100 slots; page through sdk.storage.list({ prefix: 'saves/' }) instead.
Handle a refused write without clearing the last saved progress. sdk.saves.put, sdk.saves.delete and every sdk.storage call resolve { ok: true, ... } or { ok: false, code, reason } with code one of conflict, quota, throttled, invalid (a bad name or path, data that is not JSON-serializable, or a document over its size limit), or unavailable; guests and unavailable hosts return null. On a conflict, reread and merge or ask the player. On throttling, wait before retrying. A failed response may follow a successful write, so reread before retrying a conditional write.