When exporting user accounts from JumpCloud to an external destination directory, like Active Directory or Google Workspace, using expression language (Expr) allows you to transform and manipulate user data into a specific format. This goes beyond simple one-to-one attribute mapping ensuring you have the precise data you want and need for your organization.
Prerequisites:
- Basic understanding of attribute mapping concepts
- Familiarity with the Expr expression language
Important Considerations:
- Implementation - the specific syntax and supported functions for expressions can vary slightly between different directories. Always refer to the external directory's documentation
- Testing - it's crucial to test your attribute mappings and expressions thoroughly to ensure that user data is being transformed and mapped correctly. Be aware of potential errors, such as mismatched data types, accessing null or non-existent attributes or using incorrect function arguments
- Simplicity - while expressions offer great flexibility, it's important to keep them as simple and maintainable as possible. Complex expressions can be difficult to troubleshoot and understand
Overview
What are Expressions?
The primary use of an Expr language in attribute mapping is defining how data from a source (like a JSON object from an API call) is transformed and mapped to a destination (like a user object in an external database). The core of an Expr expression is a statement that assigns a source field's value to a destination field, i.e., source_field_path = destination_field.
Why use Expressions?
Expressions bring a wealth of benefits to attribute mapping, significantly enhancing the flexibility and power of user provisioning and synchronization. Some of the key advantages:
- Data transformation and manipulation - converts data between different formats and combines multiple source attributes into a single target attribute. Can also be used to extract specific parts of an attribute
- Improved efficiency - automates data manipulation and formatting tasks, streamlining user provisioning and management
- Security - ensures programs do not access unrelated memory or introduce memory vulnerabilities
- Terminating - prevents infinite loops
How do I create an Expression?
By understanding the basics, you can effectively use Expr to create expressions for dynamic and flexible attribute mapping. Consult the Expr documentation for more information.
Configuring Expressions
To add an Expression
- Log in to the JumpCloud Admin Portal.
If your data is stored outside of the US, check which login URL you should be using depending on your region. If your organization uses LDAP, RADIUS, or requires firewall allow list configuration, the Fully Qualified Domain Names (FQDNs) will also be region specific. See JumpCloud Data Centers for the URLs, FQDNs, and IP addresses.
- Go to Identity Management > Active Directories or Cloud Directories.
- Select YOUR INTEGRATION from the list and select the Attribute Mappings tab and click Edit. The Optional Mappings table will open.
- Scroll to the bottom of the table, click +Add Attribute and then select Expression.
- Enter the expression in the JumpCloud Attribute field
- From the <Directory> Attribute dropdown, select the corresponding (destination) attribute.
- Repeat these steps for additional attributes.
- Click Preview Mappings to review the User Schema.
JumpCloud EXPR Functions
| Function Name | Function | What it does | Parameters | Examples |
|---|---|---|---|---|
| nullOrEmpty | nullOrEmpty(value) | Checks if a piece of information is completely missing (null) or if it's just empty text (a blank space). If the information is a number or a list, it is considered not empty. | value: The piece of user information you want to check (e.g., an ID or email address). | nullOrEmpty(providerUser.externalId) ? providerUser.externalId : jcUser.id nullOrEmpty(jcUser.email) ? "unknown@example.com" : jcUser.email |
| notNullOrEmpty | notNullOrEmpty(value) | Checks if the information actually exists and has content. For text, it must have at least one character. For a list, it must have at least one item. | value: The user data you are checking to ensure it exists before you use it. | notNullOrEmpty(jcUser.email) ? jcUser.email : providerUser.userName |
| toScimPhoneNumbers | toScimPhoneNumbers(phoneNumber) | Turns a single JumpCloud phone number (like the digits and type) into the list format that SCIM needs. It sets the type as "work" and "primary." If there is no phone data, it returns an empty list. | phoneNumber: A block of data that holds the phone number digits and, optionally, the type (e.g., "work" or "mobile"). | toScimPhoneNumbers(find(jcUser.phoneNumbers, .type == 'work') ?? first(jcUser.phoneNumbers)) |
| toScimAddresses | toScimAddresses(address) | Turns a single JumpCloud address (like street, city, state) into the list format that SCIM needs. It combines street lines and sets the address type as "work" and "primary." If there is no address data, it returns an empty list. | address: A block of data containing all the parts of the address (street, city, state/region, zip/postal code, etc.). | toScimAddresses(find(jcUser.addresses, .type == 'work') ?? first(jcUser.addresses)) |
| toScimEmails | toScimEmails(email) | Takes a single email address and puts it into the list format that SCIM requires. This is useful when the destination system expects a list. The email is marked as "work" and "primary." | email: The actual email address, provided as text. | toScimEmails(jcUser.email) |
| toScimEntitlements | toScimEntitlements(entitlement) | Creates one entitlement record to be included in the SCIM list of entitlements. It uses the value, type, and display name you provide and marks it as primary. If you don't provide input, it returns an empty list. | entitlement: A block of data that contains the entitlement's value, type, and display name. | toScimEntitlements(jcUser.entitlements) |
| setDefaults | setDefaults(m, defaults) | Fills in any missing data in your main data block (m) using backup values from a separate data block (defaults). Important: If the same piece of information is in both blocks, the value from the defaults block is used. If your main data block (m) is missing, the function returns nothing. | m: The primary block of data you are starting with (e.g., a user's address). defaults: The block of backup values used to fill in any missing parts of the primary block (m). | setDefaults({ "region": "CA", "country": "US" }, { "region": "" }) |
| isMemberOfAny | isMemberOfAny(groups, nameOrIDs) | Checks if a user is a member of any of the groups you list. It can check by group name or unique ID, ignoring upper/lower case in names. The result is always true or false. | groups: The list of all groups the user belongs to (usually jcGroups). nameOrIDs: The name, unique ID, or a list of names/IDs of the specific groups you are looking for. | isMemberOfAny(jcGroups, "engineering_group")isMemberOfAny(jcGroups, ["engineering_group", "sales_group"]) |
| isMemberOfAll | isMemberOfAll(groups, nameOrIDs) | Checks if a user is a member of every single group you list. If the user is missing even one group, the answer is false. Matching works like isMemberOfAny. | groups: The list of all groups the user belongs to (usually jcGroups). nameOrIDs: The name, unique ID, or a list of names/IDs of all the required groups. | isMemberOfAll(jcGroups, "engineering_group") isMemberOfAll(jcGroups, ["engineering_group","sales_group"]) |
| getGroups | getGroups(groups) getGroups(groups, field) getGroups(groups, fields, "string") | Gathers a user's group information in different ways: you can get all details (name, ID, attributes), a list of just one specific detail from each group (e.g., only the name), or a list of several specific details. Adding the text "string" as the third parameter makes sure the final output is simple text. | groups: The user's list of groups (jcGroups). field or fields (optional): The specific piece(s) of information you want to extract from each group (e.g., the group's name or a custom attribute like costCenter). "string" (optional third): Include this text if you need the result to be simple text strings. | getGroups(jcGroups) getGroups(jcGroups,"name") getGroups(jcGroups,"id") getGroups(jcGroups,"name", "string") getGroups(jcGroups, ["name", "roles"]) |
| getGroupAttr | getGroupAttr(groups, nameOrID, attrPath) getGroupAttr(groups, nameOrID, attrPath, default) | Looks up a specific piece of information (an "attribute") from a single group (which you find by its name or ID). If the group or the information is missing, it returns a backup value (default) if you provided one; otherwise, it returns blank text. | groups: The user's list of groups (jcGroups). nameOrID: The name or unique ID of the group you want to search. attrPath: The "path" (using dots) to the exact information you want (e.g., role). default (optional): The backup value to use if the group or the information you asked for cannot be found. | getGroupAttr(jcGroups,"admin_group", "role") getGroupAttr(jcGroups,"admin_group", "role", "user") |
| filterGroups | filterGroups(groups, attrPath, matchValue) | Narrows down the list of groups to keep only the ones where a specific piece of information matches one of the values you provide. The check ignores upper/lower case. The result is a list of the matching groups and all their details. | groups: The user's list of groups (jcGroups). attrPath: The specific piece of information (attribute) you want to check in each group (e.g., role). matchValue: A text value or a list of text values that the attribute must equal to be included in the result. | filterGroups(jcGroups, "role", "admin") filterGroups(jcGroups, "role", ["admin", "user"]) |
| findFirstGroupAttr | findFirstGroupAttr(groups, attrPath, priorityList) findFirstGroupAttr(groups, attrPath, priorityList, default) | Checks a list of groups, in the order you specify, and returns the first piece of non-empty information (an "attribute") it finds. This lets you prioritize data from certain groups. If the information is not found in any group, it returns the backup value (default) if you provided one; otherwise, it returns blank. | groups: The user's list of groups (jcGroups). attrPath: The specific piece of information (attribute) you are trying to find. priorityList: A list of group names or IDs, listed in the exact order you want them checked. default (optional): The backup value to use if the required information cannot be found in any of the groups. | findFirstGroupAttr(jcGroups, "role",["admin_group","user_group" ]) findFirstGroupAttr(jcGroups, "role",["admin_group","user_group"],"user") |
| toID | toID("...") toID('...') | A special shortcut. Before your expression runs, JumpCloud automatically swaps the group name you put inside the parentheses for that group's permanent, unique ID (a sequence of numbers and letters). It is best to use this when you need to refer to a specific group in a way that won't break if someone renames the group later. | "..." or '...': Group name between single or double quotes. The match is case sensitive. | getGroupAttr(jcGroups, toID("Engineering"), "costCenter") |
Expressions Examples
When mapping user attributes, define how data from a source object (like an API response) should be assigned to a destination object (like a user profile) by using different types of expressions:
Direct Mapping - the simplest expression when you assign a source attribute's value to a destination attribute using an operator:
user.email = source.email_address
- source.email_address - source attribute
- user.email - destination attribute
user.firstName = source.contact_info.first_name
- user.email - destination attribute
- source.contact_info.first_name - source attribute
Conditional Logic - use a ternary operator (? :) to set a value based on a condition:
- Assigning a role:
user.role = source.is_admin ? 'administrator' : 'standard_user'
- user.role - destination attribute
- = - assignment operator that tells the system to assign the value on the right side of the equals sign to the field on the left
- source.is_admin - conditional check. It accesses the value of the is_admin field from the source data. This is typically a boolean value (true or false)
- ? - operator that introduces the ternary conditional or if/then/else statement. It checks the value of the preceding condition (source.is_admin)
- 'administrator' - "true" value. If the source.is_admin field is true, the string 'administrator' is assigned to user.role
- : - separates the true and false values
- 'standard_user' - "false" value. If the source.is_admin field is false, the string 'standard_user' is assigned to user.role
- Setting a status based on a numeric code:
user.status = source.user_status_code == 1 ? 'active' : 'inactive'
- user.status - destination attribute
- = - assignment operator that tells the system to assign the value on the right side of the equals sign to the field on the left
- source.user_status_code == 1 - conditional check. It compares the value of the user_status_code field from the source data to the number 1. The == is a comparison operator that returns a boolean (true or false)
- ? - operator initiates the ternary conditional or if/then/else statement. It evaluates the preceding condition
- 'active' - "true" value. If source.user_status_code is equal to 1, the string 'active' is assigned to user.status
- : - separates the true and false values
- 'inactive' - "false" value. If source.user_status_code is anything other than 1, the string 'inactive' is assigned to user.status
Data Transformation - expressions use built-in functions to manipulate data:
- String Functions - combine or format text:
user.fullName = source.firstName + ' ' + source.lastName
- user.fullName - destination attribute
- = - assignment operator that tells the system to assign the value on the right side of the equals sign to the field on the left
- source.firstName - first source field. It accesses the value of the firstName field from the source data, which is an object often named source or api_data
- + - concatenation operator. In this context, it's a string operator that joins the two strings on either side of it
- ' ' - string literal . It represents a single space character, which is inserted between the first and last names
- source.lastName - second source field. It accesses the value of the lastName field from the source data
user.username = to_lower(source.login_id)
- user.username - destination attribute
- = - assignment operator that tells the system to assign the value on the right side of the equals sign to the field on the left
- to_lower (...) - function that converts a string to all lowercase characters ensuring all usernames are stored in a consistent format
- source.login_id - source attribute. It's the original value, which in this case is a user's login ID, coming from the source system
Type Conversion - change a value's data type:
user.hireDate = to_date(source.hire_date_timestamp)
- user.hireDate - destination attribute
- = - assignment operator that tells the system to assign the value on the right side of the equals sign to the field on the left
- to_date(...) - built-in function in the expression language that takes a value—in this case, a timestamp—and converts it into a date object. This is essential for systems that don't recognize raw numeric timestamps as valid dates
- source.hire_date_timestamp - source attribute. It's the original value, which is a timestamp (a large number representing the seconds or milliseconds since a specific date, like January 1, 1970)
Array/List Access - pull a single item from a list:
user.primaryGroup = source.groups[0]
- user.primaryGroup - destination attribute
- = - assignment operator, which sets the value of the destination attribute
- source.groups - source attribute
- [0] - index operator. In most programming languages, including Expr, [0] refers to the very first item in a list or array because lists are "zero-indexed"
Handling Missing Data - use the coalescing operator (??) to provide a default value if a source field is null or doesn't exist:
user.department = source.department ?? 'Unassigned'
- user.department - destination attribute
- = - assignment operator, which sets the value of the destination attribute
- source.department - source attribute
- ?? - coalescing operator that checks if the value on its left (source.department) is null or empty
- 'Unassigned'- default value. If the coalescing operator finds that source.department is null or empty, it uses this string instead
user.phone = source.mobile_number ?? source.home_phone
- user.phone - destination attribute
- = - assignment operator, which sets the value of the destination attribute (user.phone field)
- source.mobile_number - primary source field. The expression first attempts to get the value from here
- ?? - coalescing operator. It checks if the value on its left (source.mobile_number) is null or empty
- source.home_phone - fallback source field. If the coalescing operator finds that source.mobile_number is null or empty, it uses the value of source.home_phone instead