
Collecting Responses Online with hitop-form
Source:vignettes/articles/online-collection.Rmd
online-collection.Rmdhitop-form is a web page that shows one of this package’s instruments in the browser, or two or three of them in turn: the HiTOP-SR or one of its modules, the HiTOP-BR, or the PID-5 in its full, short or brief form. The item text, response options and instructions are this package’s own, read from its JSON export. The page needs no survey platform and no account, and it scores nothing. Scoring is this package’s job.
A study link tells the page what to show and where each participant’s
answers go. Four destinations are offered: a file saved on the
participant’s own device, a Google Sheet, another web address that
accepts one row per participant, or a table in a Supabase project. This
article walks the Google Sheet route end to end: a small script bound to
the sheet receives one row per participant, you download the sheet as a
CSV file, and read_form_responses() reads that file for the
scoring functions. The steps are in the order you take them.
1. Deploy the sheet’s script
A Google Sheet accepts rows from the page through a Google Apps
Script web app bound to it. The script’s code, and the steps to deploy
it, are in the hitop-form README under Send
responses to a Google Sheet. The Study Link Builder shows the same
steps when you choose “A Google Sheet” under “Where responses go”, and
its “Copy the script” button copies the code. In outline: create a new
sheet, open Extensions and then Apps Script, replace the contents of
Code.gs with the README’s code, save, and deploy it as a
web app that runs as you and that anyone can access. Copy the web app
URL, which ends in /exec.
The script appends one row per request to a tab named
Responses, taking the first row’s keys as the header. It
writes every cell behind a leading apostrophe and formats it as text, so
a participant code such as 007 keeps its zeros and, in the
sheet itself, a value such as =1+1 stays those four
characters rather than becoming a formula. It refuses a request whose
body is not one object, holds more than 1000 keys, or has a key outside
lower-case letters, digits and underscores, so no one can grow the
header without limit or put a formula in it.
Deploy one sheet and script per form. A key the header lacks is added
to it, so a sheet that receives two forms’ rows holds the union of their
columns. When the two forms are two instruments,
read_form_responses() refuses the download. Its item
columns carry two stems (hitopbr_01 beside
pid5bf_01), but each row’s instrument cell
names one, so no row matches the columns. A file whose rows each hold
both instruments is another shape, which the section Several instruments in one
session describes. When they are a HiTOP-SR module and the full
HiTOP-SR, the columns share one stem, so the download reads as one data
frame with the module’s rows blank on the items outside it, unless the
module’s link asked for a random order: its rows’
item_order then lists only the module’s items, and
read_form_responses() refuses the download. Nothing else in
the download or in read_form_responses() tells the two
apart.
A link that lists several instruments is its own form here. If two
links list the same instruments in a different order and post to one
sheet, the columns keep the first link’s order, and each row’s
instrument cell keeps its own.
read_form_responses() then refuses the download and names
the first row that differs.
2. Make the study link
Open the Study Link
Builder. Its required parts come first: the instruments, the study
name and where responses go. The optional parts sit below them in five
closed sections. Press a section’s name to open it. Choose the
instrument and name the study. For a HiTOP-SR module, choose “HiTOP-SR
module (scales you choose)” as the instrument. Then paste or choose the
module file written by write_module() in that row. For two
or three instruments in one session, see Several instruments in one
session. The Study Link Builder, opened from the “Open the Study
Link Builder” button on an instrument’s download page, arrives with that
instrument chosen. When opened with a module in its address, it arrives
with a “HiTOP-SR module” row that holds the module file. In the
“Participants and recruiting site” section, give a participant
identifier if the link is for one person. Leave it empty for a link
shared with many, and the online form asks each participant for one
before the form starts. Under “Where responses go”, choose “A Google
Sheet” and paste the /exec URL from step 1 into the “Web
app URL” field. That field takes only an Apps Script web app’s address,
on script.google.com with a path that ends in
/exec. The builder refuses any other, such as the sheet’s
own address. Press “Make the link”. The link shows under “Your study
link”, beside a “Copy the link” button. Send it to the participants.
When a participant presses Finish, the page posts their answers to
the address as one row and waits for the script to confirm. On a
confirmed send the page says the answers were sent to the study team,
and saves nothing. When the send is not confirmed, because of an error,
a lost connection, or no answer within the page’s limit, the page saves
a CSV file on the participant’s device and names it, so the participant
can send it to you by hand. That screen also has a “Save the file”
button, which saves the same file again; its instructions tell the
participant to press it if the file did not appear. A row that reached
the sheet after the limit is stored there as well; the
submitted column identifies such a pair.
3. Download the sheet as CSV
When collection ends, open the sheet’s Responses tab and
choose File, then Download, then Comma Separated Values (.csv). The file
has one header row and one row per participant, in the page’s column
order: study, participant,
instrument, form_build,
submitted, then one column per item, in one group per
instrument when the link names several. A link that asks for a random
order adds item_order after submitted, and its
item columns are in the instrument’s order rather than the order shown.
A link that recruits through Prolific adds prolific_study
and prolific_session after submitted, or after
item_order when the file has it, before the item columns.
The script builds the header from the first row it receives and appends
a key it has not seen, so a sheet that already holds rows without
item_order puts that column after the item columns.
read_form_responses() reads it there. Start a new sheet for
such a link if you want the column sixth.
Anyone who has the link can post a row to the sheet, because the script’s URL is inside the link. Look over the sheet before scoring it.
The download does not keep the apostrophe. A participant identifier
or a text answer that starts with =, such as
=1+1, is text in the sheet. If you open the downloaded file
in a spreadsheet program, it can read such a cell as a formula.
read_form_responses() returns it as text.
4. Read the file
read_form_responses() reads the download. The file below
is one such download, from a sheet that received two HiTOP-BR walks of
the page. The package installs it as an example, and
system.file() gives its path.
library(hitop)
path <- system.file("examples", "responses-sheet-hitopbr.csv", package = "hitop")
responses <- read_form_responses(path)
responses
#> # A tibble: 2 × 53
#> study participant instrument form_build submitted item_order
#> <chr> <chr> <chr> <chr> <dttm> <chr>
#> 1 fixture =1+1 hitopbr 2026-09-20 2026-09-23 19:33:25 NA
#> 2 fixture 007 hitopbr 2026-09-20 2026-09-23 19:33:30 NA
#> # ℹ 47 more variables: prolific_study <chr>, prolific_session <chr>,
#> # hitopbr_01 <int>, hitopbr_02 <int>, hitopbr_03 <int>, hitopbr_04 <int>,
#> # hitopbr_05 <int>, hitopbr_06 <int>, hitopbr_07 <int>, hitopbr_08 <int>,
#> # hitopbr_09 <int>, hitopbr_10 <int>, hitopbr_11 <int>, hitopbr_12 <int>,
#> # hitopbr_13 <int>, hitopbr_14 <int>, hitopbr_15 <int>, hitopbr_16 <int>,
#> # hitopbr_17 <int>, hitopbr_18 <int>, hitopbr_19 <int>, hitopbr_20 <int>,
#> # hitopbr_21 <int>, hitopbr_22 <int>, hitopbr_23 <int>, hitopbr_24 <int>, …Each row of the file is a row of the result. The lead columns are
typed: participant is character, so the codes
=1+1 and 007 come back as text, as the sheet
itself stored them; form_build is character, the page’s
build date as the file holds it, such as "2026-09-20";
submitted is a UTC date-time. Convert
form_build with as.Date() when you need a
date. Every item column is an integer. The sixth column,
item_order, is the order the participant saw the items, as
item numbers, when the file records one and NA when it does
not, and scoring does not read it. The page records it when the study
link asks for a random order, which the “Show the items in a random
order” box sets, in the Study Link Builder’s “Item order” section. The
seventh and eighth columns, prolific_study and
prolific_session, are the study and session identifiers
Prolific passed to the page when the link recruits through Prolific, as
the Prolific route below describes,
and NA when it does not, as here. Scoring does not read
them either.
A folder can hold the download beside any files participants sent by
hand: read_form_responses() reads every .csv
file in it, file by file in path order, and each file’s rows in its own
order. Every file must hold the same item columns in the same order, so
read each form’s files in a call of their own.
A file can also hold answer columns, which hold the answers to
questions of your own, each named q_ and then the
question’s name, such as q_age. The page writes them for a
link with questions, as the section Your
own questions describes. read_form_responses() accepts
them anywhere after submitted and returns them after the
item columns. They come in the order the reader first meets them, with
the files in path order and each file read from left to right. Each
answer column is character, as written, and a blank cell is
NA. Files that hold different answer columns read together,
and a row from a file without a column holds NA in it. The
answer columns do not enter the check that every file holds the same
item columns. After q_, a name must hold a lower-case
letter and then up to 29 lower-case letters, digits or underscores. The
reader stops with an error that names the file and the column when a
name does not. The reader does not convert an answer, so turn a number
answer into a number yourself:
# The download above with an answer column added, as a sheet would hold it.
lines <- readLines(path, warn = FALSE)
lines[1] <- paste0(lines[1], ",q_age")
lines[-1] <- paste0(lines[-1], c(",34", ","))
answers_file <- tempfile(fileext = ".csv")
writeLines(lines, answers_file)
with_answers <- read_form_responses(answers_file)
with_answers$q_age
#> [1] "34" NA
as.integer(with_answers$q_age)
#> [1] 34 NA5. Score it
Pass the item columns to the scoring function for the instrument. For
the HiTOP-BR that is score_hitopbr():
items <- grep("^hitopbr_", names(responses), value = TRUE)
score_hitopbr(responses, items = items, append = FALSE)
#> # A tibble: 2 × 8
#> hbr_antagonism hbr_detachment hbr_disinhibition hbr_internalizing
#> <dbl> <dbl> <dbl> <dbl>
#> 1 3.33 2.4 1.89 2.25
#> 2 3.33 2.4 1.89 2.25
#> # ℹ 4 more variables: hbr_somatoform <dbl>, hbr_thoughtDisorder <dbl>,
#> # hbr_externalizing <dbl>, hbr_pFactor <dbl>A full HiTOP-SR scores with score_hitopsr() the same
way, and a PID-5 form with score_pid5() and the matching
version, as vignette("pid5_scoring") shows. A
HiTOP-SR module scores through the module file the online form showed,
with layout = "printed"; the Building HiTOP-SR Modules article walks
that step.
Several instruments in one session
A study link can give two or three instruments, one after another, in
one session. In the Study Link Builder, press “Add an instrument” to add
a row, up to three, and put the rows in the order the page gives them.
The link then carries an instruments field, a list of the
instruments’ names such as ["hitopbr", "pid5bf"], in place
of instrument. A list holds at most one form of the PID-5,
and names no instrument twice. A “HiTOP-SR module” row is the HiTOP-SR
with its module file, so a list holds it or a HiTOP-SR row, not
both.
The page shows its screens in this order:
- With consent text in the link, the consent screen.
- With questions before the form, the “Before you begin” screen.
- For each instrument in turn, its start screen and then its item pages. Each start screen and each item page names its part, such as “Part 2 of 3”, and the item and page numbers start again at 1.
- With questions after the form, the “Before you finish” screen.
Under a random order, each instrument’s items are shuffled among that
instrument’s items only. read_form_responses() reads the
file the page saves, and the CSV download of the sheet’s or table’s
rows. Each row holds the answers to all the instruments. The file has
this shape:
- The item columns form one group per instrument, the columns of each
group side by side, and each group holds one stem. The optional lead
columns and the answer columns can sit anywhere after
submitted, between two groups or inside one. - The
instrumentcell holds the stems in the order of the groups, joined by single spaces, such ashitopbr pid5bf. - The
form_buildcell holds one build date per instrument, in the same order, joined by single spaces, such as2026-09-20 2026-09-18. The result keeps it as written, soform_buildis character for every file. - Under a random order, the
item_ordercell holds one group per instrument, in the same order, joined by a space, a bar and a space. Each group is that instrument’s item numbers in the order shown, or the module’s items for a HiTOP-SR module in the list. For two instruments of three and four items, a cell could be2 1 3 | 4 1 3 2. A file of one instrument has one group and no bar. - The answer columns of your own questions follow the last group. The
file’s name starts with the instruments’ names joined by
-, such ashitopbr-pid5bf_Pilot-A_p001_20260920T211531Z.csv.
The reader stops with an error that names the file and the row when
an instrument cell differs from the stems of the groups, a
form_build cell holds a date count other than the stem
count or a date that does not parse, or an item_order cell
breaks the groups. It stops with an error that names the file when
another stem’s columns split the columns of a stem. Files whose groups
differ in stems or in order do not read together, as for any item
columns.
Score each instrument in its own call, with the columns chosen by stem. The file below is the download above with the PID-5-BF’s 25 items added to each row, as a link of both instruments writes them:
lines <- readLines(path, warn = FALSE)
lines[1] <- paste0(lines[1], ",", paste(sprintf("pid5bf_%02d", 1:25), collapse = ","))
lines[-1] <- sub(",hitopbr,2026-09-20,", ",hitopbr pid5bf,2026-09-20 2026-09-18,",
lines[-1], fixed = TRUE)
lines[-1] <- paste0(lines[-1], ",", paste(rep_len(0:3, 25), collapse = ","))
both_file <- tempfile(fileext = ".csv")
writeLines(lines, both_file)
both <- read_form_responses(both_file)
both[c("participant", "instrument", "form_build")]
#> # A tibble: 2 × 3
#> participant instrument form_build
#> <chr> <chr> <chr>
#> 1 =1+1 hitopbr pid5bf 2026-09-20 2026-09-18
#> 2 007 hitopbr pid5bf 2026-09-20 2026-09-18
hbr_items <- grep("^hitopbr_", names(both), value = TRUE)
bf_items <- grep("^pid5bf_", names(both), value = TRUE)
score_hitopbr(both, items = hbr_items, append = FALSE)
#> # A tibble: 2 × 8
#> hbr_antagonism hbr_detachment hbr_disinhibition hbr_internalizing
#> <dbl> <dbl> <dbl> <dbl>
#> 1 3.33 2.4 1.89 2.25
#> 2 3.33 2.4 1.89 2.25
#> # ℹ 4 more variables: hbr_somatoform <dbl>, hbr_thoughtDisorder <dbl>,
#> # hbr_externalizing <dbl>, hbr_pFactor <dbl>
score_pid5(both, items = bf_items, version = "BF", append = FALSE)
#> # A tibble: 2 × 6
#> pid_disinhibition pid_detachment pid_psychoticism pid_negativeAffectivity
#> <dbl> <dbl> <dbl> <dbl>
#> 1 0.8 1.6 2 1.6
#> 2 0.8 1.6 2 1.6
#> # ℹ 2 more variables: pid_antagonism <dbl>, pid_total <dbl>The Supabase route
The page can insert each row into a table in a Supabase project
instead. The Study Link Builder shows the SQL that creates the table,
and the hitop-form README under Send
responses to Supabase walks the setup. The table’s CSV export from
the dashboard’s Table Editor reads with
read_form_responses() exactly as the sheet’s download
does.
In the table itself, a participant identifier or a text answer such
as =1+1 is stored as those four characters. If you open the
export in a spreadsheet program, it can read such a cell as a formula.
read_form_responses() returns it as text.
The Prolific route
A study recruited through Prolific gives each participant a
study URL. Prolific fills the participant’s ID, the study’s ID and the
submission’s ID into that URL through three placeholders,
{{%PROLIFIC_PID%}}, {{%STUDY_ID%}} and
{{%SESSION_ID%}}, as Prolific’s
API reference states under the study’s
external_study_url field, and it asks that all three be
saved with the data (What
survey / experimental software is compatible with Prolific?). The
page fits that in two steps at the Study Link Builder.
In the “Participants and recruiting site” section, choose Prolific in
the “Recruiting site” menu and leave the participant field empty. The
builder then prints the link with the three placeholders on its end.
Paste that link as the study URL on Prolific’s study page. When a
participant opens it, the page takes their Prolific ID as the
participant identifier and asks for none, and it writes the study and
submission IDs into prolific_study and
prolific_session, after submitted (and after
item_order under the random order). A Supabase table for
such a link takes the two columns too, and the builder’s SQL adds them.
When you preview the study on Prolific, the preview passes a
24-character ID in place of the placeholder (Previewing
your study), so the preview walks the form as a participant would; a
link opened with the placeholders still in it, as the builder’s “Open
the link” does, asks for the identifier instead. Prolific’s own “I’ll
use URL parameters” option, in the Data collection section of the
study’s setup, appends the three parameters to the study URL itself (the
compatibility article, under “Recording participants’ Prolific
IDs”). A pasted link with the builder’s placeholders therefore carries
each parameter once unless that option adds it again; then the address
holds the parameter twice, and the page reads the first value that is
neither blank nor a placeholder.
Give the completion URL from the study’s page on Prolific, of the
form https://app.prolific.com/submissions/complete?cc=… (the
compatibility article, under “Returning Participants to Prolific”),
in the builder’s “Completion URL” field, in its “When the participant
finishes” section. After a confirmed send the page shows the sent
screen, with a link to that address in place of “You can close this
page.”, and then sends the participant there, which is the return
Prolific recommends (Data
collection). After a saved file, because the link names no web
address or Supabase table or the send was not confirmed, the page shows
the file name first, then the instructions and the “Save the file”
button, and then a link to that address, so the participant sees the
file’s name and can save it again before leaving.
A study can hold more than one completion code, each for its own
outcome (Custom
completion codes), and each code has its own ?cc=
address, as the
API reference states under completion_codes. To give a
saved file its own code, put that code’s address in the builder’s
“Completion URL after a saved file” field, below the completion URL in
the “When the participant finishes” section. The saved-file screens then
link to it in place of the completion URL, and a confirmed send still
goes to the completion URL.
The download then reads as above, with prolific_study
and prolific_session filled. The file below is one the page
saved under such a link, with the Prolific ID as the participant:
path <- system.file("examples", "responses-hitopbr-prolific.csv", package = "hitop")
responses <- read_form_responses(path)
responses[, c("participant", "prolific_study", "prolific_session")]
#> # A tibble: 1 × 3
#> participant prolific_study prolific_session
#> <chr> <chr> <chr>
#> 1 5a9d64f5f6dfdd0001eaa73d 66f3a1b2c3d4e5f60718293a 66f3a1b2c3d4e5f60718294bThe SONA and CloudResearch Connect routes
Other recruiting sites can also pass each participant’s identifier to
the page in the study link’s address, under a parameter of their own. In
the Study Link Builder’s “Participants and recruiting site” section,
choose the site in the “Recruiting site” menu and leave the participant
field empty. The link then names the parameter in its
participantParam field, and the page takes the participant
identifier from it. When the address carries no value there, a blank
one, or a placeholder the site did not fill, the page asks for the
identifier on its start screen. No column is added: the identifier is
the participant column, so the download reads as in the
steps above.
SONA replaces the text
%SURVEY_CODE% in a study’s Study URL with a number unique
to the participant (Using
the SURVEY CODE Feature), and its Qualtrics guide has the Study URL
end in ?id=%SURVEY_CODE% (Qualtrics Help
Page). With SONA chosen, the builder names id as the
parameter, and the printed link ends in
&id=%SURVEY_CODE%. Paste it as the Study URL in SONA.
To grant credit, SONA’s client-side completion URL, of the form
https://yourschool.sona-systems.com/webstudy_credit.aspx?experiment_id=123&credit_token=…&survey_code=XXXX,
must be loaded with the participant’s survey code in place of
XXXX (External
Study Credit Granting). Give that address in the builder’s
“Completion URL” field, in the “When the participant finishes” section,
with {participant} in place of XXXX. The page
replaces {participant} with the participant’s identifier
before it sends the participant there after a confirmed send, and before
it links there after a saved file (unless the builder’s “Completion URL
after a saved file” names another address, which takes
{participant} the same way).
That completion URL carries a key specific to your study. SONA notes
that a participant can read it, because their browser loads the URL, and
can use it to try other survey codes and so grant credit to other
participants (Security
Considerations). Here the address sits inside the study link, so a
participant can read the key before they finish, not only at the end.
SONA names its server-side completion URL as the alternative. The
external study loads that URL from its own server, and the page has no
server, so the page cannot use it. A participant does not need to read
the key to try another code. If the address carries no survey code, for
example because the participant removed id from it, the
start screen asks for the identifier. The page then puts whatever the
participant types into the completion URL.
CloudResearch Connect
asks a study to record each participant’s Connect ID from a variable
named participantId, and to end either with a completion
code or with the completion redirect URL Connect gives the study (How
to Integrate your Survey with Connect). With Connect chosen, the
builder names participantId as the parameter and adds
nothing to the link. Give the link as the project URL in Connect, and
the completion redirect URL as the builder’s “Completion URL”, in the
“When the participant finishes” section. Connect’s
assignmentId and projectId, which that page
says need not be collected, are not recorded.
For another site, choose “Another site” in the “Recruiting site” menu
of the “Participants and recruiting site” section. Then type the name of
the parameter that site adds to the address in the “Address parameter”
field below the menu. The builder refuses id and
participantId there, because the SONA and Connect choices
write those names.
Consent text before the form
The consent text belongs to you and your review board. The page shows the text you give and adds no consent wording of its own. Paste the approved text into the “Consent text” box, in the Study Link Builder’s “Consent” section. The page then shows the text on a screen of its own, headed “Consent to take part”, before the form and its start screen. Under the text are an “I agree” and an “I do not agree” button, and “I agree” goes on to the start screen.
The page reads the text as plain text. A blank line starts a new
paragraph, and a single line break stays a line break. A tag or an
entity, such as <b> or &, shows
as typed, so the text cannot carry formatting or links. The text can
hold up to 20,000 characters, and the builder refuses a longer one.
“I do not agree” first asks the participant to confirm, with “Yes, I
do not agree” and “Go back”. After “Yes, I do not agree”, the page shows
a closing screen with no way back to the form. The page sends no answer
and saves no file, so no row reaches the destination and
read_form_responses() never sees the participant. The
closing screen shows the builder’s “Declined text”. When that box is
empty, it shows “You chose not to take part. You can close this page.”,
or only its first sentence when the link sends the participant on, as
the next sentences describe. A recruiting site can give a study a
completion address for this outcome, such as a Prolific completion code
for participants who do not consent (Custom
completion codes). Give it as the builder’s “Completion URL after a
decline”, in the “Consent” section. The link carries it as its
completeDeclined field, and the page sends a participant
who declines to that address.
A link with consent text carries the same fields packed, so a long text makes a shorter link. The rows, the file and the Supabase table have the same columns with consent text as without it. The hitop-form README’s section Show consent text before the form has the details.
Your own questions
A study link can carry questions of your own, such as age or how the participant heard of the study. Add them in the Study Link Builder’s “Your own questions” part. Each question is asked either before the form or after it. The page asks the before questions on a screen of its own after any consent screen and ahead of the start screen. It asks the after questions on a screen of its own after the last page of items. The questions never share a screen with the items.
The number of questions has no limit of its own. The page reads at most 100,000 bytes of setup, so the builder refuses a setup that is larger and names its size. A link that carries its setup must also fit in the 8,177 characters that the online form’s host accepts. That count includes the IDs a recruiting site adds, as the section below describes. A setup too large for that goes in a file you host, as the section below describes. With a Supabase table, the builder also refuses to make a link for a setup whose table would have more than 1,600 columns, the most a PostgreSQL table can have. PostgreSQL also limits the size of one row, which must fit in one page of 8,192 bytes. A text answer that is long enough can be kept outside the row, and 18 bytes of it then stay in the row. A shorter answer stays whole in the row. So a table with many text questions can refuse a row of long answers, and the page then saves the file on the participant’s device.
Each question has a name, and its answer is written in a column named
q_ and then the name, after the item columns.
read_form_responses() returns these columns as the section
on reading the file describes. A question has one of four types, and
each writes its answer as text:
- A text question writes the answer as typed.
- A whole-number question writes the number in digits, with no leading
zero and a minus sign only below zero. A typed
007is written7. - A question with one choice writes the number of the chosen option, counted from 1 in the order the link lists the options.
- A question with any number of choices writes the numbers of the
chosen options in ascending order, separated by single spaces, such as
1 3.
An unanswered question writes an empty cell, which the reader returns
as NA. The numbers stand for positions in the link’s list
of options, so a change to the order of the options between two links
changes what the numbers mean. Keep the order the same in every link of
a study. To turn a column of several choices into numbers, split it at
the spaces:
days <- c("1 3", "2", NA)
lapply(strsplit(days, " "), as.integer)
#> [[1]]
#> [1] 1 3
#>
#> [[2]]
#> [1] 2
#>
#> [[3]]
#> [1] NAYou can also write the questions in a spreadsheet. In the builder,
“Download a template” saves a CSV file with one example question of each
type. Its first row names the columns, in lower case. The columns
list, name, text and
type are required, and options,
required, min and max are
optional. Each further row is one question. list is
before or after, and type is
text, number, choice or
multi, both in lower case. options holds the
options in one cell, separated by |, such as
Phone|Tablet|Computer. Save the file as “CSV UTF-8” and
choose it under “Load questions from a file”, in the builder’s “Your own
questions” section. The builder reads the file in your browser and sends
it nowhere. It refuses a file with a fault and names the fault. A fault
in one cell is named by its row and column, or by its row and field
number in the header row or past the last column. “Download these
questions” saves the builder’s questions in the same form. The
hitop-form README’s section Write
your questions in a spreadsheet describes each column.
With a Supabase table, the builder’s SQL adds one text column per question. A table made before the questions were added has no column for them, so Supabase refuses the row, and the page then saves the file on the participant’s device. The columns come from the question names alone, so make a new table from the builder’s SQL when you add a question or change a name. A link with questions carries its fields packed, as a link with consent text does. The hitop-form README’s section Ask your own questions has the details.
Keep the setup in a file you host
A study link carries its whole setup by default. Consent text, many questions or a module make it long, and some sites and mail programs cut long links. If a link is longer than 8,000 characters, the builder says so under the link. RFC 9110 recommends that senders and recipients of web addresses support at least that length. GitHub Pages hosts the online form. When the online form is not in the cache that serves GitHub Pages, GitHub refuses a link with more than 8,177 characters after the host name, so the builder makes no such link.
The builder’s count includes what a recruiting site adds. It counts
each Prolific ID as 24 characters. Prolific’s API reference shows
participant, study and session IDs of that length on its Get
submission page. None of the Prolific pages that the package’s
source notes list states a maximum length. A Prolific link also counts
the three IDs once more, 108 characters in all, because Prolific’s “I’ll
use URL parameters” option can append them to the study URL. SONA’s
%SURVEY_CODE% counts as 7 characters, the longest code
SONA’s help gives.
For a short link, open the Study Link Builder’s “Where the setup is
kept” choice and pick “In a file I host”. Press “Download the setup
file” to save setup.json, and put it in a public GitHub
repository. Under “Address of the setup file”, give the file’s raw-file
address, such as
https://raw.githubusercontent.com/your-name/your-study/main/setup.json.
Then press “Make the link”. The builder fetches the file. If the file
matches the form, the builder makes the link.
The link names the file in setup and its fingerprint in
sha256. The online form fetches the file for each
participant. If the setup in the file changed after the link was made,
the online form refuses it. A change of spaces or line endings between
the values alone is not refused. So a file whose setup was edited needs
a new link: download the file again, replace the hosted copy and make
the link again. The file’s host sees each participant’s request for the
file. The hitop-form README’s section Keep
the setup in a file you host has the details.
Who holds the data
The answers go to the web address or Supabase table the link names, and to a file on the participant’s device when the link names neither or the send is not confirmed. They go nowhere else. With a Google Sheet, Google holds the rows; with a Supabase table, Supabase holds them. The page’s host, GitHub Pages, receives the study link with each page load, and the link carries the instruments and any module, the study name, the participant code, any consent text, the web address and, for Supabase, its key. It receives no answers.
A link that names a setup file carries the file’s address and fingerprint in place of the setup. The file’s host receives each participant’s request for the file. Anyone can read a file in a public repository, so the file shows where responses go, a Supabase key included, to anyone who finds it.
A participant code is whatever the link or the participant supplies. A code you put in the link reaches the page’s host with the link, and a code the participant types is whatever they choose to type. If codes must not identify anyone, issue pseudonymous codes in the links you send and keep the key to them yourself. Under the Prolific route the code is the participant’s Prolific ID, which identifies them to Prolific, and the page’s host receives the three Prolific parameters, the ID among them, with each page load, as it receives the rest of the address. Under the SONA and Connect routes the code is the site’s own identifier, and the host receives it the same way.
Whether a service may hold your study’s data is a question for your institution, and any agreement it needs with the service’s vendor, such as a business associate agreement or a data processing agreement, is the institution’s to make. This article describes where the rows go; it does not say which destination is suitable for which study.