Writing quests
Beginner Last updated Jul 9, 2026
On this page
A quest is one .quest.yaml file. The file is the source of truth: the Studio compiles it
into assets, and the Quest tab is a view of it, so hand-editing and constructor-editing are
always equivalent. This page walks through the format; the complete field-by-field reference
(also written to double as an AI prompt) is available as a
download.
Where files live
Put quests in <Project>/Content/Quests/; they appear in the Studio library automatically.
Name them <Name>.quest.yaml, one quest per file.
Minimal skeleton
quest: Region.MyQuest # unique id/slug (required)
name: "My Quest"
giver: Bran # speaker key of the NPC who gives it
source_culture: en
steps: # required, at least 1
- id: MyQuest_Offer
scene:
speakers: [Bran, Player]
lines:
- { by: Bran, id: greet, text: "Hello.", emotion: neutral }
- { by: Player, id: hi, text: "Hi." }
choices:
- { id: accept, text: "I'll help.", goto: MyQuest_Do }
on_end: { quest: accept }
- id: MyQuest_Do
end: completed
The golden rules
- Stable lowercase ids on every line, choice and objective. Ids key localization and the generated voice/face assets; inserting content must never renumber existing ids.
- Only use documented fields, emotions, shots, condition kinds and effect verbs. The validator catches anything out of contract, with jump-to-source on every finding.
- Every GameplayTag value must be registered in your project (native C++ or
DefaultGameplayTags.ini). An unregistered tag fails silently, which is the #1 cause of “my objective never counts”. See Objectives & progression.
Steps
A step is one node of the quest: a dialogue scene, a trackable objective, or a terminal marker.
| Field | Purpose |
|---|---|
id | Required, stable. Auto-namespaced to <ShortQuest>_<id>. |
scene | A dialogue scene (lines + choices) |
objective | A trackable goal: kill / collect / reach / talk / deliver |
next | Default transition to the next step (when the scene has no choices) |
end | Terminal marker: completed, declined or failed |
require | Entry gate, e.g. gate the turn-in step on the objective being complete |
Scenes: lines and choices
scene:
speakers: [Bran, Mira, Player] # every speaker key present in this scene
lines:
- { by: Bran, id: greet, text: "Come here.", emotion: worried, shot: cu }
- { by: Mira, id: warn, text: "Don't trust him.", to: Player }
- { by: Player, id: what, text: "What's wrong?" }
choices:
- { id: help, text: "I'll help.", goto: MyQuest_Accept }
- { id: refuse, text: "Not my problem.", goto: MyQuest_Refuse }
on_end: { quest: accept }
A scene supports any number of speakers: list every key in speakers and set each line’s
by. At runtime each key binds to an actor already placed in the level; if none is present,
one is spawned from the speaker map written by the Cast stage and staged beside the
giver. Spawned characters are removed automatically when the dialogue ends.
Useful line fields:
to:names who the line is addressed to; drives over-the-shoulder framing and head-turn in scenes with 3+ speakers.emotion:is one ofneutral,happy,sad,angry,afraid,surprised(plus aliases likeworried→afraid). Drives the additive facial mood and the VO tone.shot:takesauto(default; the compiler frames the scene),wide,ots,cu,reaction, or the id of a custom camera mark (see Staging & cameras).echo_of:marks a line that repeats a choice’s text; give the choice’s id so they share one localization key.
Choices can gate and grant GameplayTags: require: makes a choice available only if the player
owns the tag(s), grant: awards tags when picked.
Dynamic text
Text supports {PlayerName} and custom {Token} placeholders, substituted at display time in
a localization-safe way. See
Runtime integration for setting token values from
your game.
Authoring with an AI assistant
The fastest way is the ready-made
Questwright Quest Builder
GPT: it already knows the format, so just describe your premise (“a lighthouse keeper asks the
player to find his missing brother”) and it answers with a complete .quest.yaml.
Prefer another assistant? The full format reference is deliberately self-contained: paste
the reference file into ChatGPT or Claude
together with your premise and ask for a .quest.yaml.
Either way, drop the result into Content/Quests/; the validator catches anything out of
contract before it ever reaches a build.