jsPsych 7 + Pavlovia DATABASE mode: results POST succeeds but no data in database export

Hi all,

I’m running a jsPsych 7 experiment on Pavlovia and running into an issue specifically with DATABASE saving. I’d like to keep task details / stimuli private due to IRB constraints, but I can share the technical setup and what I’ve tried.

Setup

  • jsPsych version: 7.3.3

  • Pavlovia plugin: jspsych-7-pavlovia-2022.1.1.js (copied locally and slightly modified)

  • Saving mode: DATABASE (selected in the Pavlovia project settings)

  • Project type: pure jsPsych (no PsychoPy Builder)

The experiment itself runs fine (instructions, practice, main task, finish screen). The issue is only with how results are saved when the project is in DATABASE mode.

What works (CSV saving)

If I switch the Pavlovia project to CSV save mode and use the unmodified jspsych-7-pavlovia-2022.1.1.js:

  • The final /results POST to
    …/api/v2/experiments/{projectId}/sessions/{token}/results
    returns 200 OK.

  • The Form Data for that request contains:

    • key: e.g. EXPERIMENT_NAME_PARTICIPANT_SESSION_2026-... .csv

    • value: a standard jsPsych CSV dump (header row + one row per trial).

  • In Pavlovia’s UI, if I download results, the zip contains that CSV file as expected.

So the basic experiment and saving pipeline work under CSV.

What fails (DATABASE saving)

When I change the Pavlovia project setting to DATABASE, and keep the original plugin (from the CDN), what happens is:

  • The /results POST returns 500 with:

    • "context": "when saving data from a previously opened session on the server"

    • "error": "unable to JSON decode the value: Extra data: line 1 column 13 (char 12)"

From other forum answers this made sense: in DATABASE mode, the backend tries to JSON.parse the value, but the stock plugin always sends CSV (jsPsych.data.get().csv()), so JSON decoding fails.

My local plugin modification

To avoid that server‑side JSON error, I made a local copy of the Pavlovia jsPsych 7 plugin and changed just the saving part:

  • When the session is opened, the plugin stores:

    PavloviaPlugin._config.experiment.saveFormat = Symbol.for(serverData.experiment.saveFormat);

  • In the finish command:

    const isDatabase = PavloviaPlugin._config.experiment.saveFormat === Symbol.for(‘DATABASE’);

    const data = isDatabase

    ? this._jsPsych.data.get().json() // JSON string

    : this._jsPsych.data.get().csv(); // default CSV

    await this._finish(trial, data);

  • In the unload / incomplete‑save path, I do the same: JSON for DATABASE, CSV otherwise.

For the _save method:

  • I ended up sending:

    • key: always a .csv filename (so that the server’s “this is a result file” logic matches the CSV case), something like:

      const key = EXPERIMENT_NAME + ‘_’ + trial.participantId + ‘_SESSION_’ + dateString + ‘.csv’;

    • value: JSON string when saveFormat === DATABASE, CSV string otherwise.

  • For non‑RUNNING / pilot / fallback download, I still offer a file that uses:

    • .json extension + application/json MIME in DATABASE mode

    • .csv + text/csv in CSV mode

So in DATABASE mode, the /results request looks like:

  • key: EXPERIMENT_NAME_PARTICIPANT_SESSION_... .csv

  • value: a valid JSON string (checked manually by copying the value field into a JSON formatter).

Current behavior in DATABASE mode (with modified plugin)

With the local plugin and DATABASE mode:

  • The final /results POST returns 200 OK (no more JSON‑decode 500).

  • The response JSON for /results doesn’t show any obvious error.

  • The session appears in Pavlovia as completed.

  • However, when I go to Results → Download, the zip is empty. There is no file corresponding to that run in the DATABASE export.

I’ve verified that:

  1. The modified plugin is actually loaded (I can see my comment and changes in DevTools → Sources).

  2. The session open response has experiment.saveFormat: "DATABASE" and status2: "RUNNING".

  3. The value we send in DATABASE mode:

    • Parses as valid JSON (no errors).

    • Is a JSON array of objects, each object matching one CSV row (fields like trial_type, trial_index, phase, pic1, pic2, etc.).

    • No stray quotes or extra characters before/after the JSON.

  4. In CSV mode (stock plugin), the zip contains the expected CSV, so the backend pipeline does work for this experiment.

My questions

  1. Is jsPsych 7 officially supported with DATABASE saving on Pavlovia yet?

    • If so, is there a documented, expected JSON format for the value field when saveFormat is "DATABASE"?

    • For example, should it be:

      • A plain array [{...}, {...}] (what I’m sending now), or

      • Wrapped like { "data": [ ... ] }, or

      • Something more specific (per‑trial fields, metadata, etc.)?

  2. Does the DATABASE import layer require a particular key pattern to decide which uploads to treat as “results”?

    • Right now I’m keeping the .csv extension in the key to mimic the working CSV case, but the body is JSON. Is there any rule like “only .csv keys are imported into the DB”, or is that extension only used for the file‑download export?
  3. Is there any way (logs, API, etc.) to see why a given /results upload was not turned into a DB row, even though the HTTP response is 200 and the session is marked completed?

    • For example, some status field, or a known error condition like “JSON didn’t match expected schema”?

If jsPsych 7 + DATABASE saving isn’t supported yet (or only via a particular integration), that would also be useful to know. Ideally I need to stick to database saving to satisfy IRB requirements.

Thanks very much for any guidance here. I’m happy to share redacted examples of:

  • The exact /results Form Data for a CSV‑mode run (working), and

  • The /results Form Data for a DATABASE‑mode run (200 OK but empty export),

if that would help diagnose what the backend is expecting.