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.
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
| Variable | Description | Example value |
|---|---|---|
Q1.ANSWER | Full answer(s), comma separated | "Yes" or "Option A,Option B" |
Q1.COUNT | Number of selected answers | 1 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.
| Variable | Description | Example value |
|---|---|---|
Q2.I1.ANSWER | Answer(s) for item I1 | "Satisfied" |
Q2.I1.COUNT | Number of answers for item I1 | 1 |
Q2.I1.A001 | First answer of item I1 | "Satisfied" |
Hotspot
Z1, Z2, ... refer to the zone refs in the hotspot.
| Variable | Description | Example value |
|---|---|---|
Q3.Z1.ANSWER | Answer for zone Z1 | "Selected" |
Q3.Z1.COUNT | Number of answers for zone Z1 | 1 or 0 |
Open questions
F1, F2, ... refer to the field refs in the open question.
| Variable | Description | Example value |
|---|---|---|
Q4.F1.ANSWER | Answer of field F1 | "John Smith" |
Q4.F2.ANSWER | Answer of field F2 | "john@email.com" |
R3M questions (3 words / Experience)
| Variable | Description | Example value |
|---|---|---|
Q5.WORD1.ANSWER | First word | "Innovation" |
Q5.WORD2.ANSWER | Second word | "Quality" |
Q5.WORD3.ANSWER | Third word | "Service" |
Loop iterations
When a question is repeated inside a loop, each iteration exposes its own set of variables using the LOOPn notation (0-based):
Q1.LOOP0.ANSWER // first iteration
Q1.LOOP1.ANSWER // second iteration
Q1.LOOP2.ANSWER // third iteration
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 type | Function | Equivalent to |
|---|---|---|
| Profile / adaptive | getAnswer(questionRef) | Q1.ANSWER |
| Profile / adaptive | getAnswer(questionRef, index) | Q1.A001 |
| Profile / adaptive | getAnswerCount(questionRef) | Q1.COUNT |
| Battery | getItemAnswer(questionRef, itemRef) | Q2.I1.ANSWER |
| Battery | getItemAnswer(questionRef, itemRef, index) | Q2.I1.A001 |
| Battery | getItemAnswerCount(questionRef, itemRef) | Q2.I1.COUNT |
| Hotspot | getZoneAnswer(questionRef, zoneRef) | Q3.Z1.ANSWER |
| Hotspot | getZoneAnswer(questionRef, zoneRef, index) | - |
| Hotspot | getZoneAnswerCount(questionRef, zoneRef) | Q3.Z1.COUNT |
| Open question | getFieldAnswer(questionRef, fieldRef) | Q4.F1.ANSWER |
| Open question | getFieldAnswer(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"
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"
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.
| Function | Description |
|---|---|
hasAnswer(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 |
hasAnswer('Q1') // → true if Q1 has been answered
hasAnswer('Q2.I1') // → true if item I1 of battery Q2 has been answered
hasAnswer('Q3.Z1') // → true if zone Z1 of hotspot Q3 has been answered
exists('Q1.ANSWER') // → same as hasAnswer('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 (hasAnswer('Q1')) {
getAnswer('Q1');
} else {
'N/A';
}
hasAnswer(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).
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).
| Function | Description |
|---|---|
getQueryParameter(name) | Returns the value of the URL query parameter name, or null if it is not present |
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';
}
When the parameter is absent from the URL, getQueryParameter returns null. Always guard against null before using the value in a comparison or concatenation.
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
varto 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
returnis required, thoughreturn ...;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
Q1as text - Reads age from
Q2as an integer, defaulting to0if missing or invalid - Builds a label only for expected gender values
- Returns
N/Awhen no valid classification can be computed
Conversion functions
| Function | French alias | Description |
|---|---|---|
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 |
number('42') // → 42.0
number('abc', 0) // → 0 (fallback, 'abc' is not a number)
integer('3.14') // → 3
text(42) // → "42"
Math functions
| Function | French alias | Description |
|---|---|---|
round(number) | arrondi(number) | Rounds to the nearest integer |
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) |
round(3.7) // → 4
max(10, 20) // → 20
min(10, 20) // → 10
abs(-42) // → 42
modulo(10, 3) // → 1
exp(1) // → 2.718281828...
Text functions
| Function | French alias | Description |
|---|---|---|
length(text) | longueur(text) | Returns the length of a text value |
trim(text) | - | Removes leading and trailing whitespace |
length('Hello') // → 5
trim(' Hello ') // → "Hello"
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
| Function | Description | Example value |
|---|---|---|
day() | Current day of the month | 17 |
month() | Current month (1-12) | 8 |
year() | Current year | 2026 |
hour() | Current hour (0-23) | 14 |
minute() | Current minute | 32 |
second() | Current second | 5 |
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.
| Function | Description | Example |
|---|---|---|
day(value, format) | Day of the month extracted from value | day('17/08/2026', 'dd/MM/yyyy') → 17 |
month(value, format) | Month (1-12) extracted from value | month('17/08/2026', 'dd/MM/yyyy') → 8 |
year(value, format) | Year extracted from value | year('17/08/2026', 'dd/MM/yyyy') → 2026 |
hour(value, format) | Hour (0-23) extracted from value | hour('14:32:05', 'HH:mm:ss') → 14 |
minute(value, format) | Minute extracted from value | minute('14:32:05', 'HH:mm:ss') → 32 |
second(value, format) | Second extracted from value | second('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
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
| Function | Description | Example value |
|---|---|---|
date() | Current date, formatted dd/MM/yyyy | "17/08/2026" |
date(format) | Current date, formatted with a custom pattern | date('yyyy-MM-dd') → "2026-08-17" |
time() | Current time, formatted HH:mm:ss | "14:32:05" |
time(format) | Current time, formatted with a custom pattern | time('HH:mm') → "14:32" |
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.
| Function | Description | Example |
|---|---|---|
date(value, format) | Parses value as a date using format, returns it formatted the same way | date('2026-08-17', 'yyyy-MM-dd') → "2026-08-17" |
time(value, format) | Parses value as a time using format, returns it formatted the same way | time('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.
| Function | Description |
|---|---|
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.
formatmust matchvalueexactly (same rules asdate()/time()) and can describe a date, a time, or both.unitaccepts (case-insensitive):DAYS,MONTHS,YEARS,HOURS,MINUTES,SECONDS.- The function raises an error if
valuecannot be parsed withformat, or ifunitis not applicable to the parsed value (for example addingHOURSto 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"
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.
| Function | Description | Example |
|---|---|---|
daysBetween(startDate, endDate, format) | Number of full days between startDate and endDate | daysBetween('01/01/2026', '10/01/2026', 'dd/MM/yyyy') → 9 |
monthsBetween(startDate, endDate, format) | Number of full months between startDate and endDate | monthsBetween('01/01/2026', '15/03/2026', 'dd/MM/yyyy') → 2 |
yearsBetween(startDate, endDate, format) | Number of full years between startDate and endDate | yearsBetween('15/03/2005', '17/08/2026', 'dd/MM/yyyy') → 21 |
hoursBetween(startHour, endHour, format) | Number of full hours between startHour and endHour | hoursBetween('08:00:00', '17:30:00', 'HH:mm:ss') → 9 |
minutesBetween(startHour, endHour, format) | Number of full minutes between startHour and endHour | minutesBetween('08:00:00', '08:45:00', 'HH:mm:ss') → 45 |
secondsBetween(startHour, endHour, format) | Number of full seconds between startHour and endHour | secondsBetween('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
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.
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 avg = round(total / 2);
avg
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/elseconditions- Comparison operators (
==,!=,<,>,<=,>=) - Logical operators (
&&,||,!) - Arithmetic operators (
+,-,*,/,%) - Local variables (
var x = ...) - Function calls
- Text concatenation (
+) - Property access (
.) - Literals (arrays, objects)
Not allowed:
while/forloops- Lambdas / anonymous functions
- Object creation with
new - Access to Java classes
- Library imports
- Modification of global variables
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
nullbefore using a value, especially withgetItemAnswer,getZoneAnswer, andgetFieldAnswer, which returnnullfor missing or invalid refs - Use
hasAnswer(ref)orexists(ref)to check for an answer before reading it, as an alternative to anullcheck - 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