Skip to content

Expression Syntax

Expressions are strings used by properties such as visibleIf, enableIf, requiredIf, defaultValueExpression, setValueExpression, choicesVisibleIf, validators, and calculated values.

SurveyJS evaluates expressions while a respondent fills out the form, so conditions and calculated values update as referenced answers change.

Common expression-backed properties include:

  • Question, panel, and page logic: visibleIf, enableIf, requiredIf
  • Value automation: defaultValueExpression, setValueIf, setValueExpression, resetValueIf
  • Choice filtering: choicesVisibleIf, choicesEnableIf
  • Matrix and dynamic panel filtering: rowsVisibleIf, columnsVisibleIf, templateVisibleIf
  • Validation: expressionvalidator.expression
  • Calculations: root calculatedValues[].expression and expression question values
  • Root actions: triggers[].expression, triggers[].setToName, triggers[].setValue
  • Dynamic ranges: minValueExpression, maxValueExpression

Reference answers and calculated values with curly braces:

{
"visibleIf": "{hasPets} = true"
}

SurveyJS uses { and } as variable delimiters by default. Runtime code can customize this globally with settings.expressionVariableDelimiters, but Formspace-authored schema examples should continue to use the default brace syntax unless the host application has explicitly changed the setting.

Use quoted strings, numbers, booleans, and arrays directly in the expression:

{
"visibleIf": "{country} = 'AU' and {age} >= 18",
"requiredIf": "{contactMethods} anyof ['email', 'phone']"
}

Question names that contain hyphens or spaces still go inside braces:

{
"visibleIf": "{nps-score} >= 9"
}

Use dot and bracket notation when an expression needs values inside compound questions.

  • {choices[0]} references the first selected value in a checkbox, image picker, ranking, or similar multi-value question.
  • {contact.email} references an item named email inside a multipletext question named contact.
{
"visibleIf": "{selectedProducts[0]} notempty",
"requiredIf": "{contact.email} empty"
}

Inside matrix row expressions, use row-scoped placeholders:

  • {row.columnName} references a cell in the current row.
  • {prevRow.columnName} references the previous row.
  • {nextRow.columnName} references the next row.
  • {rowIndex} is the 1-based row position across all rows.
  • {visibleRowIndex} is the 1-based position across visible rows.
  • {rowName} references the row value.
  • {rowTitle} references the row display text.

From outside a matrix, reference rows and cells by matrix name:

  • {matrixName.rowName} references a row value in a single-select matrix.
  • {matrixName.rowName.columnName} references a cell in a multi-select matrix.
  • {matrixName[0].columnName} references a dynamic matrix row by zero-based index.
  • {matrixName[-1].columnName} references the last dynamic matrix row.
  • {matrixName-total.columnName} references a total row cell.
{
"rowsVisibleIf": "{row.quantity} > 0",
"visibleIf": "{lineItems[-1].amount} > 100"
}

Inside a dynamic panel template, use panel-scoped placeholders:

  • {panel.questionName} references a question in the current panel.
  • {prevPanel.questionName} references the previous panel.
  • {nextPanel.questionName} references the next panel.
  • {parentPanel.questionName} references a parent dynamic panel when panels are nested.

From outside a dynamic panel, reference a panel by index:

  • {dependents[0].name} references the first panel.
  • {dependents[-1].name} references the last panel.
{
"type": "paneldynamic",
"name": "jobs",
"templateElements": [
{ "type": "text", "name": "startDate", "inputType": "date" },
{
"type": "text",
"name": "endDate",
"inputType": "date",
"validators": [
{
"type": "expression",
"text": "End date must be after start date.",
"expression": "{panel.startDate} < {panel.endDate}"
}
]
}
]
}

SurveyJS can read element property values in expressions and dynamic text by prefixing the element reference with $, for example {$pageName.title} or {$panelName.visible}. Runtime code can customize this prefix with settings.expressionElementPropertyPrefix or disable the feature by setting it to an empty string.

Comparison operators are case-sensitive.

