Validation
Require answers, run authored and host checks, and handle asynchronous validation.
Validation is a runtime gate, not browser-native form validation. The model checks every reachable question in scope, records actionable errors, and advances only when the gate passes.
Author required fields and validators
isRequired handles empty answers. Validators run only after a non-empty answer exists, so one omission produces one useful message instead of a stack of failures.
{
"checkErrorsMode": "onNextPage",
"questionErrorLocation": "top",
"pages": [{
"name": "contact",
"elements": [
{
"type": "text",
"name": "email",
"title": "Work email",
"isRequired": true,
"requiredErrorText": "Enter your work email.",
"validators": [
{ "type": "emailvalidator", "text": "Enter a valid email address." }
]
},
{
"type": "text",
"name": "seats",
"title": "Number of seats",
"inputType": "number",
"validators": [
{ "type": "numericvalidator", "minValue": 1, "maxValue": 500 }
]
}
]
}]
}Built-in validator types are numericvalidator, textvalidator,regexvalidator, emailvalidator, expressionvalidator, and answercountvalidator. Their optional text property replaces the built-in localized failure message.
Choose when errors appear
| Mode | Behavior |
|---|---|
| onNextPage | Default. Check the current page when the respondent moves forward. |
| onValueChanged | Recheck only the visible question whose answer changed. |
| onComplete | Allow intermediate moves, then check all reachable questions from the last page. |
Add application and server rules
Subscribe to onValidateQuestion for synchronous rules that need application knowledge. Install a ServerValidator for checks that need a round trip. Core performs no I/O itself.
import type {
ServerValidationError,
ServerValidator,
Survey,
} from '@kajay/core'
function isServerError(value: unknown): value is ServerValidationError {
return typeof value === 'object' && value !== null
&& typeof (value as Record<string, unknown>).questionName === 'string'
&& typeof (value as Record<string, unknown>).text === 'string'
}
export function configureValidation(survey: Survey, reservedNames: ReadonlySet<string>) {
const stop = survey.onValidateQuestion.add(({ question, value, addError }) => {
if (question.name === 'username' && reservedNames.has(String(value))) {
addError('That username is reserved.')
}
})
const validateOnServer: ServerValidator = async (request) => {
const response = await fetch('/api/survey/validate', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(request),
})
if (!response.ok) throw new Error('Validation service unavailable')
const payload: unknown = await response.json()
if (!Array.isArray(payload) || !payload.every(isServerError)) {
throw new Error('Validation service returned an invalid response')
}
return payload
}
survey.validation.setServerValidator(validateOnServer)
return () => {
stop()
survey.validation.setServerValidator(undefined)
}
}Validate the server’s JSON response before returning it. Each accepted error names a question and supplies respondent-facing text. A rejected promise is a service failure, not a bad answer, and is exposed separately as survey.validation.checkError.
Treat pending as its own state
survey.nextPageOrComplete() returns advanced, blocked, or pending. Synchronous failures return blocked without starting remote work. A pending check advances automatically if it settles cleanly.
Use survey.validation.isValidating or onValidatingChanged to show progress. Do not present pending as an answer error: no check has objected yet.
Run and clear checks programmatically
validateCurrentPage() checks the visible questions on the current page.validateAll() checks every reachable question, and clear() forgets recorded errors without checking again. Both validation methods are synchronous; out-of-process work runs only through the forward-navigation gate.