Skip to main content
Custom JavaScript lets you attach a script to a question in a human survey. The script runs in the respondent’s browser once the question is on the page, so you can build interactive tasks, measure timing and behaviour, and record events with the survey.
Custom JavaScript is available on approved accounts only. Write to [email protected] to request access. Until your account is approved, a humanize schema that includes a script is rejected when you save it, and no part of that schema is saved.

Checking access

Access is per account: once yours is approved, the human surveys you create can include custom JavaScript.

Adding a script

Scripts go in the humanize schema, under questions[question_name].javascript.hooks. A hook maps an event to the script that runs on it. The only event is "question.ready", which fires once the question has been rendered and its inputs are on the page.
To add or change a script on a survey that already exists, use Coop.patch_human_survey_humanize_schema:
Set "javascript": None on a question to remove its script. Most question types support javascript, including survey messages; see Humanize schema for the list. compute and image_generation questions do not: they run in the background and never appear on the page.

Writing a hook

A hook is the body of a function, not a function. Write the statements you want to run; don’t wrap them in function () { ... }. Inside a hook you have: context carries nothing that identifies the respondent. Each question’s hook runs once when that question appears. If the same script is attached to several questions, it runs once for each, and context.questionName tells them apart.

Recording events with ep.log

ep.log(eventName, payload) records an event against the respondent’s response. Use it for anything you want to analyse later: timings, clicks, which condition a respondent saw.
  • eventName must be a non-empty string. Names longer than 200 characters are cut to 200.
  • payload is optional and must be convertible to JSON: objects, arrays, strings, numbers, booleans and null. A payload that isn’t (a page element, an object that refers to itself) is not recorded, and a warning appears in the browser console.
  • A payload larger than 4,000 characters, measured as JSON, is not recorded.
  • Each response records at most 10,000 events.
ep.log returns nothing. Events are sent in batches in the background, so don’t wait on it.

What a script may do

Scripts can build interface inside the survey, read and write answers, control when respondents can move on, measure behaviour, randomise conditions, read parameters from the page URL, load images, audio, video and other media to show, and use the camera, microphone or location for a task the respondent is told about. A script can also load a well-known, widely used library, such as d3, Chart.js or lodash, from a public CDN like cdnjs, jsDelivr or unpkg, at a pinned version (for example https://cdn.jsdelivr.net/npm/[email protected]/dist/d3.min.js, not d3@latest). Your own code must still be plain JavaScript written in the script itself. A script can’t be saved if it:
  • Loads code from anywhere else: an unfamiliar or personal site, a paste site, a little-known or custom package, source hosted somewhere other than a named library, or a URL put together while the script runs.
  • Builds code at runtime, for example by passing a constructed string to eval, new Function, setTimeout or setInterval, or by inserting markup that contains a script.
  • Makes any request to Expected Parrot, or sends respondent data anywhere else. ep.log is how data leaves a script.
  • Reads cookies, tokens, credentials, or storage it didn’t create.
  • Is obfuscated or minified so that what it does can’t be read from the source. This applies to your script, not to a well-known library it loads.
  • Asks for passwords or payment details, or imitates a login or other dialog.
  • Collects anything unrelated to taking the survey, or uses the camera, microphone or location covertly.
  • Keeps running after the respondent has finished or left the survey.
  • Misleads or traps the respondent, for example by opening popups, preventing them from leaving, or hiding the survey’s own notices.
  • Uses the respondent’s device for something other than the survey.
If a script can’t be saved, no part of that humanize schema is saved, and the survey keeps what it had. If you think a script was refused by mistake, write to [email protected].

Limits

  • Each hook can be at most 64,000 characters, and all hooks on one question at most 256,000.
  • One save can add at most 25 different new scripts. A script used unchanged on several questions counts once, and scripts the survey already has don’t count. Save larger surveys in smaller batches.

Retrieving events

Use Coop.get_all_human_survey_events to get every event a survey has recorded, oldest first:
Events from preview links are included and marked is_preview, so leave them out of an analysis. Events that arrive while the download runs are included, and no event comes back twice. To fetch new events later without downloading everything again, pass the id of the last event you downloaded as after. Take it from the full download, before filtering out previews:
Fetching only new events is useful for checking your scripts while a survey is collecting, but it can occasionally miss an event that was still being saved when you downloaded. For analysis, download the full log once fielding has ended, at least an hour after the last response.
To fetch one batch at a time, use Coop.get_human_survey_events. It takes after and limit (at most 200, the default), and returns {"events", "next_cursor", "has_more"}. Pass next_cursor as after to get the next batch while has_more is True. next_cursor is returned even when there is nothing more, so you can keep it and use it later. To see how many events a survey has without downloading them, use Coop.count_human_survey_events. The count is as of the call; on a survey still collecting, a download started afterwards can return more. Each event includes: session_state is one of:
  • "complete": the session ended normally and no events are missing.
  • "truncated": some events from this session are known to be missing, for example because they broke the limits above.
  • "unknown": no events are known to be missing, but the session never signalled that it ended, for example because the respondent’s browser closed or lost its connection. Its log may or may not be whole.
session_state describes the whole session, so every event from one session carries the same value.

From the command line

The ep CLI covers the same steps. --javascript takes QUESTION:HOOK=FILE, naming the question, the hook, and a file holding the script:
--all requires --output, which can end in .json or .jsonl. Every result includes next_cursor, the value to pass as --after next time. If a download fails part way, no output file is written. As with the Python methods, use --after for checking a survey while it collects, and a full download once fielding has ended for analysis.