# Validation {#validation}


Validation decides whether an **entered answer** is acceptable. It runs after
the respondent answers, and it can compare the answer to fixed values, to other
questions in the survey, or to the results of functions.

Validation is **not** the same as:

- **Required** — "the question must be answered at all". Required is a separate
  property on the question, not a validation expression.
- **Skip Logic** — "the question is not shown". See [Skip Logic](/user-manual/skip-logic.md).
- **Default Value / Calculation** — "the answer is pre-filled or derived". See
  [Default Values](/user-manual/default-values.md) and
  [Auto-Populate and Calculations](/user-manual/calculation-expressions.md).

Example: "Age must be at least 18":

```text
${age} >= 18
```

---


## Where to configure it {#where-to-configure-it}


1. Open your survey in the **Survey Designer**.
2. Select the question in the canvas.
3. In the right-hand **Properties** panel, scroll to the **Advanced** section.
4. Click **Validation Rules**.

The **Validation Rules** dialog gives you two ways to work:

- **Visual rules** — pick a rule type (Required, Minimum Length, Maximum Value,
  Pattern, and more), enter a value, and add a per-rule message. Rules are
  combined with **AND** (all must pass) or **OR** (any may pass) by clicking
  the connector badge between rules.
- **Custom Expression** — the "Custom Expression" rule type accepts any valid
  expression, including cross-question and function-based constraints.

A **Generated Constraint Expression** preview updates as you build rules, and
the expression is validated against the server when you save. You can also set
a **General Error Message** that is shown when any validation fails; if it is
left blank, the individual rule messages are used.

Note: **note** and **calculate** question types cannot have validation rules
(the designer disables the save button for them).

---


## Comparison operators {#comparison-operators}


| Operator | Meaning | Example | Passes when |
|---|---|---|---|
| `=` | equals | `= 'female'` | the value equals `female` |
| `!=` | not equals | `!= 0` | the value is not `0` |
| `<` | less than | `< 18` | the value is under 18 |
| `<=` | less than or equal | `<= 65` | the value is 65 or less |
| `>` | greater than | `> 18` | the value is over 18 |
| `>=` | greater than or equal | `>= 18` | the value is 18 or more |

`=` is the equality operator. The web and mobile engines also accept `==`
as an alias, but `=` is the canonical spelling — use it everywhere. There is
**no `<>` operator** — use `!=`.

The dot `.` refers to the current question's value:

```text
. >= 18
```

is the same as "this answer is at least 18". The visual rules generate `.`-based
expressions, and the **Constraint Explainer** (the `?` icon next to the
question) can translate them back into plain-language descriptions.


## Logical operators {#logical-operators}


| Operator | Meaning | Example |
|---|---|---|
| `and` | both must be true | `. >= 18 and . <= 65` |
| `or` | at least one true | `. = 0 or . = 1` |
| `not(...)` | negates the result | `not(. = 0)` |

Parentheses group conditions:

```text
(. = 'yes' or . = 'maybe') and . != 'no'
```

---


## Examples {#examples}



### Numeric range {#numeric-range}


Age between 18 and 65.

| | |
|---|---|
| Business requirement | Only accept respondents aged 18–65 |
| Expression | `. >= 18 and . <= 65` |
| Passes when | the answer is between 18 and 65 inclusive |
| Fails when | the answer is below 18 or above 65 |
| Suggested message | "Age must be between 18 and 65" |


### Date range {#date-range}


Visit date cannot occur before the registration date.

| | |
|---|---|
| Business requirement | A follow-up visit must not be dated before enrolment |
| Expression | `${visit_date} >= ${registration_date}` |
| Passes when | the visit date is on or after the registration date |
| Fails when | the visit date is earlier than the registration date |
| Suggested message | "Visit date cannot be before registration date" |


### Date not in the future {#date-not-in-the-future}


| | |
|---|---|
| Expression | `. <= today()` |
| Passes when | the date is today or earlier |
| Fails when | the date is in the future |
| Suggested message | "Date cannot be in the future" |


### Text length {#text-length}


| | |
|---|---|
| Business requirement | A code must be between 3 and 10 characters |
| Expression | `string-length(.) >= 3 and string-length(.) <= 10` |
| Passes when | the answer is 3–10 characters long |
| Fails when | the answer is shorter than 3 or longer than 10 |
| Suggested message | "Code must be 3 to 10 characters" |

