Expression Language Guide¶
When a Synchronisation Rule flows an attribute from one system to another, it often needs to do more than copy a value across unchanged. You might want to build an email address from a first and last name, force a username to lower case, decide whether an account should be enabled based on someone's employment status, or skip an object entirely unless it meets a condition. JIM's expression language is how you describe these transformations.
An expression is a short, readable formula that JIM evaluates during synchronisation to produce the value for a target attribute. If you have ever written a formula in a spreadsheet cell, this will feel familiar: you reference attributes much as a spreadsheet references cells, combine them with operators and functions, and JIM works out the result for each object as it is synchronised. No programming experience is required.
Where Expressions Fit¶
Expressions live inside Synchronisation Rules, in what is known as the Attribute Flow: the part of a rule that decides which value lands in each target attribute. Most flows are a straightforward direct mapping (copy cs["mail"] straight into mv["Email"]); you only reach for an expression when a plain copy is not enough.
Within a Synchronisation Rule, expressions are used in:
- Export attribute mappings
Transform metaverse attributes before sending them to a Connected System. - Import attribute mappings
Transform Connected System attributes before storing them in the metaverse. - Conditional logic
Choose different values based on conditions (e.g. enable or disable an account based on employee status). - Scoping filters
Determine which objects are in scope for a Synchronisation Rule.
To add or edit these mappings in the admin UI, see Synchronisation Rules. The rest of this guide focuses on the expressions themselves: the syntax, the built-in functions, and worked examples you can adapt.
Expressions are also used by example data generation templates to construct a generated attribute's value from other attributes on the same object, for example building an email address whose domain is derived from a generated company name. Only the mv accessor applies in that context (generation builds Metaverse Objects directly, so there is no Connected System Object), but the same syntax and functions described below apply.
Quick Examples¶
Before diving into the details, here are a few examples to give you a feel for how expressions work:
// Build an email address from first and last name
Lower(mv["First Name"]) + "." + Lower(mv["Last Name"]) + "@company.com"
// Enable or disable an account based on employee status
IIF(Eq(mv["Employee Status"], "Active"), 512, 514)
// Use the preferred name if available, otherwise fall back to the first name
Coalesce(mv["Preferred Name"], mv["First Name"])
Attribute Access¶
Expressions work with two sources of data:
mv
Attributes on the Metaverse Object (the central identity record).cs
Attributes on the Connected System Object (the external system record).
Access an attribute by putting its name in square brackets and quotes:
mv["Display Name"]
mv["Employee Status"]
mv["Department"]
cs["sAMAccountName"]
cs["userAccountControl"]
Attribute names are matched case-insensitively, so mv["Department"], mv["department"], and mv["DEPARTMENT"] all refer to the same attribute. This applies to attribute names only; attribute values are compared case-sensitively by default, which is why text comparisons use Eq() and Lower() (see String Comparison). Mirroring the casing shown in the JIM admin UI keeps expressions readable, but it is not required for them to work. For the wider picture of where JIM is case-sensitive and where it is forgiving, see Case Sensitivity.
Operators¶
You can use standard operators to combine values, do arithmetic, and make comparisons.
Joining Text¶
Use + to join text values together:
mv["First Name"] + " " + mv["Last Name"]
// If First Name is "Jane" and Last Name is "Smith", the result is "Jane Smith"
"CN=" + mv["Account Name"] + ",OU=Users,DC=company,DC=local"
Arithmetic¶
Standard maths operators work with numbers:
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ |
Add | 10 + 3 |
13 |
- |
Subtract | 10 - 3 |
7 |
* |
Multiply | 10 * 3 |
30 |
/ |
Divide | 10 / 3 |
3 |
% |
Remainder | 10 % 3 |
1 |
Comparisons¶
Compare values using these operators. The result is either true or false:
| Operator | Meaning | Example | Result |
|---|---|---|---|
> |
Greater than | 10 > 3 |
true |
< |
Less than | 10 < 3 |
false |
>= |
Greater than or equal to | 10 >= 10 |
true |
<= |
Less than or equal to | 3 <= 10 |
true |
!= |
Not equal to | 10 != 3 |
true |
Important: For comparing text values (strings), always use the
Eq()function instead of==. See String Comparison below.
Logic¶
Combine multiple conditions using logical operators:
| Operator | Meaning | Example | Result |
|---|---|---|---|
&& |
AND | 10 > 3 && 5 > 2 |
true |
\|\| |
OR | 10 > 3 \|\| 5 < 2 |
true |
! |
NOT | !(10 > 3) |
false |
Example -- check that an employee is both active and in the IT department:
String Comparison¶
Always use Eq() for comparing text values, NOT ==.
The == operator can give incorrect results when comparing attribute values to text. This is a technical limitation of how attribute values are stored internally.
// WRONG -- may give incorrect results
IIF(mv["Employee Status"] == "Active", 512, 514)
// CORRECT -- always use Eq() for text comparisons
IIF(Eq(mv["Employee Status"], "Active"), 512, 514)
For case-insensitive comparisons (where "Active", "ACTIVE", and "active" should all match), wrap the value in Lower() first:
Rule of thumb: Use
Eq()whenever you are comparing attribute values to text. Use>,<,>=,<=,!=for number comparisons.
Built-in Functions¶
String Functions¶
| Function | Description | Example |
|---|---|---|
Trim(value) |
Remove spaces from the start and end | Trim(mv["Name"]) |
Upper(value) |
Convert to uppercase | Upper(mv["Name"]) |
Lower(value) |
Convert to lowercase | Lower(mv["Name"]) |
Capitalise(value) |
Capitalise each word (handles hyphenated names like "O'Brien-Smith") | Capitalise(mv["Name"]) |
Left(value, count) |
Take the first N characters | Left(mv["Name"], 3) |
Right(value, count) |
Take the last N characters | Right(mv["Name"], 4) |
Substring(value, start, length) |
Extract a portion of text | Substring(mv["Name"], 2, 4) |
Replace(value, old, new) |
Replace one piece of text with another | Replace(mv["Email"], "@old.com", "@new.com") |
Length(value) |
Count the number of characters | Length(mv["Name"]) |
IsNullOrEmpty(value) |
Check if a value is missing or blank | IsNullOrEmpty(mv["Name"]) |
IsNullOrWhitespace(value) |
Check if a value is missing, blank, or only spaces | IsNullOrWhitespace(mv["Name"]) |
StartsWith(value, prefix) |
Check if text begins with a specific value | StartsWith(mv["Email"], "admin") |
EndsWith(value, suffix) |
Check if text ends with a specific value | EndsWith(mv["Email"], "@company.com") |
Contains(value, search) |
Check if text contains a specific value | Contains(mv["Email"], "@company") |
Conditional Functions¶
| Function | Description | Example |
|---|---|---|
IIF(condition, trueValue, falseValue) |
Return one value if a condition is true, another if false | IIF(Eq(mv["Status"], "Active"), 512, 514) |
Coalesce(value1, value2) |
Use the first value if it exists, otherwise use the second | Coalesce(mv["Preferred Name"], mv["First Name"]) |
Eq(value1, value2) |
Check if two values are equal (required for text comparisons) | Eq(mv["Department"], "IT") |
Conversion Functions¶
| Function | Description | Example |
|---|---|---|
ToString(value) |
Convert a value to text | ToString(mv["EmployeeId"]) |
ToInt(value) |
Convert text to a whole number (returns 0 if not a valid number) | ToInt(mv["Age"]) |
Date Functions¶
| Function | Description | Example |
|---|---|---|
Now() |
Current date and time (UTC) | Now() |
Today() |
Current date at midnight (UTC) | Today() |
FormatDate(date, format) |
Format a date as text (e.g., "2026-03-15") | FormatDate(mv["HireDate"], "yyyy-MM-dd") |
ToFileTime(date) |
Convert a date to Active Directory's FILETIME format | ToFileTime(mv["Account Expires"]) |
FromFileTime(filetime) |
Convert an Active Directory FILETIME back to a date | FromFileTime(cs["accountExpires"]) |
Distinguished Name (DN) Functions¶
| Function | Description | Example |
|---|---|---|
EscapeDN(value) |
Safely escape special characters for use in LDAP distinguished names | "CN=" + EscapeDN(mv["Display Name"]) + ",OU=Users,DC=domain,DC=local" |
Password Generation¶
| Function | Description | Example |
|---|---|---|
RandomPassword(length, extendedChars) |
Generate a random password. Set extendedChars to true for special characters (!@#$%^&*) |
RandomPassword(16, true) |
RandomPassphrase(wordCount, separator) |
Generate a passphrase from random words | RandomPassphrase(4, "-") |
Collection Functions¶
These functions work with multi-valued attributes (attributes that hold a list of values rather than a single value).
| Function | Description | Example |
|---|---|---|
CollectionContains(collection, value) |
Check if a list of values contains a specific value | CollectionContains(cs["memberOf"], "CN=Admins") |
Split(value, delimiter) |
Split a delimited text value into a list | Split(cs["coursesCompleted"], "\|") |
Join(collection, delimiter) |
Combine a list of values into a single delimited text value | Join(mv["Groups"], ",") |
Account Control Functions¶
These functions are primarily used with Active Directory's userAccountControl attribute, which stores account settings as a single number using individual bit flags.
| Function | Description | Example |
|---|---|---|
EnableUser(uac) |
Enable a user account | EnableUser(cs["userAccountControl"]) |
DisableUser(uac) |
Disable a user account | DisableUser(cs["userAccountControl"]) |
SetBit(value, bit) |
Turn on a specific flag | SetBit(mv["uac"], 65536) |
ClearBit(value, bit) |
Turn off a specific flag | ClearBit(mv["uac"], 65536) |
HasBit(value, bit) |
Check if a specific flag is turned on | HasBit(cs["userAccountControl"], 2) |
⚠️ Nulls, Missing Inputs, and Whitespace¶
This is the most important section to read before you write an expression that references an attribute which might be absent. How JIM treats a missing input decides whether a target value is cleared, kept, or silently filled with malformed data. Get this wrong and an expression can quietly clear good data, or write a broken value that passes straight through to the target system.
When you reference an attribute the object does not have (for example cs["middleName"] on a person with no middle name), the expression sees null. There are three behaviours to understand.
1. A null result means "no value", and clears the target¶
When an expression evaluates to null, JIM treats that as a deliberate assertion that the attribute should have no value, not as "no opinion, leave it alone". For an import mapping, JIM removes any existing value from the target Metaverse attribute.
This is intentionally different from the behaviour of some traditional ILM tools, where a function call that returned null meant "do nothing". In JIM, null means no value, and a null result on an import mapping clears the target.
Which rule's value ultimately wins (and therefore whether a null from one rule actually clears a value contributed by another) is resolved by priority. The key authoring takeaway is simpler: a null result is a clear, not a skip. If you do not want an expression to clear the target when an input is missing, you must guard it (see point 3).
2. Built-in string functions are null-safe and propagate null¶
The string functions (Trim, Upper, Lower, Capitalise, Left, Right, Substring, Replace, EscapeDN, and similar) are null-safe: given a null input, they return null rather than erroring. That is convenient, but it means null travels through them unchanged.
So a bare Trim(cs["middleName"]) on a person with no middle name evaluates to null, which (per point 1) clears the target attribute:
// HAZARD -- if middleName is absent, this evaluates to null and CLEARS the target
Trim(cs["middleName"])
// SAFE -- fall back to the existing target value, or to an empty result you control
Coalesce(Trim(cs["middleName"]), mv["Middle Name"])
To avoid an accidental clear, guard the expression so it never resolves to a bare null for a value you want to preserve: use Coalesce() to supply a fallback, or IIF() with an IsNullOrEmpty() / IsNullOrWhitespace() check (see point 3).
3. String concatenation does NOT yield null; it yields a malformed value¶
This is the more dangerous case, because it fails silently with wrong data rather than clearing. In a + concatenation, a null operand is treated as an empty string, not as null. So the whole expression produces a real, non-null value that is stored as-is and bypasses the null/clear path entirely.
// HAZARD -- if Last Name is absent, this produces "jane." (a real value), not null
Lower(cs["First Name"]) + "." + Lower(cs["Last Name"]) + "@company.com"
// Result: "jane.@company.com" -- a malformed email, written without complaint
The same hazard produces malformed LDAP distinguished names when a component is missing:
// HAZARD -- if Account Name is absent, this produces "CN=,OU=Users,..." -- a broken DN
"CN=" + mv["Account Name"] + ",OU=Users,DC=company,DC=local"
Steer around it by validating or supplying fallbacks before composing the value:
| Technique | Use it to | Example |
|---|---|---|
Coalesce(a, b) |
Supply a fallback when the first value is missing | Coalesce(mv["Preferred Name"], mv["First Name"]) |
IIF(condition, trueVal, falseVal) |
Build the composed value only when its inputs are present, otherwise fall back to a safe value | IIF(IsNullOrWhitespace(cs["Last Name"]), mv["Email"], Lower(cs["First Name"]) + "." + Lower(cs["Last Name"]) + "@company.com") |
IsNullOrEmpty / IsNullOrWhitespace |
Test an input before composing with it | IsNullOrWhitespace(cs["Last Name"]) |
EscapeDN plus careful construction |
Build directory DNs safely; still guard each required component for presence | "CN=" + EscapeDN(mv["Display Name"]) + ",OU=Users,DC=company,DC=local" |
EscapeDN() protects against special characters such as commas in a value; it does not protect against a value being absent. A missing required component still yields a malformed DN, so guard for presence as well as escaping for safety.
4. Whitespace-only results become no value (by default)¶
A result that is only whitespace (spaces, tabs, newlines) is, by default, collapsed to no value and clears the target, exactly like null. This is governed per import mapping by the Treat whitespace as no value control in inbound value processing, which is on by default. Switch it off for a mapping where whitespace is genuinely meaningful, and a whitespace-only result is stored as a literal value instead (the portal flags it with a (whitespace) indicator so it is not mistaken for an empty cell).
Summary¶
| Expression evaluates to | Stored as | Effect on target |
|---|---|---|
null (for example Trim() of an absent attribute) |
No value | ❌ Clears any existing value |
| Whitespace only, Treat whitespace as no value on (default) | No value | ❌ Clears any existing value |
| Whitespace only, Treat whitespace as no value off | Literal whitespace | ✅ Stored (flagged (whitespace) in the portal) |
Concatenation with a missing operand (for example a + "." + null) |
A real, possibly malformed value | ✅ Stored as-is, bypassing the null/clear path |
| A non-empty value | That value | ✅ Stored |
The rule of thumb: reference a possibly-absent attribute and you risk clearing the target; concatenate one and you risk writing malformed data. Guard with Coalesce(), IIF(), and the IsNullOrEmpty / IsNullOrWhitespace checks, and test with sample data (including the missing-input case) before you deploy.
Common Scenarios¶
This section shows practical examples of expressions you will use when setting up identity synchronisation.
Enabling and Disabling User Accounts¶
Active Directory uses a userAccountControl attribute to control whether an account is enabled or disabled. The common values are:
| Value | Meaning |
|---|---|
512 |
Normal account, enabled |
514 |
Normal account, disabled |
66048 |
Normal account, enabled, password never expires |
Enable or disable based on employee status:
This reads as: "If the Employee Status is Active, set the value to 512 (enabled), otherwise set it to 514 (disabled)."
Check if an account is currently disabled:
Setting Account Expiration Dates¶
Active Directory stores expiration dates in a special format called FILETIME. JIM provides functions to convert between normal dates and this format automatically.
Set the account expiration from an employee's end date:
Convert an AD expiration date back to a readable date:
Important notes:
- If the date attribute is empty or missing, these functions return nothing (null), which clears the target (any existing value is removed); see Nulls, Missing Inputs, and Whitespace
- AD treats
0and very large numbers as "never expires" --FromFileTime()returns nothing for these values ToFileTime()safely handles empty text, missing values, and invalid dates by returning nothing
Building Email Addresses¶
For "Jane Smith", this produces: jane.smith@company.com
Building a Display Name with Title¶
Coalesce uses the title if one exists, otherwise uses an empty string -- avoiding a "null" appearing in the output.
Building Distinguished Names (DNs)¶
When constructing LDAP distinguished names, always use EscapeDN() to handle special characters (commas, quotes, etc.) in names:
Without EscapeDN(), a name like "Smith, Jane" would break the DN because of the comma.
Generating Initials¶
For "Jane Smith", this produces: JS
Placing Users in Different OUs by Department¶
IIF(Eq(mv["Department"], "IT"),
"OU=IT,OU=Users,DC=domain,DC=local",
IIF(Eq(mv["Department"], "HR"),
"OU=HR,OU=Users,DC=domain,DC=local",
"OU=General,OU=Users,DC=domain,DC=local"))
This nests IIF functions to check multiple conditions: first IT, then HR, with a default of General for everyone else.
Adding a Prefix to Account Names¶
IT department users get a "tech-" prefix; everyone else keeps their account name as-is.
Splitting Delimited Values into a List¶
When a source system stores multiple values in a single field separated by a delimiter (e.g., "COURSE1|COURSE2|COURSE3"), use Split() to break them into separate values.
Example -- training courses from a CSV file:
The source CSV has pipe-separated courses in a single column:
Use this expression to create individual values on the Metaverse Object:
This creates three separate values: SOFT101, SOFT201, and SEC101.
Notes:
- Empty entries are automatically removed
- Whitespace is trimmed from each value
- Returns nothing for missing or empty input
- The delimiter can be any text (e.g.,
",","|",";")
Combining a List into a Single Value¶
When exporting to a system that does not support multi-valued attributes, use Join() to combine them into one delimited value.
If the attribute has the values Admin, Users, and Developers, this produces: Admin|Users|Developers
Notes:
- Returns nothing for empty lists
- Empty and missing values are filtered out
- Uses a comma as the default delimiter if none is specified
Validation and Troubleshooting¶
Validation¶
JIM validates expressions when you save a Synchronisation Rule. If an expression has a syntax error, you will see an error message indicating what went wrong and where in the expression the problem is.
Common errors:
- Missing closing quote or bracket
Check that every"and(has a matching pair. - Unknown function name
Check the spelling; function names are case-sensitive. - Wrong number of parameters
Check the function reference above for the correct parameters. - Syntax errors
Look for misplaced operators or missing commas between function parameters.
Troubleshooting¶
When an expression does not produce the result you expect:
- Check the attribute name spelling
Attribute names are matched case-insensitively, somv["department"]andmv["Department"]are equivalent; casing is never the cause. Do check the name is spelled correctly and matches an attribute that exists in the JIM admin UI. - Use
Eq()for text comparisons
Using==for text is a common mistake (see String Comparison). - Check for missing values
If an attribute does not exist on the object, it returns nothing (null), which can clear the target or, in a concatenation, produce a malformed value. UseCoalesce()orIsNullOrEmpty()to handle this; see Nulls, Missing Inputs, and Whitespace for the full picture. - Test with sample data
Use the expression test feature in the Synchronisation Rule editor to try your expression with real attribute values before saving. - Check the worker logs
If expressions fail during sync, the worker service logs the error details.
Best Practices¶
-
Always use
Eq()for text comparisons
The==operator can give incorrect results when comparing attribute values. -
Handle missing values
UseCoalesce()to provide a fallback, orIsNullOrEmpty()to check before using a value. This prevents an expression clearing the target or writing malformed data when an attribute has not been populated yet; see Nulls, Missing Inputs, and Whitespace. -
Always use
EscapeDN()in distinguished names
This prevents special characters in names from breaking LDAP paths. -
Test before saving
Use the expression test feature in the UI to verify your expressions with sample data. -
Keep expressions simple
If an expression is getting complex, consider splitting the logic across multiple attribute mappings. -
Document complex expressions
Add a note in the Synchronisation Rule's description explaining what complex expressions do, so the next person can understand them.