S
Documentation

Validation

Define validation rules on each question to ensure respondents provide valid answers. Validation runs on page navigation and final submission, with 10 built-in rule types and unlimited custom validators.

ValidationRule Interface

Typestypescript
interface ValidationRule {
  type: ValidationRuleType;
  value?: string | number | RegExp;
  message?: string;
  /** Custom validator function — only used when type is 'custom' */
  validator?: (value: AnswerValue, question: Question, answers: SurveyAnswers) => boolean | string;
}

type ValidationRuleType =
  | 'required'
  | 'minLength'
  | 'maxLength'
  | 'pattern'
  | 'min'
  | 'max'
  | 'email'
  | 'url'
  | 'phone'
  | 'time'
  | 'custom';

interface ValidationError {
  questionId: string;
  message: string;
  rule: ValidationRuleType;
}

All Built-in Rules

Rulevalue typeDescriptionApplies to
requiredField must have a non-empty valueAll types
minLengthnumberMinimum character lengthtext, textarea, password, email, url, phone
maxLengthnumberMaximum character lengthtext, textarea, password, email, url, phone
patternstring | RegExpValue must match the regex patterntext, textarea, email, url, phone
minnumberMinimum numeric valuenumber, slider, rating
maxnumberMaximum numeric valuenumber, slider, rating
emailValue must be a valid e-mail addressemail, text
urlValue must be a valid http / https URLurl, text
phoneValue must be a valid phone numberphone, text
timeValue must be a valid HH:MM or HH:MM:SS timetime, text
customRun any validator function you provideAll types

required

Fails when the answer is empty, null, undefined, an empty string, or an empty array. For boolean questions rendered as a checkbox, required means the box must be checked (value === true).

The top-level required: true shorthand is identical to adding { type: 'required' } inside the validation array.

requiredtypescript
{
  id: 'name',
  type: 'text',
  label: 'Full name',
  required: true,          // ← shorthand (preferred)
  // or equivalently:
  // validation: [{ type: 'required', message: 'Please enter your name' }],
}
Default error: "[label] is required"

minLength

Checks String(value).length >= value. Only runs when the field is non-empty.

minLengthtypescript
{
  id: 'bio',
  type: 'textarea',
  label: 'Short bio',
  validation: [
    { type: 'minLength', value: 20, message: 'Please write at least 20 characters' },
  ],
}
Default error: "[label] must be at least [value] characters"

maxLength

Checks String(value).length <= value.

maxLengthtypescript
{
  id: 'username',
  type: 'text',
  label: 'Username',
  validation: [
    { type: 'minLength', value: 3,  message: 'At least 3 characters' },
    { type: 'maxLength', value: 20, message: 'At most 20 characters' },
  ],
}
Default error: "[label] must be at most [value] characters"

pattern

Tests the value against a regular expression. Supply the pattern as a string (wrapped in new RegExp(...)) or as an actual RegExp object.

patterntypescript
// String pattern
{
  id: 'username',
  type: 'text',
  label: 'Username',
  validation: [
    {
      type: 'pattern',
      value: '^[a-zA-Z0-9_]+$',
      message: 'Only letters, numbers, and underscores allowed',
    },
  ],
}

// RegExp object
{
  id: 'zip',
  type: 'text',
  label: 'ZIP code',
  validation: [
    { type: 'pattern', value: /^d{5}(-d{4})?$/, message: 'Enter a valid ZIP code' },
  ],
}
Default error: "[label] does not match the required pattern"

min

Checks Number(value) >= rule.value. Useful for number, slider, and rating questions.

mintypescript
{
  id: 'age',
  type: 'number',
  label: 'Age',
  required: true,
  validation: [
    { type: 'min', value: 18, message: 'You must be at least 18 years old' },
  ],
}
Default error: "[label] must be at least [value]"

max

Checks Number(value) <= rule.value.

maxtypescript
{
  id: 'quantity',
  type: 'number',
  label: 'Quantity',
  required: true,
  validation: [
    { type: 'min', value: 1,   message: 'Minimum order is 1'   },
    { type: 'max', value: 500, message: 'Maximum order is 500' },
  ],
}
Default error: "[label] must be at most [value]"

email

Tests the value against /^[^\s@]+@[^\s@]+\.[^\s@]+$/. The email question type applies this automatically, but you can add it explicitly to any text field too.

emailtypescript
{
  id: 'contact',
  type: 'email',
  label: 'Contact email',
  required: true,
  validation: [
    { type: 'email', message: 'Please enter a valid email address' },
  ],
}
Default error: "[label] must be a valid email"

url

Parses the value with new URL() and requires the protocol to be http: or https:. The url question type applies this automatically.

urltypescript
{
  id: 'website',
  type: 'url',
  label: 'Your website',
  validation: [
    { type: 'url', message: 'Must be a valid URL starting with http:// or https://' },
  ],
}
Default error: "[label] must be a valid URL"

phone

Tests against /^\+?[\d\s\-().]{7,20}$/. Accepts common formats such as +1 800 555 0199, (123) 456-7890, and +91 98765 43210. The phone question type applies this automatically.

phonetypescript
{
  id: 'mobile',
  type: 'phone',
  label: 'Mobile number',
  required: true,
  validation: [
    { type: 'phone', message: 'Enter a valid phone number (e.g. +1 800 555 0199)' },
  ],
}
Default error: "[label] must be a valid phone number"