The visual builder's **Minimum Length** and **Maximum Length** rules generate
these expressions for you.


### Regex / pattern {#regex--pattern}


| | |
|---|---|
| Business requirement | Phone number must be exactly 10 digits |
| Expression | `regex(., '^[0-9]{10}$')` |
| Passes when | the answer is exactly 10 digits |
| Fails when | the answer contains anything else |
| Suggested message | "Enter a 10-digit phone number" |

The designer ships with built-in regex shortcuts — **Email Format**, **10-Digit
Mobile**, **Numbers Only**, **Money (1 decimal)**, **Letters Only**, and
**Letters & Numbers** — that expand to the full `regex(., '...')` expression
automatically.


### Selection validation (minimum selections) {#selection-validation-minimum-selections}


| | |
|---|---|
| Business requirement | At least two symptoms must be selected |
| Expression | `count-selected(${symptoms}) >= 2` |
| Passes when | two or more options in `symptoms` are selected |
| Fails when | fewer than two options are selected |
| Suggested message | "Select at least two symptoms" |

`count-selected()` counts the selected options of a select-multiple question.
`selected(${symptoms}, 'fever')` checks whether one specific option is chosen.


### Conditional validation {#conditional-validation}


| | |
|---|---|
| Business requirement | If the respondent is pregnant, the LMP (last menstrual period) date must be supplied |
| Expression | `if(${pregnant} = 'yes', . != '', .)` |
| Passes when | not pregnant, OR pregnant and a date was entered |
| Fails when | pregnant and no date was entered |
| Suggested message | "Please provide the LMP date" |

`if(condition, thenValue, elseValue)` evaluates the `then` value when the
condition is true and the `else` value otherwise. Here, when the respondent is
not pregnant the constraint is `.` — a bare non-empty value check — so it
passes.


### Cross-question validation {#cross-question-validation}


| | |
|---|---|
| Business requirement | End date must be after the start date |
| Expression | `${end_date} > ${start_date}` |
| Passes when | the end date is strictly after the start date |
| Fails when | the end date is on or before the start date |
| Suggested message | "End date must be after start date" |


### BMI validation {#bmi-validation}


If a calculated BMI question exists, you can validate the *input* height and
weight directly, or validate the computed result:

| | |
|---|---|
| Expression (on weight) | `. > 0 and . < 300` |
| Expression (on height) | `. > 0 and . < 250` |
| Expression (on a calculated BMI) | `bmi(${weight_kg}, ${height_cm}) >= 16 and bmi(${weight_kg}, ${height_cm}) <= 40` |

Note that `bmi()` expects height in **centimetres** — see
[Auto-Populate and Calculations](/user-manual/calculation-expressions.md#bmi).


### Required only under a condition {#required-only-under-a-condition}


MQUAD's `required` flag applies unconditionally. To make a field required only
when some condition holds, use `if()` as above:

```text
if(${has_children} = 'yes', . != '', .)
```

---


## When validation is evaluated {#when-validation-is-evaluated}


- **Web forms**: simple constraints (comparisons like `. >= 18`, `${age} >= 18`,
  and `selected()` / `and` / `or` / `not()` logic) are checked as you answer.
  Function-based constraints — `string-length(.)`, `regex(., ...)`,
  `count-selected(...)`, date functions — are evaluated by the **server at
  submission** (they are not evaluated by the web client's inline checker).
- **Mobile field app**: the full expression engine evaluates every constraint
  locally on each answer change and again at submission.
- **Form submission** (both platforms): the server re-validates every
  constraint with the same expression engine, so a function-based constraint
  always fails submission on every platform even if the inline checker could
  not evaluate it.

Validation is skipped when the question has **no answer** — an empty value only
fails if the question is **Required**. That means an empty answer does not fail
`count-selected(${symptoms}) >= 2`; it fails only because the field is marked
Required. Use `if()` to make a field required conditionally (see above).

---


## Common mistakes {#common-mistakes}


- **Using `<>` for not-equals** — MQUAD uses `!=`; `<>` is not supported.
- **Forgetting the quotes** — string values need quotes: `= 'female'`, not
  `= female`.
- **Comparing a number to text** — `. >= '18'` compares text, not numbers.
  Write `. >= 18`.
- **Putting the expression in the "Error Message" field** — the message is
  plain text shown on failure, not an expression.
- **Using `#` references** — `${name}` is the correct reference style in
  constraints; `#name` is only for default values.
