JEXL Practices And Cheat Sheet
Recommended Practices
Keep expressions short. If an expression combines an API call, multiple nested ternary branches, and several transformations, it will be hard to maintain.
Use ?? for numeric fallbacks:
$context.form.amount ?? 0
Use || for text where an empty string means "no value":
$context.form.name || 'No name'
For custom validation, always verify whether the field expects true as "valid" or as "invalid". In schema customValidations, current form examples use true as "valid".
Use Public API calls only where they are necessary. An expression can be evaluated multiple times while the user edits the form.
For date/time expressions, distinguish:
addTimereturns ISO with offset,addTimeIsoreturns UTC ISO withZ,now(true)sets time to start of day,midnight()returns end of day.
For less common helpers, verify availability in the JEXL debug console before using them in production configuration.
Use deprecated helpers only when maintaining existing configuration. For new date expressions, prefer dateFormat, toIso, toISOString, or addTimeIso over dateServerFormat.
Common Mistakes
Reversed Hidden Logic
In a Hidden expression, true means hide:
$context.form.segment == 'B2B'
This expression does not mean "visible for B2B"; it hides the field for B2B.
Losing Zero With ||
$context.form.count || 1
If count is 0, the result is 1. For numbers, use:
$context.form.count ?? 1
Unguarded Nested Access
$context.form.customer.name
If customer is not filled, the expression can fail. Safer form:
$context.form.customer ? $context.form.customer.name : null
API Call In A Frequently Evaluated Field
crm.customer.find({ code: $context.form.customerCode })
This is fine for a targeted lookup, but it should not be part of an expression that is recalculated on every keystroke without debounce or cache.
Assuming Full JavaScript Runtime
JEXL is not full JavaScript. Do not assume helpers such as keys(...) or Object.keys(...) are available in a configured field:
keys($context.form)
Use a registered helper, an explicit array of known keys, or a backend script:
[
{ key: 'productInternet', value: $context.form.productInternet },
{ key: 'productTv', value: $context.form.productTv },
{ key: 'productVoice', value: $context.form.productVoice }
].filter(x => x.value != null)
For less common helpers, verify the expression in the JEXL debug console before using it in production configuration.
Quick Cheat Sheet
| Need | Expression |
|---|---|
| Today | now(true) |
| Current time | now() |
| Add 30 days | addTimeIso($context.form.validFrom, 30, 'days') |
| Format date | dateFormat($context.form.date, 'dd.MM.yyyy') |
| Sum prices | sumMany($context.form.items, 'price', 2) |
| Empty value | isNullOrEmpty($value) |
| Email validation | $value && !isValidEmail($value) |
| Array contains value | includes($context.form.roles, 'ADMIN') |
| Array contains at least one value | includesArray($context.form.roles, ['ADMIN', 'SUPPORT'], true) |
| First primary contact | find($context.form.contacts, 'primary', true) |
| Contact names | join(map($context.form.contacts, 'name'), ', ') |
| Create object | createObject('customer.id', $context.form.customerId) |
| Public API get | crm.customer.get($context.form.customerId) |
| Public API filter | crm.customer.find({ code__in: $context.form.customerCodes }) |
| Translate label | `'order.installation.self' |
| Show object as JSON | `$context.form.productInternet |
| Parse configured JSON | `$context.form.rawJson ? ($context.form.rawJson |
| Legacy date conversion | Existing dateServerFormat(...) only; do not use for new fields |