Skip to content

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:

Eq(mv["Employee Status"], "Active") && Eq(mv["Department"], "IT")

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:

Eq(Lower(mv["Status"]), "active")

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:

IIF(Eq(mv["Employee Status"], "Active"), 512, 514)

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:

IIF(HasBit(cs["userAccountControl"], 2), "Disabled", "Enabled")

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:

ToFileTime(mv["Employee End Date"])

Convert an AD expiration date back to a readable date:

FromFileTime(cs["accountExpires"])

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 0 and 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

Lower(mv["First Name"]) + "." + Lower(mv["Last Name"]) + "@company.com"

For "Jane Smith", this produces: jane.smith@company.com

Building a Display Name with Title

Coalesce(mv["Title"], "") + " " + mv["First Name"] + " " + mv["Last Name"]

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:

"CN=" + EscapeDN(mv["Display Name"]) + ",OU=Users,DC=domain,DC=local"

Without EscapeDN(), a name like "Smith, Jane" would break the DN because of the comma.

Generating Initials

Upper(Left(mv["First Name"], 1)) + Upper(Left(mv["Last Name"], 1))

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

IIF(Eq(mv["Department"], "IT"), "tech-" + mv["Account Name"], mv["Account Name"])

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:

employeeId,coursesCompleted
E001,"SOFT101|SOFT201|SEC101"

Use this expression to create individual values on the Metaverse Object:

Split(cs["coursesCompleted"], "|")

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.

Join(mv["Group Memberships"], "|")

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:

  1. Check the attribute name spelling
    Attribute names are matched case-insensitively, so mv["department"] and mv["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.
  2. Use Eq() for text comparisons
    Using == for text is a common mistake (see String Comparison).
  3. 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. Use Coalesce() or IsNullOrEmpty() to handle this; see Nulls, Missing Inputs, and Whitespace for the full picture.
  4. Test with sample data
    Use the expression test feature in the Synchronisation Rule editor to try your expression with real attribute values before saving.
  5. Check the worker logs
    If expressions fail during sync, the worker service logs the error details.

Best Practices

  1. Always use Eq() for text comparisons
    The == operator can give incorrect results when comparing attribute values.

  2. Handle missing values
    Use Coalesce() to provide a fallback, or IsNullOrEmpty() 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.

  3. Always use EscapeDN() in distinguished names
    This prevents special characters in names from breaking LDAP paths.

  4. Test before saving
    Use the expression test feature in the UI to verify your expressions with sample data.

  5. Keep expressions simple
    If an expression is getting complex, consider splitting the logic across multiple attribute mappings.

  6. Document complex expressions
    Add a note in the Synchronisation Rule's description explaining what complex expressions do, so the next person can understand them.