Skip to main content

Scripting

Overview​

Scripting lets you compute a final value for a Profile question using code instead of a manual selection.

Use this when you need advanced logic (for example: score computation, segmentation, status labels, or dynamic classification based on previous answers).

Prerequisites​

To use scripting, configure your question with all of the following:

  • Question type: Profile question
  • Mode: Automatic
  • Automatic type: Scripting (Compute a value with a script)

When this setup is enabled, a dedicated text area appears (editor-like input) where you can write the script that calculates the respondent's final value.

important

Scripts run in a secure sandbox with no access to the file system, network, or Java classes, and are limited to 500ms of execution time. Keep scripts short and avoid repeated calculations to stay well under this limit.

Referencing previous answers​

Variables follow a structured naming pattern based on the question type:

[Question ref].[Element ref].[Property]

Profile / adaptive questions​

VariableDescriptionExample value
Q1.ANSWERFull answer(s), comma separated"Yes" or "Option A,Option B"
Q1.COUNTNumber of selected answers1 or 3
Q1.A001, Q1.A002, ...Nth selected answer (1-based index)"Option A"

Battery of items​

I1, I2, ... refer to the item refs in the battery.

VariableDescriptionExample value
Q2.I1.ANSWERAnswer(s) for item I1"Satisfied"
Q2.I1.COUNTNumber of answers for item I11
Q2.I1.A001First answer of item I1"Satisfied"

Hotspot​

Z1, Z2, ... refer to the zone refs in the hotspot.

VariableDescriptionExample value
Q3.Z1.ANSWERAnswer for zone Z1"Selected"
Q3.Z1.COUNTNumber of answers for zone Z11 or 0

Open questions​

F1, F2, ... refer to the field refs in the open question.

VariableDescriptionExample value
Q4.F1.ANSWERAnswer of field F1"John Smith"
Q4.F2.ANSWERAnswer of field F2"john@email.com"

R3M questions (3 words / Experience)​

VariableDescriptionExample value
Q5.WORD1.ANSWERFirst word"Innovation"
Q5.WORD2.ANSWERSecond word"Quality"
Q5.WORD3.ANSWERThird word"Service"

Possible answers​

Besides the answers given by the respondent, you can read the list of possible answers defined on a question, battery, or hotspot, i.e. the answer choices shown to the respondent. The format is always:

[Question ref].[Answer ref]

There is no item or zone level in this notation: the possible answers are shared by the whole question, so you write Q2.A1 for a battery and Q3.A1 for a hotspot, never Q2.I1.A1 or Q3.Z1.A1.

For example, with the following question:

  • Q1: "Do you like Coca-Cola?"
    • A1: Yes
    • A2: No
VariableDescriptionExample value
Q1.A1Text of the possible answer A1 of question Q1"Yes"
Q1.A2Text of the possible answer A2 of question Q1"No"
Q12.A4Text of the possible answer A4 of question Q12-
Q1.A1                                     // → "Yes"
Q1.A2 // → "No"

// Check whether the respondent picked "Yes", without hard-coding the label
contains(split(getAnswer('Q1')), Q1.A1) // → true if A1 was selected
warning

Do not confuse Q1.A1 with Q1.A001. Q1.A1 uses the ref of a possible answer (A1) and always returns its text, whether or not the respondent selected it. Q1.A001 uses a zero-padded index (001) and returns the first answer the respondent actually selected.

Loop iterations​

When a question is repeated inside a loop, each iteration exposes its own set of variables using the LOOPn notation (1-based, consistent with the replacement variable numbering):

Q1.LOOP1.ANSWER  // first iteration
Q1.LOOP2.ANSWER // second iteration
Q1.LOOP3.ANSWER // third iteration

When the script itself runs inside a loop, use the CURRENT_LOOP_INDEX variable to get the number of the current iteration (1-based, like the LOOPn notation above):

var currentIteration = CURRENT_LOOP_INDEX;
// CURRENT_LOOP_INDEX is an int, convert to text
var currentIteration = text(CURRENT_LOOP_INDEX);
tip

