Introduction
PsychLab.IO Documentation
PsychLab.IO is a browser-based platform for building, running, and collecting data from psychology experiments with no programming required. Drag nodes onto a canvas, configure their properties, and share a link. Responses are saved to your account automatically.
Quick start
Your first experiment
- 1
Create an experiment
Go to Dashboard → Experiments → New experiment. Choose a template or start from scratch.
- 2
Build your timeline
Drag nodes from the left palette onto the canvas. Arrange them into a sequence: instructions, fixation, stimulus, feedback, and so on.
- 3
Configure properties
Click any node to open its properties in the right panel. Changes save automatically.
- 4
Test it
Open the experiment page from the dashboard and click Run. Draft experiments do not record data.
- 5
Activate to collect data
Set the status to Active. Share the participant link. Responses flow in automatically.
The builder
Builder overview
Node palette
Left panel. Lists all available node types. Drag any node onto the canvas to add it.
Canvas
Center panel. Your experiment timeline as a tree. Drag nodes to reorder them.
Properties panel
Right panel. Configuration for the selected node. Changes save automatically; the header shows "Saved", "Saving…", or "Unsaved".
Timeline strip
The strip at the bottom shows a proportional view of experiment timing. Branching conditions appear on parallel lanes. Click a segment to select that node.
Resizing panels
Drag the dividers between panels to resize them.
Node reference
All nodes
Every experiment is built from nodes in one of four categories: structure, flow, display, and data.
Structure
Sequence
containerRuns its children one after another in order. No configurable properties.
Repeat
containerTwo modes. Fixed count: runs children a set number of times. Trial list: iterates over conditions, exposing the current condition as _trial.
| Property | Description |
|---|---|
| repeatCount | Number of repetitions, fixed count mode (default 2) |
| trialSource | balanced_shuffle or manual; enables trial list mode |
| conditions | Condition labels to shuffle across (balanced_shuffle mode) |
| trialCount | Total number of trials to run (balanced_shuffle mode) |
| trialsExpr | Any iterable TypeScript expression (manual mode) |
Flow
If
containerRuns the "then" branch when the condition is true, the "else" branch otherwise. Supports else-if chains.
| Property | Description |
|---|---|
| conditionType | correct, incorrect, no_response, key_is, trial_is, group_is, or custom |
| conditionKey | Key code to match (key_is) |
| conditionValue | Value to match (trial_is / group_is) |
| predicate | Custom TypeScript expression (custom) |
| hasElse | Whether to include an else branch |
Loop
containerRepeats its children as long as the condition remains true. Useful for re-showing a stimulus until the participant responds correctly.
| Property | Description |
|---|---|
| conditionType | Same options as If node |
| conditionKey / conditionValue / predicate | Same as If node |
| loopMaxIter | Safety cap; max iterations before the loop exits (default 50) |
Counterbalance
Assigns each participant to a named group at the start of the experiment. The group is stored in _group for the rest of the session. Use If → group is… to branch per group.
| Property | Description |
|---|---|
| cbMode | random: random assignment; sequential: fills the least-populated group; url_param: reads from a URL query parameter |
| cbGroups | List of group names (e.g. control, treatment) |
| cbParam | URL parameter name for url_param mode (default group) |
Sequential mode uses a server-side atomic counter so group sizes stay balanced under concurrent participants.
Display
Instructions
Shows a text screen and waits for Space.
| Property | Description |
|---|---|
| text | Instruction text. Newlines are preserved. |
| size | Font size in px (optional) |
| color | Text colour (default white) |
| align | left, center, or right (default center) |
Fixation
Displays a fixation cross for a set duration. Enable random range to jitter duration between min and max each trial.
| Property | Description |
|---|---|
| duration | Fixed display time in ms (default 500) |
| durationMin / durationMax | Range for random-range mode |
| fixSize | Cross arm length in px (optional) |
| thickness | Cross stroke width in px (optional) |
| color | Cross colour (default white) |
Stimulus
Displays a stimulus and waits for a key response. Three types: text, image, and rectangle. Records reaction time and response accuracy.
| Property | Description |
|---|---|
| stim.type | text, image, or rect |
| stim.content | Static text. Toggle {} for a TypeScript expression. |
| stim.fontSize | Font size in px (default 48, text type) |
| stim.color | Text colour. Toggle {} for a dynamic expression. |
| stim.path | Path or URL to image file (image type) |
| keys | Accepted key codes, e.g. KeyF, KeyJ |
| correctKey | Key that counts as correct. Toggle {} for a dynamic expression. |
| timeout | Max wait time in ms before moving on (default 1500) |
Blank
A silent screen for inter-stimulus or inter-trial intervals.
| Property | Description |
|---|---|
| duration | Pause duration in ms (default 800) |
| color | Background colour (default black) |
Feedback
Displays text feedback after a response. Three modes: Correct / Incorrect, With timeout (adds a third branch for no response), and Always show (fixed message).
| Property | Description |
|---|---|
| feedbackType | correct_incorrect, with_timeout, or always |
| correctText / incorrectText / timeoutText | Text per outcome |
| correctColor / incorrectColor / timeoutColor | Colour per outcome |
| duration | Display time in ms (default 600) |
End Screen
A final thank-you or debrief screen. Automatically advances after the set duration, or waits for Space if no duration is set.
| Property | Description |
|---|---|
| text | Message to display. Newlines are preserved. |
| duration | How long to show the screen in ms. Leave blank to wait for Space. |
Data
Record
Appends a row to the in-memory trial log. Add fields by name and give each a TypeScript expression. Data is sent to the server when the experiment ends.
| Property | Description |
|---|---|
| fields | Array of { name, expr } pairs evaluated at runtime |
Expressions
Dynamic values
Toggle the ƒ button next to a field to switch it to expression mode. Expressions are plain JavaScript evaluated at runtime.
| Variable | Type | Description |
|---|---|---|
| _trial | string | Current condition label from a Repeat node in trial-list mode |
| _lastResp | string | null | Key code of the most recent response (e.g. "KeyF"), or null on timeout |
| _lastCorrect | boolean | Whether the last response was correct |
| _lastRt | number | null | Reaction time of the last response in ms, or null on timeout |
| _group | string | Group assigned by a Counterbalance node |
Examples
Show the current condition as the stimulus text
_trialSet the correct key based on the trial condition
_trial === 'left' ? 'KeyF' : 'KeyJ'Loop while the participant keeps responding incorrectly
!_lastCorrectRecord reaction time in a Record node field
_lastRtRunning experiments
Running & data collection
Consent
If you add consent text to an experiment, participants will see a consent screen before the experiment starts. They can agree to proceed or decline. If they decline, no data is recorded and the session ends immediately.
Participant ID
Each participant is automatically assigned a UUID on their first visit, stored in localStorage per experiment. This ID is attached to all data saved by that session. If a participant clears their browser storage and revisits the link, they will receive a new ID.
Data flow
The experiment runs entirely in the browser. When it ends, all recorded rows are sent to /api/results as a JSON payload with a header object (experiment name and participant ID) and a trials array.
Statuses
| Status | Meaning |
|---|---|
| draft | In development. You can run it to test, but responses are not recorded. |
| active | Open for participants. Share the experiment link to collect data. |
| closed | No longer accepting responses. Existing data is preserved. |
Plans & limits
Free vs Pro
Free
- - Up to 3 experiments
- - 60 responses per month
- - All node types & templates
Pro
$5 / month- ✓ Unlimited experiments
- ✓ Unlimited responses
- ✓ Everything in Free
Monthly response counts reset at the start of each calendar month. If a free account reaches 60 responses mid-month, subsequent saves will return a monthly_limit_reached error and no further data will be recorded until the next month.