R SDK (psycloudr)
psycloudr lets you build a PsyCloud study from R. It is designed for the way
many psychology projects already work: stimulus tables in data frames, design
checks in scripts, and analysis planned before data collection begins.
The useful mental model is: make the trial table, describe the screen for each row, and name the columns you want saved.
psycloudr is under active development and not yet on CRAN. Install it from
the monorepo:
# install.packages("remotes")
remotes::install_github("bbuchsbaum/psycloud", subdir = "packages/psycloudr")Core authoring and write_bundle() are pure R. Some preview/workbench helpers
need extra local tools such as the V8 package or the psycloud CLI.
Start With The Data You Want
Before building the study, decide what columns should appear in your final data:
| Column | What it means |
|---|---|
condition | congruent or incongruent |
color | the ink color on this trial |
word | the printed word |
correct_key | the response key that counts as correct |
| response fields | key press, RT, correctness, timeout |
The example below builds those columns, shows one trial per row, and writes a bundle that Studio and the runtime can open.
library(psycloudr)
instructions_md <- paste(
"# Color Stroop",
"",
"Name the INK COLOR, not the word.",
"",
"Keys:",
"- r = red",
"- g = green",
"- b = blue",
"",
"Press any key to begin.",
sep = "\n"
)
bundle <- experiment(id = "demo.stroop", name = "Color Stroop") |>
instructions(instructions_md) |>
phase("trials", label = "Stroop trials") |>
factors(
condition = c("congruent", "incongruent"),
color = c("red", "green", "blue")
) |>
cross() |>
derive(
"word",
expr_if(
row$condition == "congruent",
uppercase(row$color),
case_when(
row$color == "red" ~ "GREEN",
row$color == "green" ~ "BLUE",
.default = "RED"
)
)
) |>
derive(
"correct_key",
case_when(
row$color == "red" ~ "r",
row$color == "green" ~ "g",
.default = "b"
)
) |>
repeat_rows(2) |>
shuffle(seed = seed_per_participant("stroop-demo")) |>
screen("trial") |>
present(fixation_cross(x = "center", y = "center", size = 36), 500) |>
ask(
text(trial$word, fill = trial$color, font_size = 72, x = "center", y = "center"),
keypress(keys = c("r", "g", "b"), correct = trial$correct_key, timeout_ms = 2000)
) |>
record(condition, color, word, correct_key) |>
end_screen("# Done\n\nThanks for participating.") |>
bundle(with_auto_ids = TRUE)
write_bundle(bundle, dir = "stroop_bundle", overwrite = TRUE)Run it with:
source("stroop.R")The stroop_bundle/ directory is the portable study. You can import it into
Studio, validate it with the CLI, or keep it with the R script that generated it.
If write_bundle() warns that the instructions or end screen can wait forever,
that means those pages wait for a key press. That is usually intentional for
instructions. Timed trials should still have explicit limits such as
timeout_ms = 2000.
Read The Pipeline
The R SDK uses base R's pipe, |>, so the study reads from top to bottom:
- Start the study
experiment(id, name)gives the study a stable identity.instructions(...)adds a one-screen introduction that waits for a key. - Build the trial table
phase("trials")opens the block.factors(...)names independent variables,cross()makes all combinations,derive(...)computes new columns,repeat_rows(...)adds repetitions, andshuffle(...)randomizes. - Draw one row
screen("trial")describes the display for one row.trial$wordandtrial$colormean "use the value from the current trial." - Collect the response
keypress(..., correct = trial$correct_key, timeout_ms = 2000)stores the response and lets PsyCloud score correctness. - Save analysis columns
record(condition, color, word, correct_key)copies these trial columns into the output data beside the response fields.
row$ And trial$
Two pronouns do most of the work:
| Use this | Where | Meaning |
|---|---|---|
row$color | inside derive(), filter_rows(), constraints | a row while the trial table is being built |
trial$color | inside screen() stimuli and responses | the participant's current trial at runtime |
In the Stroop example, row$color helps compute word and correct_key.
Later, trial$word, trial$color, and trial$correct_key are filled in on
each participant trial.
psycloudr has its own expression case_when(). If another package masks it,
call psycloudr::case_when(...) inside derive(...).
If You Have A Fixed Item Table
Not every study is a full factorial crossing. For a lexical decision, recognition
memory, or sentence task, you may already know the exact items. Define those rows
directly in an R data frame and pass them to trials(stimuli).
library(psycloudr)
stimuli <- data.frame(
item_id = c("cat_01", "blit_01"),
letter_string = c("CAT", "BLIT"),
condition = c("word", "nonword"),
correct_key = c("f", "j")
)
bundle <- experiment(id = "demo.lexical", name = "Lexical Decision") |>
instructions("Press F for WORD and J for NONWORD.") |>
phase("main", label = "Trials") |>
trials(stimuli) |>
screen("trial") |>
ask(
text(trial$letter_string, font_size = 64, x = "center", y = "center"),
keypress(keys = c("f", "j"), correct = trial$correct_key, timeout_ms = 2500)
) |>
record(item_id, condition, correct_key) |>
end_screen("# Done\n\nThank you.") |>
bundle(with_auto_ids = TRUE)
write_bundle(bundle, dir = "lexical_bundle", overwrite = TRUE)For small and medium item sets, this is usually clearer than maintaining a separate spreadsheet. If you already have a CSV from another source, import it only after checking the rows and column names explicitly.
Check Before You Run Participants
These are the checks to run before recruitment:
summary(bundle) # phases, screens, bindings
preview_trials(bundle) # materialized trial table
validate_bundle(bundle) # schema validation
lint_bundle(bundle)$counts # warnings/errors by type
write_bundle(bundle, dir = "stroop_bundle", overwrite = TRUE)preview_trials(bundle) is especially important. It lets you confirm that the
condition balance, repetitions, randomization seed, derived correct keys, and
analysis columns match your design before anyone participates.
You can also preview interactively in RStudio:
psycloud(bundle, width = 800, height = 600)Common Recipes
| Study need | Use |
|---|---|
| factorial design | `factors(...) |
| fixed item table | trials(stimuli) |
| fixed delay | present(stimulus, duration_ms) |
| response-gated screen | ask(stimulus, response) |
| keyboard response | keypress(keys = c("f", "j"), correct = ..., timeout_ms = ...) |
| "press any key" page | instructions(...), end_screen(...), or anykey() |
| survey page | survey_page(...), form_response() |
| output condition columns | record(condition, item_id, ...) |
For a questionnaire, keep the items in a data frame and render them on one page:
items <- data.frame(
name = c("calm", "alert", "nervous"),
label = c("Calm", "Alert", "Nervous")
)
bundle <- experiment(id = "demo.mood", name = "Mood Survey") |>
instructions("Please rate how you feel right now.") |>
phase("survey", label = "Mood ratings") |>
factors() |>
screen("survey") |>
ask(
survey_page(
items,
response_type = "likert",
labels = c("Not at all", "A little", "Moderately", "Very much"),
required = TRUE,
submit_label = "Continue"
),
form_response()
) |>
bind(role = "trial") |>
end_screen("# Thanks") |>
bundle(with_auto_ids = TRUE)What To Learn First
Start with experiment(), phase(), factors() or trials(), screen(), and
record(). That is enough to build many simple reaction-time, categorization,
memory, and survey studies. Add derived columns, feedback, counterbalancing, and
adaptive logic after the basic design table feels natural.
Low-level pc_* constructors are available for advanced package work and
migration edge cases, but most study authors should stay with the fluent verbs
shown here.
Worked Examples
Runnable examples live under packages/psycloudr/inst/examples/high-level/,
including Stroop, simple reaction time, recognition memory, PANAS, Luck and
Vogel change detection, and design-first memory. The vignettes also include
side-by-side jsPsych-to-psycloudr versions of common paradigms.