FlexiSurvey has three places where you write logic instead of clicking it together: conditional logic rules that show, hide, and require questions while a survey is being taken; the formula language behind computed variables and panel variable transforms; and indicator formulas that reduce a whole response set to an M&E indicator value. This page is the complete reference for all three. Everything here describes the engine as it runs today; the same evaluation semantics apply on the web taker, the respondent portal, the embedded widget, the mobile apps, and server-side validation.
Conditional logic rules
A rule has four parts: a trigger, a condition group, one or more actions, and a priority. When a rule's trigger applies and its conditions match the answers given so far, its actions run. Disabled rules are ignored entirely.
Triggers
| Trigger | When the rule is considered |
|---|---|
| On answer | Every time an answer changes (the default; always evaluated) |
| On page load | Every evaluation pass (same behaviour as "on answer") |
| On survey start | Only while no answers have been given yet |
| On survey end | Only once every question has an answer |
Condition operators
Each condition compares one question's answer against a fixed value. Twelve operators are available:
| Operator | Matches when | Notes |
|---|---|---|
| equals | the answer equals the value | compared as text, so 5 equals "5" |
| not equals | the answer differs from the value | |
| contains | the answer contains the value as a substring | on multi-choice answers, matches if any selected option contains it |
| not contains | the answer does not contain the value | |
| greater than | the answer is numerically greater | |
| less than | the answer is numerically smaller | |
| greater than or equal | numeric ≥ | |
| less than or equal | numeric ≤ | |
| is empty | the question is unanswered, blank, or has no selections | |
| is not empty | the question has any answer | |
| in | the answer equals one of a list of values | |
| not in | the answer equals none of the listed values |
An unknown or malformed operator never matches (the engine fails closed rather than guessing).
Combining conditions
Conditions sit inside a group with one of four combinators, and groups nest to any depth:
- AND: every condition (or nested group) must match.
- OR: at least one must match.
- NOT: matches when the AND of its members does not.
- XOR: exactly one member matches.
A group with no conditions always matches, which lets you author an unconditional rule.
Actions
| Action | Effect |
|---|---|
| Show question | Makes the target question visible |
| Hide question | Hides the target question |
| Skip to question | Jumps the taker forward to the target question |
| End survey | Finishes the survey at that point |
| Set required | Makes the target question required for this respondent |
Precedence and interactions
Rules evaluate in priority order, highest first. Once a higher-priority rule has decided a question's visibility, a lower-priority show or hide on the same question is ignored. Two behaviours are worth knowing because they prevent whole classes of field problems:
- A required question that logic has hidden is not enforced at submit. Respondents are never blocked by a question they were never shown, on any channel.
- Inside repeat groups, rules evaluate per instance. Each repeat instance's own answers drive visibility within that instance, so "if this household member is under 5, show the vaccination question" behaves correctly for each member independently.
The formula language
Computed variables (named values derived from a response's answers) and panel variable transforms share one expression language. It is evaluated by a purpose-built parser with no access to anything outside the expression: no code execution, no network, no other responses.
Referencing answers
Inside a formula, a question's answer is available as the token q_<questionId> (the builder inserts these tokens for you, so you rarely type them by hand). Computed variables can also reference other variables by their name. When a question lives inside a repeat group, its token holds the list of that question's answers across all instances, which is exactly what the aggregate functions expect: sum(q_income) totals income across all household members.
Operators
In order of binding strength, tightest first:
| Operators | Meaning |
|---|---|
^ |
exponentiation |
* / % |
multiply, divide, modulo |
+ - |
add, subtract (unary minus is supported) |
< > <= >= |
comparison |
== != |
equality |
&& |
logical AND |
|| |
logical OR |
Parentheses group as usual. Strings are written in quotes; function names are case-insensitive.
Functions
Math
| Function | Returns |
|---|---|
abs(x) |
absolute value |
ceil(x) / floor(x) |
round up / down |
round(x) or round(x, decimals) |
rounded value |
min(a, b, …) / max(a, b, …) |
smallest / largest argument |
sqrt(x) |
square root |
pow(base, exp) |
power |
log(x) / log10(x) / exp(x) |
natural log, base-10 log, e^x |
Aggregates (accept lists, e.g. a repeated question's token, or several arguments)
| Function | Returns |
|---|---|
sum(…) |
total |
avg(…) |
mean of the numeric entries; blank entries are skipped; empty input returns nothing rather than 0 |
count(…) |
how many entries are non-empty |
median(…) |
median of the numeric entries |
stdev(…) |
population standard deviation; needs at least two numeric entries |
Text
| Function | Returns |
|---|---|
len(s) |
length |
upper(s) / lower(s) / trim(s) |
case changes, whitespace strip |
concat(a, b, …) |
joined text |
substr(s, start) or substr(s, start, length) |
a slice |
Conditional
| Function | Returns |
|---|---|
if(condition, whenTrue, whenFalse) |
one of two values |
coalesce(a, b, …) |
the first non-empty argument |
isBlank(v) |
whether the value is missing or whitespace |
isNumber(v) |
whether the value is a real number (a blank is not a number) |
Date
| Function | Returns |
|---|---|
now() / today() |
current timestamp / current date |
year(d) / month(d) / day(d) |
calendar parts |
daysBetween(d1, d2) |
whole days between two dates |
Missing-data semantics
The language treats blanks honestly, because a blank silently becoming a zero is how field data goes wrong:
- A blank or whitespace-only answer is missing, not
0.avg,median, andstdevskip missing entries;countdoes not count them;isNumberreturns false for them. - An aggregate over nothing returns no value rather than
0. - If an expression fails to evaluate (bad input, division problems, a malformed reference), the variable's value is empty for that response. A formula error never blocks a respondent from submitting.
Indicator formulas (MEAL)
Indicator formulas are a separate, aggregate language: where a computed variable produces one value per response, an indicator formula reduces the whole response set to one number, with the survey answers exposed through the variables you map in the indicator binding (written with a $ prefix, e.g. $water_access).
Aggregate functions iterate the response set; their arguments are per-response expressions:
| Function | Returns |
|---|---|
count() / count(predicate) |
responses, or responses matching the predicate |
pct(predicate) |
percentage of responses matching |
pctOf(predicate, predicate) |
percentage with an explicit denominator |
sum(expr) / avg(expr) / median(expr) / stddev(expr) |
numeric aggregates over a per-response expression |
distinct(expr) |
count of distinct values |
min(v) / max(v) |
smallest / largest value of a bound variable |
Inside a predicate or value expression you can use abs, round, floor, ceil, sqrt, pow, min, max, if, coalesce, between, and in, plus the same operators as the formula language.
Three guarantees the indicator engine makes:
- Division by zero yields "no data", never infinity: a
pctwith an empty denominator reports no value instead of a misleading number. - Missing answers are skipped in numeric aggregates, never counted as zero.
- Results are computed to 6 decimal places and each stored result records the engine version that produced it, so a historical value can always be traced to the semantics that computed it.
Indicator formulas are validated when you save a binding, including a check that every $variable you reference is actually mapped, and the binding editor's live test runs your formula against real responses before anything is saved.
Where to go next
- Question-level validation (ranges, formats) is configured per question in the builder and documented in the survey building guide.
- Platform ceilings that affect logic-heavy instruments (questions per survey, options per question) are on the Technical Limits page.