Purpose Operators Example
Empty empty, notempty {email} notempty
Equality =, ==, equal, !=, <>, notequal {country} = 'AU'
Comparison <, <=, >, >=, less, lessorequal, greater, greaterorequal {age} >= 18
Contains *=, contains, contain, notcontains, notcontain {email} contains '@'
Array match anyof, noneof, allof {roles} anyof ['admin', 'owner']
Logical !, negate, and, &&, or, `
Arithmetic +, -, *, /, %, ^, power {quantity} * {unitPrice}

Use parentheses when mixing logical groups:

{
"visibleIf": "({country} = 'AU' or {country} = 'NZ') and {age} >= 18"
}

Call functions directly in the expression. Do not wrap function names in braces.

  • iif(condition, valueIfTrue, valueIfFalse) returns one of two values.
{
"expression": "iif({score} >= 80, 'Pass', 'Review')"
}
  • currentDate() returns the current date and time.
  • currentYear() returns the current four-digit year.
  • today() returns the current date.
  • today(days) returns the current date shifted by a number of days.
  • getDate(value) converts a supported value to a date.
  • getYear(date) returns the four-digit year from a date.
  • age(date) returns age for a birthdate.
  • year(date), month(date), day(date), and weekday(date) extract date parts.
  • diffDays(start, end) returns the absolute number of calendar days between two dates.
  • dateDiff(start, end, unit) returns a difference in days, hours, minutes, seconds, months, or years.
  • dateAdd(date, amount, unit) adds or subtracts days, hours, minutes, seconds, months, or years.
{
"defaultValueExpression": "today(7)",
"validators": [
{
"type": "expression",
"text": "Trip cannot be longer than 14 days.",
"expression": "dateDiff({startDate}, {endDate}, 'days') <= 14"
}
]
}

Use basic aggregation for numeric values:

  • sum(value1, value2, ...)
  • min(value1, value2, ...)
  • max(value1, value2, ...)
  • avg(value1, value2, ...)
  • count(value1, value2, ...)
  • round(number, optionalPrecision)
  • trunc(number, optionalPrecision)

Use ...InArray functions for dynamic matrices and dynamic panels:

  • sumInArray(arrayQuestion, fieldName, optionalFilter)
  • minInArray(arrayQuestion, fieldName)
  • minInArray(arrayQuestion, fieldName, returnField)
  • minInArray(arrayQuestion, fieldName, optionalFilter)
  • minInArray(arrayQuestion, fieldName, returnField, optionalFilter)
  • maxInArray(arrayQuestion, fieldName)
  • maxInArray(arrayQuestion, fieldName, returnField)
  • maxInArray(arrayQuestion, fieldName, optionalFilter)
  • maxInArray(arrayQuestion, fieldName, returnField, optionalFilter)
  • avgInArray(arrayQuestion, fieldName, optionalFilter)
  • countInArray(arrayQuestion, fieldName, optionalFilter)

For minInArray and maxInArray, the optional third argument can now be either a return field or a filter condition. If you provide a return field and a filter, pass the filter as the fourth argument.

{
"calculatedValues": [
{
"name": "orderTotal",
"expression": "sumInArray({lineItems}, 'amount', {amount} > 0)"
}
]
}

Runtime performance note: expression questions recalculate on any survey value change by default. Application code can enable settings.expressionQuestionTrackDependencies = true to recalculate expression questions only when their dependencies change.

  • substring(text, start, optionalEnd) returns part of a string using zero-based indexes.
  • displayValue(questionName, optionalValue) returns a question’s display text, including choice labels instead of stored values.
  • getComment(questionName, optionalChoiceValue) returns an Other/comment value for a question or choice.
  • propertyValue(questionName, propertyName) returns a runtime property from a question.
  • isContainerReady(containerName, optionalPanelIndex) checks whether a page, panel, or dynamic panel instance passes validation.
  • isDisplayMode() returns true when the form is read-only or displaying a completed response.
{
"type": "comment",
"name": "details",
"title": "Tell us more",
"visibleIf": "{needsFollowUp} = true"
}
{
"type": "text",
"name": "email",
"title": "Email",
"inputType": "email",
"requiredIf": "{contactMethod} = 'email'"
}
{
"name": "total",
"expression": "{quantity} * {unitPrice}"
}
{
"type": "text",
"name": "dueDate",
"inputType": "date",
"defaultValueExpression": "today(7)"
}
{
"type": "radiogroup",
"name": "supportPlan",
"choices": [
"starter",
"team",
{ "value": "enterprise", "text": "Enterprise", "visibleIf": "{companySize} >= 100" }
]
}
{
"type": "expression",
"name": "totalDisplay",
"title": "Total",
"expression": "sumInArray({lineItems}, 'amount')"
}
  • Prefer stable, simple question names because expressions reference name values directly.
  • Quote string literals, but do not quote numbers or booleans.
  • Use notempty before comparing optional values when blank input should not pass the rule.
  • Use anyof, noneof, and allof for checkbox and tagbox answers because those values are arrays.
  • Use panel and row scoped placeholders only in properties that evaluate inside that context.
  • Prefer a calculated value when several expressions reuse the same derived result.