Save/Load Strategy (Client-Driven)
This guide explains how to use the client-driven save/load flow introduced for RPGJS. The client requests the slot list and triggers save/load actions, while the server executesplayer.save(slot) and player.load(slot) using a pluggable storage strategy.
Concepts
- Save storage strategy (server): decides where snapshots are stored (DB, file, memory).
- Auto-save strategy (server): decides when saving/loading is allowed and the auto slot.
- Save client service (client): requests slot list and triggers save/load actions.
- Slots metadata: what the UI displays (level, exp, map, date, custom fields).
Standalone and MMORPG storage
The same save API works in both modes, but the correct storage is different:
MMORPG room storage is not save-slot storage. Room storage keeps synchronized
room state and session mappings. A
SaveStorageStrategy keeps long-term
character snapshots and must be available to every server instance that can
receive the player.
In an authenticated MMORPG, use the stable player.id returned by
auth() as the account key. Never accept the account id from a
save request sent by the browser.
Server: provide a storage strategy
The server exposessave.list, save.save, and save.load actions. A storage
strategy is injected via DI; if none is provided, a memory-only strategy is used.
The server can also control whether saving/loading is allowed.
Strategy contract
Server: provide an auto-save strategy
getDefaultSlot() function is the auto slot. When you call
player.save("auto"), this strategy decides which slot is used (e.g. always slot 0,
or last used slot).
Register auto-save strategy
Auto-save is disabled by default. Duringplayer.syncChanges(), RPGJS calls
shouldAutoSave() and saves to the default slot only when it returns true.
Do not return true on every synchronization: network storage could otherwise
receive many overlapping writes.
This example limits automatic saves to one attempt per player per minute:
await player.save("auto") explicitly from the corresponding server hook. The
auto-save policy selects the slot and can still allow or deny that operation;
the storage strategy only decides where the snapshot is written.
Save points (server authority)
If you want to restrict saving to specific points, you can deny saves by default and only allow them when the player interacts with a save point.Register the strategy
Example: store MMORPG saves through an HTTP API
The following example shows the complete adapter shape for a trusted remote API, such as an API hosted by your own backend or by Studio in the future. The/saves/list, /saves/get, /saves/upsert, and /saves/delete routes are
illustrative: they are not existing RPGJS Studio endpoints. Replace them with
the real contract of your service.
VITE_ variable for
the API key because those variables can be included in browser code.
projectId + playerId + index.
list returns metadata without snapshots, while get returns one object shaped
like { snapshot, ...meta }. The example expects save and delete to return
204 No Content; save should perform an atomic insert-or-update. For
production, also impose a maximum snapshot size and prevent an older concurrent
request from replacing a newer save.
Built-in localStorage strategy (standalone)
For standalone mode (server running in the browser), use the built-in localStorage strategy. It stores full slots (meta + snapshot) under a single key and can carry an optional policy.Client: request slots and trigger save/load
The client usesSaveClientService to talk to the server. It is already included
in provideRpg() and provideMmorpg().
Typical flow
- Call
saveClient.listSlots()to get the current slot list. - Show the Save/Load UI with those slots.
- On interaction:
saveClient.saveSlot(index)for save.saveClient.loadSlot(index)for load.
player from the WebSocket action and passes it to the storage strategy.
When an MMORPG save is loaded
RPGJS does not choose a slot automatically. Your game must choose one of these flows after authentication:- list the slots and let the player select a character;
- load a known slot, such as slot
0; - remember the last selected slot in your account service;
- initialize a new character when no slot exists.
0 from the
server-side onConnected hook:
onJoinMap: a map change already transfers the live player state between rooms.
Refresh, room transfer, and returning later
- Map change: RPGJS transfers the current session and player state. No save API read is required.
- Browser refresh: RPGJS can reconnect the private session and room state.
This is not a call to
player.load(). - New session or later visit:
auth()recovers the stable account id, then your login/title-screen flow must explicitly select and load a slot.
Player API (server-side)
player.snapshot()-> returns the raw snapshot object (low-level).player.save()-> returns a JSON snapshot string for v4 compatibility.player.save(slot)-> stores a snapshot using the storage strategy.- Use
"auto"to ask the policygetDefaultSlot()which slot to use.
- Use
player.load(snapshot)-> loads a JSON string or object snapshot for v4 compatibility.player.load(slot)-> loads a slot using the storage strategy.
player.snapshot() if you need to serialize or inspect state without saving.
RpgPlayerHooks.onLoad(player, snapshot) runs after any snapshot is applied.
RpgPlayerHooks.onSave(player, snapshot) runs before a slot snapshot is handed to
the storage strategy. Both hooks receive the exported RpgPlayerSnapshot type.
Examples:
GUI options (auto slot + save disabled)
The save/load GUI can display a dedicated “Auto Save” slot at the top. It is read-only in save mode, and selectable in load mode.Menu GUI (server-side)
MenuGui.open() accepts these options:
saveShowAutoSlot(boolean) -> show the auto slot in the GUIsaveAutoSlotIndex(number) -> which slot index to use for auto savesaveAutoSlotLabel(string) -> label displayed for auto slot
canSave is computed from the AutoSaveStrategy and sent to the client; if false,
the “Save” entry is disabled in the menu.
Save/Load component (client-side)
Props supported by the component:showAutoSlot(boolean)autoSlotIndex(number)autoSlotLabel(string)
showAutoSlot is enabled:
- save mode: auto slot is displayed but read-only
- load mode: auto slot behaves like a normal slot and loads as usual
Example (menu or title screen)
Events sent by the server
These are emitted to the client and handled bySaveClientService:
save.list.result->{ requestId, slots }save.save.result->{ requestId, index, slots }save.load.result->{ requestId, index, ok, slot }save.error->{ requestId, message }
Notes
- Works in standalone and client/server modes.
- Slot metadata is a free object, so you can display any custom fields.
- The Save/Load GUI displays interactions.
SaveClientServicerequests the slots, while the server storage strategy persists them. - Auto-save is disabled by default. Providing storage does not enable it.