Skip to content

Responses — browsing, CSV export, deletion

Responses in the workspace toolbar opens the responses browser for that questionnaire.

Responses list newest-first, 25 per page, with columns for created date, external reference, status (Completed / In progress), pinned version, answered-question count, and uploaded-file count. Three filters:

  • Status — all, in progress, or completed.
  • Version — all, or one specific published version.
  • Reference — exact match on the external reference (apply with Enter).

Clicking a row expands it: the response id, the completion timestamp, and the full answers map as JSON — exactly what’s stored, keyed by question code. Uploaded files appear as their reference objects (fileId, fileName, size, contentType); the browser doesn’t currently offer a download link, but the management API’s download endpoint serves any file by id (see the management API reference).

The delete button on each row is data-subject erasure: it permanently removes the response document and deletes its uploaded files from storage (the file index keeps tombstone rows, which contain metadata only — name, size, type — not content). The confirmation requires typing DELETE:

This permanently deletes response r_… and its 2 uploaded file(s) from storage. There is no undo — erasure means erasure.

If a storage deletion fails mid-cascade, the response is still removed and the hourly cleanup job retries the stranded file.

Deleting a response also invalidates any browser session that was resuming it — the respondent’s next visit starts fresh.

Export CSV downloads the current filter set as a spreadsheet-ready file. The important design decision: an export covers one questionnaire version — the live version, unless you’ve set the version filter — because columns are derived from that version’s definition. Codes that existed in v1 but not v3 would otherwise produce a ragged, misleading sheet. Export per version and you always get coherent columns.

Details of the format:

  • Meta columns first: responseId, externalRef, status, versionNumber, createdAt, completedAt — then one column per question code, in the questionnaire’s reading order.
  • Address questions expand into per-sub-field columns (homeAddr.city, homeAddr.state, …) for their enabled sub-fields.
  • Checkbox selections and uploaded files join with ; (files export their names); toggles export true/false; numbers export bare.
  • UTF-8 with a byte-order mark, so Excel opens it with correct accents.
  • Formula-injection guarded: any text cell starting with =, +, - or @ is prefixed with ' so a malicious respondent’s answer can’t execute as a spreadsheet formula. Numeric answers are exempt — -5 stays a number.

A version with no responses exports a header-only file, which is a handy way to see the exact column layout before data arrives.

In-progress responses accumulate indefinitely — abandoned drafts are not auto-deleted (orphaned files are; the answer documents are not). Until a retention setting ships, a Mongo one-liner covers it — for example, deleting abandoned drafts older than 90 days:

Terminal window
docker compose exec mongo mongosh forms_engine --eval \
'db.responses.deleteMany({status:"IN_PROGRESS",updatedAt:{$lt:new Date(Date.now()-90*864e5)}})'