Response lifecycle
Start from a frozen definition, save drafts, and validate the same snapshot on submit.
startResponse copies publishedJson into response.definition. Every draft
write and submit check remains tied to that snapshot—not the current survey
draft and not a later publish.
A response is both the answer data and the SurveyJS definition needed to interpret it.
States and transitions
| Status | Meaning | Allowed next actions |
|---|---|---|
draft | Answers may be incomplete | Partial save, submit, or abandon |
submitted | The stored definition accepted the data | Reopen or idempotent resubmit |
abandoned | Closed without submission | Reopen |
| Operation | Effect |
|---|---|
startResponse | Create a draft or return the response selected by collection settings |
savePartial | Replace data on a draft |
submitResponse | Validate the snapshot and store the accepted data |
abandonResponse | Close a draft without changing definition or data |
reopenResponse | Move a submitted or abandoned response back to draft |
savePartial and abandonResponse throw RESPONSE_CLOSED unless the row is a
draft. reopenResponse throws RESPONSE_CLOSED on a draft, and when
settings.reopen is false.
Save a draft
Partial save replaces the entire data object; it is not a key patch.
With the default sanitizePartial: "clear", the server loads the stored
definition, assigns the posted data, runs clearIncorrectValues(true), and
persists the resulting survey.data. It does not run required-question
validation, so an incomplete draft remains valid.
"replace" stores the payload as sent.
Questions with choicesByUrl keep their posted value through the clear. The
server does not fetch that URL.
Submit against the snapshot
The default validateResult loads response.definition, removes values that
cannot be assigned, runs SurveyJS validate(), and returns the resulting
survey.data to persist. An invalid result throws VALIDATION_FAILED with the
failing question names in questions.
Replace validateResult for application-specific checks. It still receives the
stored definition. Return the object to persist, return nothing to keep the
input, or throw an APIError to reject the submit. See
Configuration.
A repeated submit of a row that is already submitted returns that row when
the cleaned payload matches stored data. Omitting data also returns the
stored row. A different payload fails with RESPONSE_CLOSED.
Lifecycle hooks
onStart runs only before a new insert; a resumed response skips both start
hooks. onSubmit runs after validation and before the submitted write.
Throwing from either on* callback aborts the write.
afterStart and afterSubmit run after the row has been stored. A failure
there leaves the write in place. An idempotent resubmit does not run submit
hooks again.
Handle concurrent writes
Partial save, submit, abandon, and reopen accept expectedUpdatedAt from the
last read. The comparison happens inside the write. A mismatch fails with
STALE_UPDATE.
useSurveyResponse sends the token it last read. On STALE_UPDATE it sets
stale and leaves the model mounted. Call reload to read the stored snapshot
again.
Omit the token only when a last-write-wins update is intentional.
Read response lists
listResponses defaults to a summary. Summary rows omit definition and
data. include: "full" returns the complete analytics snapshot and is only
available to the editor audience.
Both lists default to 50 rows and cap at 100. Results sort by updatedAt
descending. total ignores limit and offset.
submittedFrom and submittedTo are inclusive bounds on submittedAt. Rows
with no submit time are excluded. updatedAfter is an exclusive lower bound on
updatedAt.
Read full rows against definition on that row. Do not join them back to the
live survey document.