CURRENT_LOOP_INDEX is only available when the Profile question is itself inside a loop.

Data-access functions​

Instead of reading raw variables, use the dedicated access functions below. They are recommended: they are more readable, and they resolve missing answers or invalid refs to null (or 0 for counts) instead of failing.

Question typeFunctionEquivalent to
Profile / adaptivegetAnswer(questionRef)Q1.ANSWER
Profile / adaptivegetAnswerOrDefault(questionRef, default)Q1.ANSWER, or default if unanswered
Profile / adaptivegetAnswer(questionRef, index)Q1.A001
Profile / adaptivegetAnswerCount(questionRef)Q1.COUNT
BatterygetItemAnswer(questionRef, itemRef)Q2.I1.ANSWER
BatterygetItemAnswer(questionRef, itemRef, index)Q2.I1.A001
BatterygetItemAnswerCount(questionRef, itemRef)Q2.I1.COUNT
HotspotgetZoneAnswer(questionRef, zoneRef)Q3.Z1.ANSWER
HotspotgetZoneAnswer(questionRef, zoneRef, index)-
HotspotgetZoneAnswerCount(questionRef, zoneRef)Q3.Z1.COUNT
Open questiongetFieldAnswer(questionRef, fieldRef)Q4.F1.ANSWER
Open questiongetFieldAnswer(questionRef, fieldIndex)Q4.F1.ANSWER (by position)
R3M (3 words)getWord1(questionRef) / getWord2(...) / getWord3(...)Q5.WORD1.ANSWER, etc.
R3M (3 words)getWord(questionRef, wordRef)Q5.WORD1.ANSWER
getAnswer('Q1')                    // → "Yes"
getAnswerOrDefault('Q1', 'N/A') // → "Yes", or "N/A" if Q1 has no answer
getAnswer('Q1', 1) // → "Option A"
getAnswerCount('Q1') // → 3

getItemAnswer('Q2', 'I1') // → "Satisfied"
getItemAnswerCount('Q2', 'I1') // → 2

getZoneAnswer('Q3', 'Z1') // → "Selected"
getZoneAnswerCount('Q3', 'Z1') // → 1

getFieldAnswer('Q4', 'F1') // → "John Smith"
getFieldAnswer('Q4', 1) // → "John Smith"

getWord1('Q5') // → "Innovation"
getWord('Q5', 'WORD2') // → "Quality"
tip

An invalid or out-of-range ref (for example getItemAnswer('Q2', 'I999')) returns null rather than raising an error. Always guard against null before using a value in a comparison or calculation.

Checking for an answer​

Use these functions to check whether an answer exists before reading it, as an alternative to comparing the result of getAnswer(...) (and the other access functions) to null.

FunctionDescription
hasAnswered(ref)Returns true if ref has an answer, i.e. if the variable ref.ANSWER exists
exists(ref)Returns true if the exact variable ref exists in the response data
hasAnswered('Q1')        // → true if Q1 has been answered
hasAnswered('Q2.I1') // → true if item I1 of battery Q2 has been answered
hasAnswered('Q3.Z1') // → true if zone Z1 of hotspot Q3 has been answered

exists('Q1.ANSWER') // → same as hasAnswered('Q1')
exists('Q1.COUNT') // → true if the Q1 answer count variable exists
exists('Q1.A002') // → true if a second answer was selected for Q1
if (hasAnswered('Q1')) {
getAnswer('Q1');
} else {
'N/A';
}
tip

hasAnswered(ref) is a shortcut for exists(ref + '.ANSWER') and accepts any question, item (Q2.I1), zone (Q3.Z1), or field (Q4.F1) ref. exists(ref) is more generic: it checks any of the raw variables described in "Referencing previous answers" above (for example exists('Q1.A002') to check whether a second answer was selected).

warning

hasAnswer(ref) still works but is deprecated — use hasAnswered(ref) instead.

URL query parameters​

Use getQueryParameter(name) to read a parameter from the survey link's query string (for example a language code, a panel provider ID, or a tracking parameter appended to the invitation URL).

