Skip to main content

Expressions

A connector computes some values when it runs rather than fixing them in the manifest: a base URL built from a subdomain, an Authorization header built from a token, a new account's email built from the identity. These are expressions in OXL, Owlie's expression language. Each place that takes an expression gives it different values to read, called roots; this page lists them, and the syntax and functions every expression shares.

Syntax​

An expression reads like a JavaScript expression without statements or method calls:

WriteFor
'text' or "text"A string. Numbers, true, false, null, arrays ['a', 'b'] and objects also work.
config.subdomainA property. config['sub' + 'domain'] and items[0] read by computed name or index.
'https://' + config.subdomain+ adds numbers and joins strings. -, *, /, % work on numbers.
a == b, a != b, <, >=Comparisons. There is no === or !==; write == and !=.
a && b, a || b, !aLogic. a || 'default' gives the default when a is missing, empty, 0 or false, else a.
a ? b : cA condition.
'x' in ['x', 'y']Whether an array holds a value, or a string contains another.
lower(identity.email)A function, called by name with its arguments.

An expression that does not parse, such as one with a ===, fails when the connector runs, so Owlie checks every expression when you import, test or publish a connector and names the field, the parser's message and the position (see Validation issues).

Functions are called by name, never as methods: write join(config.scopes, ' '), not config.scopes.join(' '), which does not work. A property that does not exist reads as empty instead of failing, and joining an empty value to a string writes the text undefined. Wrap a value that can be missing in coalesce(value, '') or string(value) before joining it.

Functions​

These functions are available in authentication, baseline and sync profile expressions:

FunctionReturns
upper(s), lower(s), trim(s)The string in upper or lower case, or without surrounding spaces.
startsWith(s, prefix), endsWith(s, suffix), contains(s, part)Whether the string starts with, ends with or contains the other. contains also takes an array.
replace(s, search, replacement)The string with the first occurrence of search replaced.
split(s, delimiter), substring(s, start, end?)The parts of a string, or a slice of it.
join(array, delimiter?)The array's items joined with the delimiter (a comma by default).
first(array), last(array), unique(array), flat(array), sort(array), reverse(array)The first or last item, or a new array.
string(v), number(v), bool(v), typeOf(v)The value converted, or its type name. string turns a missing value into ''.
coalesce(a, b, ...), isEmpty(v), length(v)The first value that is set, whether a value is missing or empty, or its length.
keys(object), values(object)An object's keys or values.
lookup(value, mapping, default?)mapping[value], or the default when the mapping has no such key.
now()The current time in milliseconds.
password(length?)A random password with lower- and upper-case letters, digits and symbols (16 characters by default).

Authentication expressions​

Every { "expr": "..." } node in an authentication option is an expression. It can read only these roots:

RootHolds
configThe install's shared configuration and the chosen option's configuration, merged into one object: config.subdomain, config.api_token.
credentialsWhat the option's credentials stage produced: credentials.access_token, credentials.refresh_token, and any extra values.
token_responseThe vendor's token response, after a token call. Use it in oauth2_token_response.extra: token_response.scope.
authauth.key, the chosen option's key.
requestThe request being authorized: request.method, request.url.

The connect stage (browser consent) is evaluated before any token exists, so it reads only config and auth.

{
"header": { "Authorization": { "expr": "'Bearer ' + credentials.access_token" } },
"base_url": { "expr": "'https://' + config.subdomain + '.acmeprojects.example/api/v2'" }
}

Baseline expressions​

In an account entity's provisioning_profile, a baseline attribute { "type": "expression", "value": "..." } is evaluated for each identity Owlie provisions:

RootHolds
identityThe identity being provisioned (fields below).
generatedThe value the profile's usernameGenerator produced, so other attributes can be built from it: generated + '@' + split(identity.email, '@')[1].
rolesThe slugs of the identity's Owlie roles.
groupsThe Owlie groups the identity belongs to.
formDataThe answers to the request form, when the access came from a request that had one.

identity has these fields; any of them can be empty:

FieldHolds
idThe identity's Owlie id.
type, statusHUMAN or SERVICE_ACCOUNT; ACTIVE or DISABLED.
email, secondary_emailEmail addresses.
first_name, last_name, middle_name, preferred_name, display_name, pronounsNames.
title, department, division, business_unit, organization, team, cost_centerWhere the person works.
location, region, timezone, preferred_language, work_phone, mobile_phonePlace and contact details.
employee_id, external_id, employee_type, employment_status, is_manager, manager_idEmployment details. manager_id is the manager's Owlie identity id.
job_code, job_family, job_level, job_functionJob classification.
hire_date, start_date, end_date, contract_end_date, seniority_dateDates as YYYY-MM-DD.
lifecycle_state, tagsThe identity's lifecycle state and its tags.
customThe tenant's custom identity attributes: identity.custom.badge_number.

Baseline expressions also have relatedAccount(identityId, attribute), which returns the value of attribute on the account that another identity has in the same integration, such as the manager's account id for a manager field: relatedAccount(identity.manager_id, 'id'). It returns null when that identity has no account there yet.

A baseline never contains customer-specific values such as an email domain: build them from the identity, as in lower(identity.email).

Sync profile expressions​

An account entity's sync_profile.is_active, is_deleted and is_person, and each matching rule's externalExpression, are evaluated for each record Sync reads. Their roots are the record's own top-level properties, so an account record with a status property is tested as status != 'deactivated', a SCIM-style record as active != false, and a matching rule reads the record's email as email. is_active, is_deleted and is_person must give true or false; any other result counts as unknown.

Values in a definition​

In an LDAP, SCIM, SQL or flat-file definition.json, a field that an install may change is written as { "$oxl": "config.<name>" }, naming a declared configuration field of the same type. That is the only expression those fields accept. The one exception is a SCIM definition's baseUrl, which takes a full expression over config with a single function, encode, which percent-encodes a value for a URL path: { "$oxl": "'https://api.example.com/scim/v2/' + encode(config.workspace_id)" }.