Skip to contents

hitop-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.

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 NA

5. 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:

  1. With consent text in the link, the consent screen.
  2. With questions before the form, the “Before you begin” screen.
  3. 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.
  4. 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 instrument cell holds the stems in the order of the groups, joined by single spaces, such as hitopbr pid5bf.
  • The form_build cell holds one build date per instrument, in the same order, joined by single spaces, such as 2026-09-20 2026-09-18. The result keeps it as written, so form_build is character for every file.
  • Under a random order, the item_order cell 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 be 2 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 as hitopbr-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 66f3a1b2c3d4e5f60718294b

The 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.

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 &amp;, 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 007 is written 7.
  • 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] NA

You 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.