FunctionDescriptionEquivalent to
getQueryParameter(name)Returns the value of the URL query parameter name, or null if it is not presentPARAM.<NAME> (<NAME> upper-cased), e.g. PARAM.SOURCE for getQueryParameter('source')
var lang = getQueryParameter('lang');
// For a survey link such as https://.../survey?lang=en → "en"
var lang = getQueryParameter('lang');

if (lang == 'en') {
'English';
} else if (lang == 'fr') {
'Français';
} else {
'N/A';
}
tip

When the parameter is absent from the URL, getQueryParameter returns null. Always guard against null before using the value in a comparison or concatenation.

Form variables​

Variables defined at the global level of the form (outside of any question) are accessible directly by their name, or through the dedicated getFormVariable(index) function.

FunctionDescriptionEquivalent to
getFormVariable(index)Returns the value of the form variable numbered indexVAR_1, VAR_2, ...
var product = VAR_1;
// equivalent, using the access function:
var product = getFormVariable(1);

JexlScript syntax basics​

Scripting is based on JexlScript. If you are familiar with JavaScript-like control flow, you will quickly recognize the syntax.

Key syntax rules:

  • End each instruction with ;
  • Use var to declare variables
  • Use if (...) { ... } for conditions
  • Use else { ... } for alternative logic
  • Use ==, !=, >, <, >=, <=, &&, ||, ! for comparisons and logical tests
  • The script's result is the value of the last evaluated expression — no explicit return is required, though return ...; is also supported

Example script​

var gender = text(getAnswer('Q1'));
var age = integer(getAnswer('Q2'), 0);

var value = 'N/A';

if (gender == 'Male' || gender == 'Female') {
value = gender;

if (age > 45) {
value = value + ' - out of target';
} else {
value = value + ' - in target';
}
}

value

What this example does:

  • Reads gender from Q1 as text
  • Reads age from Q2 as an integer, defaulting to 0 if missing or invalid
  • Builds a label only for expected gender values
  • Returns N/A when no valid classification can be computed

Conversion functions​

FunctionFrench aliasDescription
number(value)nombre(value)Converts a value to a decimal number
number(value, default)nombre(value, default)Same, using default if the conversion fails
integer(value)entier(value)Converts a value to an integer (truncates decimals)
integer(value, default)entier(value, default)Same, using default if the conversion fails
text(value)texte(value)Converts a value to text
isNumeric(text)-Returns true if text can be converted to a number
toNumbers(list)-Converts a list of text values into a list of numbers
number('42')        // → 42.0
number('abc', 0) // → 0 (fallback, 'abc' is not a number)
integer('3.14') // → 3
text(42) // → "42"

isNumeric('42') // → true
isNumeric('abc') // → false
toNumbers(['1', '2', '3']) // → [1, 2, 3]

Math functions​

FunctionFrench aliasDescription
round(number)arrondi(number)Rounds to the nearest integer
round(number, decimals)-Rounds to the given number of decimals
max(a, b)-Returns the greater of two numbers
min(a, b)-Returns the lesser of two numbers
abs(number)-Returns the absolute value
modulo(a, b)-Returns the remainder of a divided by b
exp(number)exponential(number) / exponentielle(number)Computes the exponential (e^x)
sum(list)-Returns the sum of a list of numbers
avg(list)-Returns the average of a list of numbers
round(3.7)             // → 4
round(3.14159, 2) // → 3.14
max(10, 20) // → 20
min(10, 20) // → 10
abs(-42) // → 42
modulo(10, 3) // → 1
exp(1) // → 2.718281828...
sum([10, 20, 30]) // → 60
avg([10, 20, 30]) // → 20

Text functions​

FunctionFrench aliasDescription
length(text)longueur(text)Returns the length of a text value
trim(text)-Removes leading and trailing whitespace
isBlank(text)-Returns true if text is empty or contains only whitespace
split(text)-Splits text into a list, using , as the delimiter
split(text, delimiter)-Splits text into a list, using a custom delimiter
toList(entry1, entry2, ...)-Builds a list from the given text values (any number of arguments)
join(list, separator)-Joins the elements of list into a single text, using separator
contains(list, value)-Returns true if value is one of the elements of list
containsAny(list, values)-Returns true if list contains at least one element of values
containsAll(list, values)-Returns true if list contains every element of values
count(list)-Returns the number of elements in list
length('Hello')        // → 5
trim(' Hello ') // → "Hello"
isBlank(' ') // → true
isBlank('Hello') // → false

