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:
| Write | For |
|---|---|
'text' or "text" | A string. Numbers, true, false, null, arrays ['a', 'b'] and objects also work. |
config.subdomain | A 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, !a | Logic. a || 'default' gives the default when a is missing, empty, 0 or false, else a. |
a ? b : c | A 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:
| Function | Returns |
|---|---|
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:
| Root | Holds |
|---|---|
config | The install's shared configuration and the chosen option's configuration, merged into one object: config.subdomain, config.api_token. |
credentials | What the option's credentials stage produced: credentials.access_token, credentials.refresh_token, and any extra values. |
token_response | The vendor's token response, after a token call. Use it in oauth2_token_response.extra: token_response.scope. |
auth | auth.key, the chosen option's key. |
request | The 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:
| Root | Holds |
|---|---|
identity | The identity being provisioned (fields below). |
generated | The value the profile's usernameGenerator produced, so other attributes can be built from it: generated + '@' + split(identity.email, '@')[1]. |
roles | The slugs of the identity's Owlie roles. |
groups | The Owlie groups the identity belongs to. |
formData | The 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:
| Field | Holds |
|---|---|
id | The identity's Owlie id. |
type, status | HUMAN or SERVICE_ACCOUNT; ACTIVE or DISABLED. |
email, secondary_email | Email addresses. |
first_name, last_name, middle_name, preferred_name, display_name, pronouns | Names. |
title, department, division, business_unit, organization, team, cost_center | Where the person works. |
location, region, timezone, preferred_language, work_phone, mobile_phone | Place and contact details. |
employee_id, external_id, employee_type, employment_status, is_manager, manager_id | Employment details. manager_id is the manager's Owlie identity id. |
job_code, job_family, job_level, job_function | Job classification. |
hire_date, start_date, end_date, contract_end_date, seniority_date | Dates as YYYY-MM-DD. |
lifecycle_state, tags | The identity's lifecycle state and its tags. |
custom | The 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)" }.