Saving & multiplayer
Intermediate Last updated Jul 9, 2026
Quest progress persists out of the box: in single player you don’t write a line of code, and the same model scales up to dedicated servers with one interface.
Autosave / autoload
Each player’s quest component loads stored progress when it starts and saves after changes (accept, objective credit, step advance, flags, completion). Saves are coalesced, so a burst of changes in one frame costs one write. A final save runs on quit, map travel and logout, and autoload restores progress across level changes automatically.
The policy is tunable in Project Settings → Plugins → Questwright Quests (details in the Settings reference):

Auto-persistence engages for components owned by a PlayerState (the shipped deployment);
components placed on bare actors keep fully manual SaveProgress() / LoadProgress().
What is stored
Only the per-player progress records: state, current step, objective counters, flags, timestamps. Quest content lives in the compiled assets; an active dialogue scene is presentation and is never saved. Saved progress survives content updates: a record whose stored step no longer exists falls back to the quest’s first step (with a log warning), and records of quests unknown to the current build are kept untouched.
The three tiers
| Deployment | What you do |
|---|---|
| Single player | Nothing. One USaveGame slot (Questwright_Quests). |
| Listen-server co-op | Nothing. Each player gets an isolated slot keyed by their online id (Questwright_Quests_<id>). |
| Dedicated server / own backend | Implement one C++ interface (below). |
The backend seam is IQuestwrightSaveProvider:
class UMyDbSaveProvider : public UObject, public IQuestwrightSaveProvider
{
// PlayerKey is the player's UniqueNetId string; use it as your database key.
virtual bool SaveQuestRecords(const FString& PlayerKey,
const TArray<FQuestwrightQuestRecord>& Records) override;
virtual bool LoadQuestRecords(const FString& PlayerKey,
TArray<FQuestwrightQuestRecord>& Out) override;
// Optional non-blocking variant for REST/database fetches:
// virtual void LoadQuestRecordsAsync(...) override;
};
Register it as SaveProviderClass in the settings page, or inject an instance with
SetSaveProvider() on login. Everything runs server-side; clients receive loaded progress
through normal replication.
Manual control
For a save-station game: set AutoSavePolicy to Manual and call SaveProgress() /
LoadProgress() yourself. OnProgressLoaded fires after stored progress is applied.
Multiplayer model
Questwright works in standalone, listen server and dedicated server, with a strict server-authoritative split:
- Progress is per player, on a component on the PlayerState, replicated owner-only, so players never see each other’s journal internals.
- All mutations are server-authoritative. Client calls (accepting a quest from the UI, reporting an event) route through Server RPCs automatically; the API looks the same on either side.
- Kill credit goes to the instigating player when your death code passes the killer; otherwise all players are credited (shared-world fallback).
- Presentation never runs on the server. Cameras, faces, dialogue UI and look-at run only on the owning client; a dedicated server executes none of it.
- Components are auto-provisioned on login, so late joiners get theirs automatically.