split('A,B,C') // → ["A", "B", "C"]
split('A;B;C', ';') // → ["A", "B", "C"]
toList('A', 'B', 'C') // → ["A", "B", "C"]
join(['A', 'B', 'C'], ';') // → "A;B;C"

contains(split('A,B,C'), 'B') // → true
containsAny(split('A,B,C'), ['B', 'Z']) // → true
containsAll(split('A,B,C'), ['A', 'B']) // → true
count(split('A,B,C')) // → 3

containsAny(split(getAnswer('Q1')), toList(Q1.A1, Q1.A2)) // → true if answer A1 or A2 was selected in Q1
containsAll(split(getAnswer('Q1')), toList('A', 'B')) // → true if both options A and B were selected in Q1
tip

split and contains are useful together to check whether a specific option was selected in a multi-select answer, since getAnswer(...) returns selected values as a single comma-separated text: contains(split(getAnswer('Q1')), 'Option A').

Date and time functions​

These functions give access to the current date/time, let you extract or reformat a date/time value coming from an answer, add or subtract an amount of time from a date, a time, or an arbitrary date/time value, and compute the difference between two dates or two times.

Current date/time components​

FunctionDescriptionExample value
day()Current day of the month17
month()Current month (1-12)8
year()Current year2026
hour()Current hour (0-23)14
minute()Current minute32
second()Current second5
tip

The current date/time is computed using the timezone configured in the SYSTEM.TIMEZONE variable, or the system's default timezone if it is not configured.

Extracting a component from a given date/time value​

Use these overloads to extract a component from a date/time coming from an answer (for example a birth date collected in an open question) instead of the current date/time. value is parsed using format, then the requested component is returned.

FunctionDescriptionExample
day(value, format)Day of the month extracted from valueday('17/08/2026', 'dd/MM/yyyy') → 17
month(value, format)Month (1-12) extracted from valuemonth('17/08/2026', 'dd/MM/yyyy') → 8
year(value, format)Year extracted from valueyear('17/08/2026', 'dd/MM/yyyy') → 2026
hour(value, format)Hour (0-23) extracted from valuehour('14:32:05', 'HH:mm:ss') → 14
minute(value, format)Minute extracted from valueminute('14:32:05', 'HH:mm:ss') → 32
second(value, format)Second extracted from valuesecond('14:32:05', 'HH:mm:ss') → 5
day('17/08/2026', 'dd/MM/yyyy')      // → 17
month('17/08/2026', 'dd/MM/yyyy') // → 8
year('17/08/2026', 'dd/MM/yyyy') // → 2026
hour('14:32:05', 'HH:mm:ss') // → 14
minute('14:32:05', 'HH:mm:ss') // → 32
second('14:32:05', 'HH:mm:ss') // → 5
tip

day, month, and year expect value to contain a date; hour, minute, and second expect value to contain a time. format must match value exactly.

Current date/time as text​

FunctionDescriptionExample value
date()Current date, formatted dd/MM/yyyy"17/08/2026"
date(format)Current date, formatted with a custom patterndate('yyyy-MM-dd') → "2026-08-17"
time()Current time, formatted HH:mm:ss"14:32:05"
time(format)Current time, formatted with a custom patterntime('HH:mm') → "14:32"
tip

The format argument follows the Java DateTimeFormatter pattern syntax. The most common letters are d/M/y for day/month/year and H/m/s for hour/minute/second (for example dd/MM/yyyy, yyyy-MM-dd, HH:mm:ss).

Parsing and reformatting a given date/time value​

date(value, format) and time(value, format) parse value using format and return it formatted the same way. Since input and output use the same pattern, they mainly serve to validate that an answer matches the expected pattern before using it elsewhere in the script.