time

Tests against /^([01]\d|2[0-3]):([0-5]\d)(:[0-5]\d)?$/ — i.e. 24-hour HH:MM or HH:MM:SS. The time question type applies this automatically.

timetypescript
{
  id: 'meeting-time',
  type: 'time',
  label: 'Preferred meeting time',
  required: true,
  validation: [
    { type: 'time', message: 'Enter a valid time in HH:MM format' },
  ],
}
Default error: "[label] must be a valid time (HH:MM)"

custom

Runs a validator function you supply. The function receives the current answer value and the full answers map, and must return:

  • true — valid, no error.
  • false — invalid; uses rule.message or a generic fallback.
  • A string — invalid; that string becomes the error message (overrides rule.message).
TypeScript / JS schemas only. The validator field holds a function, which is not valid JSON. If your schema is stored as JSON (e.g. loaded from an API or database), use the validators option approach below instead.

Inline validator (TypeScript / JS schema)

custom — inline (TypeScript only)typescript
// schema defined in .ts / .tsx — functions are fine here
const schema: SurveySchema = {
  id: 'signup',
  pages: [{
    id: 'p1',
    questions: [
      {
        id: 'password',
        type: 'password',
        label: 'Password',
        required: true,
        validation: [
          { type: 'minLength', value: 8, message: 'At least 8 characters required' },
          {
            type: 'custom',
            validator: (value) => {
              const str = String(value);
              if (!/[0-9]/.test(str))        return 'Must contain at least one number';
              if (!/[A-Z]/.test(str))        return 'Must contain at least one uppercase letter';
              if (!/[^a-zA-Z0-9]/.test(str)) return 'Must contain at least one special character';
              return true;
            },
          },
        ],
      },
      {
        id: 'confirm-password',
        type: 'password',
        label: 'Confirm password',
        required: true,
        validation: [
          {
            type: 'custom',
            message: 'Passwords do not match',
            validator: (value, _question, answers) => value === answers['password'] || 'Passwords do not match',
          },
        ],
      },
    ],
  }],
};

validators option (works with JSON schemas too)

Pass a validators map to useSurvey or SurveyRenderer. Each key is a question id; the value is the validator function. The schema itself stays pure JSON — just mark the question with { type: 'custom' } and a message.

custom — validators option (JSON-compatible)tsx
// schema can be plain JSON — no functions needed
const schema = {
  id: 'signup',
  pages: [{
    id: 'p1',
    questions: [
      {
        id: 'password',
        type: 'password',
        label: 'Password',
        required: true,
        validation: [
          { type: 'minLength', value: 8, message: 'At least 8 characters required' },
          { type: 'custom',    message: 'Must contain a number, uppercase letter, and special character' },
        ],
      },
      {
        id: 'confirm-password',
        type: 'password',
        label: 'Confirm password',
        required: true,
        validation: [
          { type: 'custom', message: 'Passwords do not match' },
        ],
      },
    ],
  }],
};

// Validator logic lives here, outside the schema
<SurveyRenderer
  schema={schema}
  options={{
    validators: {
      password: (value) => {
        const str = String(value);
        if (!/[0-9]/.test(str))        return 'Must contain at least one number';
        if (!/[A-Z]/.test(str))        return 'Must contain at least one uppercase letter';
        if (!/[^a-zA-Z0-9]/.test(str)) return 'Must contain at least one special character';
        return null;
      },
      'confirm-password': (value, _question, answers) =>
        value === answers['password'] ? null : 'Passwords do not match',
    },
    onSubmit: async (answers) => console.log(answers),
  }}
/>
Default error (when returning false): "[label] is invalid"

Per-Page Validation & Programmatic Access

By default, the library validates the current page when the user navigates forward. You can also trigger validation programmatically:

Programmatic Validationtypescript
const { validate, validateCurrentPage, errors, getError } = useSurvey(schema);

// Validate just the current page (returns true if no errors)
const isPageValid = validateCurrentPage();

// Validate the entire survey across all pages
const isAllValid = validate();

// errors is a flat array of ValidationError objects:
// [{ questionId: 'name', message: 'Full name is required', rule: 'required' }]

// getError returns the first error message for a question ID:
const nameError = getError('name'); // string | undefined

Schema-level Settings

Control when validation fires via the settings object on your schema:

settings.validateOnPageChangetypescript
const schema: SurveySchema = {
  id: 'my-survey',
  pages: [...],
  settings: {
    validateOnPageChange: true,  // validate current page before advancing (default: true)
  },
};

Tips

  • — Hidden questions (via visibleIf) are automatically skipped during validation.
  • — If a field is empty and not required, all other rules (length, pattern, format, etc.) are skipped.
  • — Rules run in array order; the first failure adds an error but the rest still execute, so multiple errors can surface at once.
  • email, url, phone, and time question types already wire up their respective format rules internally — you only need to add them explicitly if you want a custom error message.
  • custom validators with an inline validator function require a TypeScript/JS schema — functions are not valid JSON. For JSON schemas, use the validators option on useSurvey / SurveyRenderer instead.
  • — Validation errors are cleared automatically when the user changes their answer.