Contents
What Belongs In Save Data vs SlotInfo?
Decide what belongs in full game save data, what belongs in lightweight slotInfo, and how to keep save menus fast.
Every save system eventually separates two kinds of information:
- the full state needed to resume the game
- the small preview fields needed to show a save menu, character select screen, or support/debug summary
Persistly calls the full state save data. The lightweight preview object is slotInfo.
This article expands the save-model decision from One Save vs Multiple Save Slots. That guide helps you choose one autosave or multiple slots. This one focuses on what data belongs inside each layer once you have chosen the model.
The Short Rule
Put gameplay truth in save data.
Put preview labels and routing hints in slotInfo.
Save data answers:
Can the game resume correctly from this?
slotInfo answers:
Can the player or developer recognize this save without loading the whole payload?
What Belongs In Save Data
Save data should contain the facts your game needs to restore progress.
Good examples:
- player level, XP, currency, and unlocked upgrades
- checkpoint, area, wave, quest, and tutorial state
- inventory, equipment, crafting state, and cooldowns
- building positions, unlocked rooms, and persistent world state
- schema version and migration markers
For a simple idle game, save data may look like this:
await PersistlyGameSaves.shared.saveData({
schemaVersion: 1,
level: 12,
coins: 18420,
checkpoint: "forest-gate",
upgrades: {
pickaxe: 4,
backpack: 2,
},
lastTickAt: new Date().toISOString(),
});This is the data the game needs after a refresh, crash, offline session, or device switch.
What Belongs In SlotInfo
slotInfo should stay small. It is for labels and preview fields, not the full save.
Good examples:
- character name
- class or build name
- campaign name
- level for display
- last checkpoint label
- thumbnail id
- last played timestamp
await PersistlyGameSaves.shared.saveSlot("mage-main", gameState, {
slotInfo: {
characterName: "Astra",
className: "Mage",
level: 12,
checkpointLabel: "Forest Gate",
lastPlayedAt: new Date().toISOString(),
},
});When the game builds a save menu, it can list slots and show this preview data without loading every full save payload.
Why Not Put Everything In SlotInfo?
Because preview data tends to be read more often and shown in more places.
Keep it boring:
- small
- stable
- non-sensitive
- easy to render
- safe to show in a menu
Do not use slotInfo for hidden economy state, anti-cheat decisions, private player notes, or large nested objects.
Why Not Put Everything In Save Data?
You can start that way for one-save games.
For a tiny game with only one autosave, saveData and loadData are enough. There may be no save menu and no need for preview metadata.
The moment you add manual slots, characters, campaigns, or "continue from this run" UI, slotInfo becomes useful because it avoids loading every full state just to draw a list.
Stable Slot IDs Are Not Display Names
A slot id should be stable. A display name can change.
Good slot ids:
autosavecampaign-1character-mainchallenge-hardcore
Bad slot ids:
Astra Level 12Forest Gate 2026-06-15New Save Copy Final
Use the stable id for API calls:
await PersistlyGameSaves.shared.saveSlot("character-main", state, {
slotInfo: {
characterName: "Astra",
level: 12,
},
});Use slotInfo for the display label:
Astra - Level 12This keeps renames, localization, and UI polish from changing the save identity.
A Practical Split
Use this split as a starting point:
| Data | Save Data | SlotInfo |
|---|---|---|
| Current coins | Yes | Usually no |
| Player level | Yes | Optional preview |
| Character name | Optional | Yes |
| Inventory list | Yes | No |
| Equipped weapon name | Yes | Optional preview |
| Last played time | Yes | Yes |
| Save schema version | Yes | No |
| Menu thumbnail id | Optional | Yes |
The same fact can appear in both places when it serves two different jobs. For example, level belongs in save data because gameplay needs it, and it may also appear in slotInfo because the menu wants to show it quickly.
Keep SlotInfo Backward-Compatible
Players may have old saves. Your save menu should tolerate missing preview fields.
const slots = await PersistlyGameSaves.shared.listSlots();
for (const slot of slots.slots) {
const name = slot.slotInfo?.characterName ?? "Unnamed save";
const level = slot.slotInfo?.level ?? "?";
renderSaveRow(`${name} - Level ${level}`);
}Avoid code that assumes every old slot has every new field.
What About Account-Level Data?
Some games also have data that belongs above one slot:
- shared currency
- account-wide unlocks
- settings shared by characters
- owned DLC flags
- global tutorial state
Keep that separate from character or campaign slots. A character slot should not need to duplicate every account-wide fact.
For many games, the first version can ignore account-level data. Start with saveData or named slots, then add account-level structure when the game actually needs shared progress across slots.
Checklist
Use save data for:
- gameplay state needed to resume
- large or nested state
- inventory, quests, upgrades, world state
- migration/version fields
Use slotInfo for:
- save-menu labels
- character/campaign preview fields
- small routing hints
- values that can be missing without breaking load
Avoid:
- large payloads in
slotInfo - secrets or private player data in preview fields
- display names as slot ids
- syncing every small UI change as a cloud write