Skip to content
KajayDocs
SurveysTypeScript SDK

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.

contact-survey.jsonjson
{
  "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

Validation timing modes
ModeBehavior
onNextPageDefault. Check the current page when the respondent moves forward.
onValueChangedRecheck only the visible question whose answer changed.
onCompleteAllow 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.

configureValidation.tstypescript
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.