FunctionDescriptionExample
date(value, format)Parses value as a date using format, returns it formatted the same waydate('2026-08-17', 'yyyy-MM-dd') → "2026-08-17"
time(value, format)Parses value as a time using format, returns it formatted the same waytime('14:32:05', 'HH:mm:ss') → "14:32:05"
date('2026-08-17', 'yyyy-MM-dd')   // → "2026-08-17"
time('14:32:05', 'HH:mm:ss') // → "14:32:05"

Adding to the current date/time​

Each function returns the current date (or time) shifted by the given amount, formatted as text. The format argument is optional and defaults to dd/MM/yyyy for date functions, HH:mm:ss for time functions.

FunctionDescription
addDays(amount) / addDays(amount, format)Current date + amount days
addMonths(amount) / addMonths(amount, format)Current date + amount months
addYears(amount) / addYears(amount, format)Current date + amount years
addHours(amount) / addHours(amount, format)Current time + amount hours
addMinutes(amount) / addMinutes(amount, format)Current time + amount minutes
addSeconds(amount) / addSeconds(amount, format)Current time + amount seconds

amount can be negative to go backward (for example addDays(-7) for one week ago).

addDays(7)                    // → one week from today, e.g. "24/08/2026"
addDays(-30, 'yyyy-MM-dd') // → 30 days ago, e.g. "2026-07-18"
addMonths(1) // → same day next month
addYears(-18) // → 18 years before today (e.g. a majority threshold)
addHours(2, 'HH:mm') // → current time + 2 hours, e.g. "16:32"

Adding to an arbitrary date/time value​

add(value, format, amount, unit) parses value using format, adds amount of unit to it, and returns the result formatted the same way. Use this when you need to shift a date/time coming from an answer rather than the current date/time.

  • format must match value exactly (same rules as date()/time()) and can describe a date, a time, or both.
  • unit accepts (case-insensitive): DAYS, MONTHS, YEARS, HOURS, MINUTES, SECONDS.
  • The function raises an error if value cannot be parsed with format, or if unit is not applicable to the parsed value (for example adding HOURS to a date-only value with no time part).
add('01/01/2026', 'dd/MM/yyyy', 3, 'MONTHS')     // → "01/04/2026"
add('23:50:00', 'HH:mm:ss', 20, 'MINUTES') // → "00:10:00"
add('01/01/2026 23:00', 'dd/MM/yyyy HH:mm', 2, 'HOURS') // → "02/01/2026 01:00"
warning

Passing an unrecognized unit (anything other than DAYS, MONTHS, YEARS, HOURS, MINUTES, SECONDS) or a value that doesn't match format stops the script with an error. Validate values coming from open answers before calling add(...) on them.

Difference between two dates or times​

These functions return the difference between two dates (or two times), both parsed with the same format, as a whole number of the given unit.

FunctionDescriptionExample
daysBetween(startDate, endDate, format)Number of full days between startDate and endDatedaysBetween('01/01/2026', '10/01/2026', 'dd/MM/yyyy') → 9
monthsBetween(startDate, endDate, format)Number of full months between startDate and endDatemonthsBetween('01/01/2026', '15/03/2026', 'dd/MM/yyyy') → 2
yearsBetween(startDate, endDate, format)Number of full years between startDate and endDateyearsBetween('15/03/2005', '17/08/2026', 'dd/MM/yyyy') → 21
hoursBetween(startHour, endHour, format)Number of full hours between startHour and endHourhoursBetween('08:00:00', '17:30:00', 'HH:mm:ss') → 9
minutesBetween(startHour, endHour, format)Number of full minutes between startHour and endHourminutesBetween('08:00:00', '08:45:00', 'HH:mm:ss') → 45
secondsBetween(startHour, endHour, format)Number of full seconds between startHour and endHoursecondsBetween('08:00:00', '08:00:30', 'HH:mm:ss') → 30
daysBetween('01/01/2026', '10/01/2026', 'dd/MM/yyyy')      // → 9
monthsBetween('01/01/2026', '15/03/2026', 'dd/MM/yyyy') // → 2
yearsBetween('15/03/2005', '17/08/2026', 'dd/MM/yyyy') // → 21
hoursBetween('08:00:00', '17:30:00', 'HH:mm:ss') // → 9
minutesBetween('08:00:00', '08:45:00', 'HH:mm:ss') // → 45
secondsBetween('08:00:00', '08:00:30', 'HH:mm:ss') // → 30
tip

