Stagebook Syntax Reference
July 9, 2026 · View on GitHub
A concise, precise reference for the Stagebook experiment description language. For detailed explanations, see the individual guides: Treatment Files, Elements, Prompts, Conditions, Discussions, Templates.
1. Top-Level Structure
templates: # optional: array of template definitions
consent: # optional: array of consent arms — the host shows one, selected by name
introSequences: # required: array of intro sequence objects
treatments: # required: array of treatment objects
2. Primitives
- Names: 1-64 chars;
[a-zA-Z0-9 _-]plus${field}placeholders. - Durations: positive integer (seconds).
- Positions: zero-based nonnegative integers.
- Visibility:
showToPositions/hideFromPositions— nonempty int arrays. - Time gates:
displayTime(nonnegative int),hideTime(positive int) — seconds into stage.
3. Templates
templates:
- name: <name>
contentType: <element|stage|treatment|...> # required
content: <any structure>
# Usage:
- template: <name>
fields: { key: value } # ${key} substitution
broadcast: { d0: [...] } # cartesian expansion
Content types: introSequence, introSequences, elements, element, stage, stages, treatment, treatments, reference, condition, conditions, player, groupComposition, introExitStep, introSteps, exitSteps, consentArm, consent, discussion, broadcastAxisValues.
4. References
A reference identifies a value somewhere in the study state. Two forms (#240): the dotted-string sugar and the structured object form. Both are accepted at every reference site (conditions, display.reference, trackedLink/qualtrics urlParams[].reference); both parse to the same internal shape.
Every reference begins with a position selector (#298). The first segment is required and is one of:
self— the current participant's valueshared— group-shared stateall— addresses every participant's value as a list (for aggregating across the group)- A non-negative integer (
0,1,2, …) — a specific slot index
Un-prefixed references like prompt.topicVote are rejected at parse time. The error message includes a migration hint suggesting self.prompt.topicVote for the common case.
String shorthand (the common form):
| Pattern | Example |
|---|---|
<position>.prompt.<name> | self.prompt.topicVote |
<position>.survey.<name>.<path...> | self.survey.TIPI.responses.q1 |
<position>.submitButton.<name>.<path> | self.submitButton.confirm.time |
<position>.qualtrics.<name>.<path> | self.qualtrics.exit.sessionId |
<position>.trackedLink.<name>.<path> | self.trackedLink.signup.events |
<position>.timeline.<name>(.<path>) | self.timeline.story.0.start |
<position>.discussion.<name>(.<path>) | shared.discussion.lobby.messageCount |
<position>.entryUrl.params.<key> | self.entryUrl.params.PROLIFIC_PID |
<position>.attributes.<field> | self.attributes.stableParticipantId |
Structured form (#240 — preferred in new code):
reference:
source:
prompt | survey | submitButton | qualtrics | timeline | trackedLink | discussion |
entryUrl | attributes
name: <element name> # required for named sources, forbidden for external sources
path: [<segments>...] # optional for named sources, required for external sources
For named sources, prompt references default to path: [value] when omitted (the participant's saved answer). Other named sources read the whole stored record by default. The structured form lets you override the implicit default — e.g. path: [debugMessages] to address other fields on a prompt's saved record.
For external sources, path is required. Additionally, entryUrl references must currently start the path with params (e.g. path: [params, condition] — equivalent to the dotted entryUrl.params.condition). The entryUrl.* namespace is reserved so future additions like entryUrl.path / entryUrl.host / entryUrl.href can land non-breakingly.
attributes.* is the host-supplied bag of participant metadata (#473), replacing the former connectionInfo / browserInfo / participantInfo sources — references to those are now rejected. Common fields: stableParticipantId (the anonymized id used to link exported data — always available), sampleId (the per-assignment data-row id — only from the game phase onward), name, country, timezone, language, screenWidth. The recruitment-platform id is intentionally not exposed here.
5. Conditions
conditions:
- reference: <reference> # string sugar OR { source, name?, path? }
comparator: <comparator>
value: <expected value> # omit for exists/doesNotExist
position: <position> # optional
Comparators: exists, doesNotExist, equals, doesNotEqual, isAbove, isBelow, isAtLeast, isAtMost, hasLengthAtLeast, hasLengthAtMost, includes, doesNotInclude, matches, doesNotMatch, isOneOf, isNotOneOf.
Position values: player (default), shared, or integer slot index. (After #238, position is a pure read selector. Cross-player aggregation lives in the all: / any: / none: boolean-tree operators — see Conditions.)
6. Elements
All elements accept: name?, notes?, displayTime?, hideTime?, showToPositions?, hideFromPositions?, conditions?, tags?. (file? is per-type — only prompt, audio, image, mediaPlayer accept it; see #249.)
| Type | Key Fields |
|---|---|
prompt | file (required), shared? |
display | reference (required), position? (default: player) |
submitButton | buttonText? (default: "Next") |
timer | startTime?, endTime?, warnTimeRemaining? |
separator | style? (thin, regular, thick) |
audio | file (required) |
image | file (required), width? |
mediaPlayer | file (required), name, controls?, syncToStageTime?, submitOnComplete?, startAt?, stopAt?, stepDuration?, playVideo?, playAudio?, captionsFile?, allowScrubOutsideBounds? |
timeline | source (required, name of a sibling mediaPlayer), name (required), selectionType (required, range or point), selectionScope? (default all), multiSelect? (default false), showWaveform? (default true), trackLabels? |
survey | surveyName (required) — deprecated; pending removal once a module-reuse pattern lands. Prefer prompt-based patterns. |
qualtrics | url (required), urlParams? |
trackedLink | name (required), url (required), displayText (required), helperText?, urlParams? |
Media hosting requirements
The <video> element rendered by mediaPlayer always sets crossOrigin="anonymous". This is required for the Web Audio API to read the audio stream when a timeline element with showWaveform: true is attached — without it, the analyser is silently CORS-tainted and the waveform tracks render as flat lines.
This means all media URLs must be served with proper CORS headers (Access-Control-Allow-Origin: * or matching the experiment origin), regardless of whether you use the timeline. Same-origin media (e.g., served from the same host as the experiment) is unaffected.
If you see flat waveforms in the timeline despite audio playing, check the browser console — Stagebook logs a warning after 5 seconds of playback if the AnalyserNode is producing only silence:
[MediaPlayer] Waveform capture is producing all-zero data after 5s of playback...
7. Stages
gameStages:
- name: <name>
duration: <seconds>
discussion: <discussion object> # optional
elements: [...] # required, nonempty
Time bounds on elements (displayTime, hideTime, startTime, endTime) must not exceed stage duration.
8. Discussions
discussion:
chatType: text | audio | video
showNickname: true
showTitle: false
# text-only: reactionEmojisAvailable?, reactToSelf?, numReactionsPerMessage?
# video-only: showSelfView?, showReportMissing?, showAudioMute?, showVideoMute?
# video-only: rooms? or layout?
showToPositions: [0, 1] # optional
hideFromPositions: [2] # optional
conditions: [...] # optional
9. Intro/Exit/Consent Steps
introSequences:
- name: <name>
introSteps:
- name: <name>
elements: [...] # no duration, no position-based visibility
treatments:
- name: <name>
exitSequence:
- name: <name>
elements: [...] # no shared prompts
Constraints: no shared prompts, no position/showToPositions/hideFromPositions on elements in intro steps. Exit steps disallow shared prompts.
Consent (#481)
consent: # top-level; sibling of introSequences/treatments
- name: <name> # unique within consent: only — the host selects an arm by name
locale: <locale> # optional — arms declare their OWN locale (pre-assignment)
steps:
- name: <name>
elements: [...] # intro-step constraints apply
Consent steps take the intro-step constraints (advancement element required, no shared prompts, no position fields). Consent keys are audit-only: referencing one from intro/game/exit/groupComposition is an error; within-arm references (the gated-submit pattern) are legal; consent steps can't reference later-phase data. Collision-checked against every intro sequence and treatment; arm × arm key reuse is legal.
Debrief (#481)
Debrief content is authored as the trailing steps of exitSequence — there is no separate debrief: field. The host renders the exit sequence before the completion code, so the trailing exit steps are the debrief and the code stays gated behind them. They are ordinary exit steps (exit-step constraints apply) and may reference any earlier phase except consent (audit-only).
10. Treatments
treatments:
- name: <name>
playerCount: <integer>
compatibleIntroSequences: [<names>] # required; [] = runs without an intro sequence
groupComposition: # optional
- position: 0
title: "Role A"
conditions: [...]
gameStages: [...] # required, nonempty
exitSequence: [...] # optional; trailing steps are the debrief (#481)
Position indices in showToPositions, hideFromPositions, groupComposition, and discussion rooms must be < playerCount.
compatibleIntroSequences (#499) names the intro sequences the treatment may follow; names resolve against the top-level introSequences: collection. Dangling names error; duplicates warn; every game/exit/groupComposition reference to intro-provided data must resolve in every listed sequence. ${field} placeholders allowed, whole-field or per-item (like groupComposition).
11. Prompt Files
Three sections separated by ---:
---
type: multipleChoice | dropdown | openResponse | noResponse | listSorter | slider
name: My Prompt # optional — human-readable identifier
---
## Markdown body text
- Response option 1
- Response option 2
name is optional. Can be any string — use it as a human-readable identifier. Prompt files must use the .prompt.md extension.
Slider requires min, max, interval in metadata. Slider initializes without a visible thumb (anti-anchoring).