daysBetween, monthsBetween, and yearsBetween expect date values; hoursBetween, minutesBetween, and secondsBetween expect time values. Both values must use the same format. If endDate/endHour comes before startDate/startHour, the result is negative.

warning

Every function documented above that parses a value with a format (day(value, format), month(value, format), year(value, format), hour(value, format), minute(value, format), second(value, format), date(value, format), time(value, format), add(...), and the *Between functions) raises an error if the value cannot be parsed with the given format. Validate or guard values coming from open answers before calling them.

Additional examples​

Numeric score with rounding​

var scoreA = number(getItemAnswer('Q3', 'I1'));
var scoreB = number(getItemAnswer('Q3', 'I2'));

var total = scoreA + scoreB;
var average = round(total / 2);

average

Keep the highest value from multiple inputs​

var v1 = number(getAnswer('Q4'));
var v2 = number(getAnswer('Q5'));
var v3 = number(getAnswer('Q6'));

max(max(v1, v2), v3)

Weighted score​

var note1 = number(getAnswer('Q_NOTE1'), 0);
var note2 = number(getAnswer('Q_NOTE2'), 0);
var note3 = number(getAnswer('Q_NOTE3'), 0);

// Weighting: 50% note1, 30% note2, 20% note3
var score = (note1 * 0.5) + (note2 * 0.3) + (note3 * 0.2);

round(score)

Message based on a threshold​

var score = number(getAnswer('Q_SCORE'), 0);

if (score >= 80) {
'Excellent - ' + text(score) + '%';
} else if (score >= 60) {
'Good - ' + text(score) + '%';
} else {
'Below target - ' + text(score) + '%';
}

Concatenation of several fields​

var firstName = getFieldAnswer('Q_IDENTITY', 'F1');
var lastName = getFieldAnswer('Q_IDENTITY', 'F2');
var city = getFieldAnswer('Q_IDENTITY', 'F3');

firstName + ' ' + lastName + ', ' + city
// → "John Smith, Paris"

Majority check from a birth date​

Use yearsBetween to compute the age directly, rather than comparing formatted date strings:

var birthDate = getFieldAnswer('Q_BIRTHDATE', 'F1');  // e.g. "2005-03-15"

if (birthDate == null) {
'N/A';
} else {
var age = yearsBetween(birthDate, date('yyyy-MM-dd'), 'yyyy-MM-dd');

if (age >= 18) {
'Major';
} else {
'Minor';
}
}

Rules and limitations​

Allowed:

  • if / else conditions
  • Comparison operators (==, !=, <, >, <=, >=)
  • Logical operators (&&, ||, !)
  • Arithmetic operators (+, -, *, /, %)
  • Local variables (var x = ...)
  • Function calls
  • Text concatenation (+)
  • Property access (.)
  • Literals (arrays, objects)

Not allowed:

  • while / for loops
  • Lambdas / anonymous functions
  • Object creation with new
  • Access to Java classes
  • Library imports
  • Modification of global variables
warning

Scripts are limited to 500ms of execution time and run in an isolated sandbox with no access to the file system or network. Only the documented functions and question variables are available.

Best practices​

  • Initialize a default fallback value first (for example "N/A")
  • Convert values explicitly (text(...), number(...), integer(...)) before comparisons, using the default-value overload to guard against missing answers
  • Check for null before using a value, especially with getItemAnswer, getZoneAnswer, and getFieldAnswer, which return null for missing or invalid refs
  • Use hasAnswered(ref) or exists(ref) to check for an answer before reading it, as an alternative to a null check
  • Store repeated calculations in a variable instead of recomputing them in every branch
  • Keep scripts short and readable to stay within the 500ms timeout
  • Test edge cases (empty answers, unexpected values, missing responses)
  • Always verify that the script returns a value in every branch