Skip to main content

Type definitions

CEL expressions use typed values. Understanding these types helps you write correct expressions and avoid runtime errors.

Primitive types

TypeDescriptionExample values
stringText value"Engineering", "user@company.com"
boolBoolean true/falsetrue, false
intInteger number0, 42, -1
doubleFloating-point number3.14, 0.5
bytesBinary dataRarely used directly

Time types

TypeDescriptionHow to create
timestampA point in time (UTC)now(), timestamp("2025-01-01T00:00:00Z"), time.parse(...)
durationA length of timeduration("24h"), duration("30m"), duration("720h")
Duration format: Use h for hours, m for minutes, s for seconds. Examples: "2h" (2 hours), "30m" (30 minutes), "720h" (30 days). Timestamp arithmetic:
now() + duration("24h")              // 24 hours from now
now() - duration("720h")             // 30 days ago
timestamp1 - timestamp2              // Returns a duration

Collection types

TypeDescriptionExample
list<T>Ordered list of items[user1, user2], ["a", "b", "c"]
map<K,V>Key-value mappingsubject.profile (map of string to any)
List operations:
size(myList)                         // Number of items
myList[0]                            // First item (0-indexed)
"value" in myList                    // Check membership
myList + otherList                   // Concatenate lists
myList.filter(x, x.status == UserStatus.ENABLED)  // Filter
myList.map(x, x.email)               // Transform
myList.exists(x, x.department == "IT")  // Any match?
myList.all(x, x.status == UserStatus.ENABLED)     // All match?

Enum types

Enums are predefined constants. Always use the full enum name (e.g., UserStatus.ENABLED, not just ENABLED).

UserStatus

ValueMeaning
UserStatus.ENABLEDUser is active
UserStatus.DISABLEDUser is disabled
UserStatus.DELETEDUser is deleted

UserType

ValueMeaning
UserType.HUMANRegular human user
UserType.AGENTAutomated agent
UserType.SERVICEService account
UserType.SYSTEMSystem account

TaskOrigin

ValueMeaning
TaskOrigin.WEBAPPCreated in ConductorOne web interface
TaskOrigin.SLACKCreated via Slack integration
TaskOrigin.APICreated via API
TaskOrigin.JIRACreated via Jira integration
TaskOrigin.COPILOTCreated via Copilot
TaskOrigin.PROFILE_MEMBERSHIP_AUTOMATIONCreated by automation
TaskOrigin.TIME_REVOKECreated by time-based revocation

AppUserStatus (for triggers)

Used in ctx.trigger.oldAccount.status.status and ctx.trigger.newAccount.status.status:
ValueMeaning
APP_USER_STATUS_ENABLEDAccount is active
APP_USER_STATUS_DISABLEDAccount is disabled
APP_USER_STATUS_DELETEDAccount is deleted

TimeFormat

ConstantFormatExample output
TimeFormat.RFC3339ISO 8601 / RFC 33392025-10-22T14:30:00Z
TimeFormat.DATEDate only2025-10-22
TimeFormat.DATETIMEDate and time2025-10-22 14:30:00
TimeFormat.TIMETime only14:30:00

Object types

These are complex types returned by functions or available as variables.
User vs AppUser: These are different types. A User is a person in the ConductorOne directory (your identity provider sync). An AppUser is that person’s account within a specific application (their GitHub account, Okta account, etc.). One User can have many AppUsers across different apps.

User

Represents a person in the ConductorOne directory. This is your organization’s user record, typically synced from an identity provider. Returned by: FindByEmail, GetByID, GetManagers, DirectReports, GetEntitlementMembers Available as: subject, elements of appOwners, ctx.trigger.oldUser, ctx.trigger.newUser
FieldTypeDescription
idstringUnique user identifier
emailstringPrimary email address
emailslist<string>All email addresses
displayNamestringDisplay name
usernamestringUsername
usernameslist<string>All usernames
departmentstringDepartment
jobTitlestringJob title
employmentTypestringEmployment type (e.g., “Full Time”)
employmentStatusstringEmployment status (e.g., “Active”)
statusUserStatusUser status enum
directoryStatusUserStatusDirectory sync status
typeUserTypeUser type enum
managerstringManager’s email
manager_idstringManager’s user ID
profilemapCustom profile attributes
attributesmapCustom user attributes

Group

Represents a ConductorOne group. Returned by FindByName.
FieldTypeDescription
idstringGroup identifier
app_idstringApp ID the group belongs to
display_namestringGroup display name

AppUser

Represents a user’s account within a specific connected application (e.g., their GitHub account, Salesforce account, AWS IAM user). Different from User which is the directory-level identity. Returned by: ListAppUsersForUser Available as: ctx.trigger.oldAccount, ctx.trigger.newAccount (in account-change triggers)
FieldTypeDescription
idstringApp user identifier
displayNamestringDisplay name
usernamestringUsername in the app
usernameslist<string>All usernames
emailstringEmail in the app
emailslist<string>All emails
employeeIdslist<string>Employee IDs
statusAppUserStatusNested status object
status.statusenumAPP_USER_STATUS_ENABLED/DISABLED/DELETED
status.detailsstringStatus details
profilemapCustom profile attributes
attributesmapAttribute mappings

Task

Represents an access request or task in ConductorOne. See Task object for all fields. Available as: task (in policy expressions)

TaskAnalysis

Analysis data attached to a task, including access conflict information. See Task analysis object for all fields. Available as: task.analysis (in policy expressions)

Entitlement

The entitlement (permission/role) being requested. See Entitlement object for all fields. Available as: entitlement (in policy expressions)

IP

Represents an IP address with properties for classification. See IP address object for all fields. Created by: ip("1.2.3.4")

CIDR

Represents a network range for IP matching. See IP CIDR object for all methods. Created by: cidr("10.0.0.0/8")

Context (ctx)

Workflow execution context containing trigger data and step outputs. See Context object for all fields. Available as: ctx (in automation triggers and workflow steps)

Built-in variables

These variables are automatically available in specific contexts.
VariableTypeAvailable inDescription
subjectUserPolicies, Groups, Automations, Campaigns, Account provisioningThe current user being evaluated
taskTaskPolicies onlyThe current access request
entitlementEntitlementPolicies onlyThe entitlement being requested
appOwnerslist<User>Policy step approvers onlyOwners of the application
ctxContextAutomations onlyWorkflow context and trigger data
ipIPPolicies, AutomationsRequestor’s IP address (when available)

Functions

These library functions let you interact with the ConductorOne system to look up whether a user has access to a certain application or entitlement, or to find the user or list of users who should review a task.

User library functions

FunctionAcceptsReturnsAvailability
c1.user.v1.HasAppuser, app IDBooleanPolicies, Groups, Automations, Account provisioning
c1.user.v1.HasEntitlementuser, app ID, entitlement IDBooleanPolicies, Groups, Automations, Account provisioning
c1.user.v1.GrantedFromEnrollmentuser, app ID, entitlement IDBooleanPolicy conditions only
c1.user.v1.AutomaticallyGrantedFromEnrollmentuser, app ID, entitlement IDBooleanPolicy conditions only
c1.user.v1.ListAppUsersForUseruser, app IDList of AppUserAutomations, Account provisioning
What can go wrong:
  • Invalid app ID or entitlement ID returns false for boolean functions (no error thrown)
  • ListAppUsersForUser returns empty list [] if user has no accounts in the app

Directory library functions

FunctionAcceptsReturnsAvailability
c1.directory.users.v1.FindByEmailemailuserPolicies, Groups, Automations, Account provisioning
c1.directory.users.v1.GetByIDuser IDuserPolicies, Groups, Automations, Account provisioning
c1.directory.users.v1.GetManagersuserlist of usersPolicies, Groups, Automations, Account provisioning
c1.directory.users.v1.DirectReportsuser or list of userslist of usersPolicies, Groups, Automations, Account provisioning
c1.directory.groups.v1.FindByNamegroup namegroupPolicies, Groups, Automations, Account provisioning
c1.directory.apps.v1.GetEntitlementMembersapp ID, entitlement IDlist of usersPolicies, Groups, Automations, Account provisioning
What can go wrong:
  • FindByEmail fails if email doesn’t exist in directory - verify emails before deploying
  • GetByID fails if user ID doesn’t exist
  • GetManagers returns empty list [] if user has no manager - this silently skips approval steps
  • DirectReports returns empty list [] if user has no reports
  • GetEntitlementMembers returns empty list [] if entitlement has no members
Critical: When GetManagers returns an empty list in a policy approver expression, the approval step is silently skipped rather than failing. Always add a fallback approver:
// DANGER without fallback: step skipped if no manager
c1.directory.users.v1.GetManagers(subject)

// SAFE with fallback: uses app owners if no manager
size(c1.directory.users.v1.GetManagers(subject)) > 0
  ? c1.directory.users.v1.GetManagers(subject)
  : appOwners
Go to an application or entitlement’s details page to look up its ID, or use Cone.
Function availability varies by context.Note that automation triggers do NOT have access to directory or user library functions - they can only access the ctx.trigger object and basic enums.

Time functions

ConductorOne provides comprehensive time functions for working with dates and times in CEL expressions. These functions are available in all CEL contexts (Policies, Groups, Automations, Account provisioning).

Core time functions

FunctionDescriptionExample
now()Get current timestampnow() > timestamp("2025-01-01T00:00:00Z")
time.parse(value, layout)Parse time from string (UTC)time.parse("2025-10-22", TimeFormat.DATE)
time.parse(value, layout, timezone)Parse time with timezonetime.parse("2025-10-22 14:30:00", TimeFormat.DATETIME, "America/New_York")
time.format(timestamp, layout)Format timestamp to string (UTC)time.format(now(), TimeFormat.RFC3339)
time.format(timestamp, layout, timezone)Format with timezonetime.format(now(), "Monday", "America/New_York")
time.unix(seconds)Convert Unix timestamp (seconds)time.unix(1734787200)
time.unix_ms(milliseconds)Convert Unix millisecondstime.unix_ms(1734787200123)
time.start_of(timestamp, unit)Get start of periodtime.start_of(now(), "day")
time.start_of(timestamp, unit, timezone)Get start of period in timezonetime.start_of(now(), "week", "America/New_York")
time.end_of(timestamp, unit)Get end of periodtime.end_of(now(), "month")
Time units for start_of/end_of:
  • "day" - Start/end of day (midnight)
  • "week" - Start/end of week (Monday/Sunday)
  • "month" - Start/end of month
  • "quarter" - Start/end of quarter (Jan/Apr/Jul/Oct)
  • "year" - Start/end of year

TimeFormat constants

See TimeFormat enum for all constants. You can also use custom Go time layouts like "2006-01-02" or "Monday, January 2, 2006".

Common time patterns

Check if date is recent:
// Check if user's hire date is within last 30 days (if stored in profile)
has(subject.profile.hire_date) &&
time.parse(subject.profile.hire_date, TimeFormat.DATE) > now() - duration("720h")

// Account modified this month (in automations)
has(ctx.trigger.newAccount.updated_at) &&
timestamp(ctx.trigger.newAccount.updated_at) >= time.start_of(now(), "month")
Business hours checking:
// Check if it's business hours (9 AM - 5 PM ET)
time.format(now(), "15", "America/New_York") >= "09" &&
time.format(now(), "15", "America/New_York") < "17"

// Check if it's after hours
time.format(now(), "15", "America/New_York") >= "17" ||
time.format(now(), "15", "America/New_York") < "09"
Day of week checking:
// Check if it's a weekend
time.format(now(), "Monday", "America/New_York") == "Saturday" ||
time.format(now(), "Monday", "America/New_York") == "Sunday"

// Check if it's a weekday
!(time.format(now(), "Monday") == "Saturday" || time.format(now(), "Monday") == "Sunday")
Parsing profile date fields:
// Parse contract end date and check if within 30 days
has(subject.profile.contract_end_date) &&
time.parse(subject.profile.contract_end_date, TimeFormat.DATE) - now() < duration("720h")

// Check if hire date is in the past
has(subject.profile.hire_date) &&
time.parse(subject.profile.hire_date, TimeFormat.DATE) < now()
Working with timezones:
// Start of day in company timezone
time.start_of(now(), "day", "America/New_York")

// Format date in user's timezone
time.format(now(), TimeFormat.DATETIME, "Europe/London")
Important:The now() function returns the same value throughout a single expression evaluation. All timestamps are stored in UTC internally; timezone parameters only affect parsing and formatting.

Objects

Subject object

The “subject” variable refers to the ConductorOne user.

Most common fields

These fields are used in the majority of condition expressions:
FieldData typeDescriptionExample usageCommon use cases
subject.emailstringUser’s email addresssubject.email.endsWith("@company.com")Distinguish between employees and contractors
subject.departmentstringUser’s departmentsubject.department == "Engineering"Department-based access control
subject.statusenumUser’s active statussubject.status == UserStatus.ENABLEDCheck if user is active
subject.typeenumUser type (human, service, system)subject.type == UserType.HUMANFilter by user type

Identity and contact fields

FieldData typeDescriptionExample usageCommon use cases
subject.idstringUnique user identifiersubject.id == "user123"Specific user targeting
subject.email.startsWithstringEmail prefix checksubject.email.startsWith("admin")Admin user identification
subject.email.endsWithstringEmail domain checksubject.email.endsWith("@company.com")Employee vs contractor
subject.managerstringUser’s manager emailsubject.manager == "boss@company.com"Manager-based routing
subject.manager_idstringUser’s manager IDsubject.manager_id != ""Check if user has manager
Both subject.manager (email) and subject.manager_id (ID) are available. Use manager for email-based comparisons and manager_id to check existence or for ID-based lookups with GetByID.

Organizational fields

FieldData typeDescriptionExample usageCommon use cases
subject.jobTitlestringUser’s job titlesubject.jobTitle == "Senior Developer"Role-based access
subject.employmentTypestringEmployment typesubject.employmentType == "Full Time"Employment status checks
subject.employmentStatusstringEmployment statussubject.employmentStatus == "Active"Active employee verification

Status and directory fields

FieldData typeDescriptionExample usageCommon use cases
subject.directoryStatusenumDirectory sync statussubject.directoryStatus == UserStatus.ENABLEDDirectory health checks
subject.profilemap[string]interfaceProfile attributessubject.profile.department == "IT"Custom profile data access
subject.attributes.<CUSTOM_USER_ATTRIBUTE>variesCustom user attributessubject.attributes.contractor == "true"Custom business logic
Use custom user attributes.You can write condition expressions that leverage the custom user attributes you’ve set up in ConductorOne. Any custom user attribute can be passed in to the subject.attributes.<CUSTOM USER ATTRIBUTE> property and used in your condition expressions.

User object

The user object is used when referencing other users in the system (not the current subject).
FieldData typeDescriptionExample usageCommon use cases
user.idstringUnique user identifieruser.id == "user123"Specific user targeting
user.emailstringUser’s email addressuser.email.endsWith("@company.com")Email-based filtering
user.displayNamestringUser’s display nameuser.displayName == "John Doe"Name-based identification
user.usernamestringUser’s usernameuser.username == "johndoe"Username-based filtering
user.departmentstringUser’s departmentuser.department == "Engineering"Department-based grouping
user.jobTitlestringUser’s job titleuser.jobTitle == "Manager"Role-based filtering
user.statusUserStatus enumUser’s active statususer.status == UserStatus.ENABLEDActive user verification
user.directoryStatusUserStatus enumDirectory sync statususer.directoryStatus == UserStatus.ENABLEDDirectory health checks
user.employmentTypestringEmployment typeuser.employmentType == "Full Time"Employment status filtering
user.employmentStatusstringEmployment statususer.employmentStatus == "Active"Active employee verification
user.profileJSONProfile attributesuser.profile.department == "IT"Custom profile data access

Task object

The task object is used in policy expressions to reference the current access request or task.
FieldData typeDescriptionExample usageCommon use cases
task.idstringUnique task identifiertask.id == "task123"Specific task targeting
task.numericIdstringNumeric task identifiertask.numericId == "12345"Numeric task reference
task.displayNamestringHuman-readable task nametask.displayName == "Access Request"Task identification
task.originTaskOrigin enumWhere the task was createdtask.origin == TaskOrigin.SLACKRoute based on creation source
task.isGrantPermanentBooleanWhether access is permanenttask.isGrantPermanent == truePermanent vs temporary access
task.grantDurationdurationHow long access is grantedtask.grantDuration > duration("2h")Time-based access control
task.subjectUserIdstringID of the user who is the subject of the tasktask.subjectUserId == task.requestorUserIdSelf-service access checks
task.requestorUserIdstringID of the user who created the tasktask.requestorUserId == "user123"Requestor-based routing
task.analysisanalysisTask analysis datatask.analysis.hasConflictViolationsAccess conflict detection
See TaskOrigin enum for all possible values.

Task analysis object

The task analysis object provides information about potential access conflicts and other analysis data.
FieldData typeDescriptionExample usageCommon use cases
task.analysis.idstringAnalysis identifiertask.analysis.id == "analysis123"Specific analysis reference
task.analysis.hasConflictViolationsBooleanWhether conflicts existtask.analysis.hasConflictViolationsCheck for access conflicts
task.analysis.conflictViolationsarray of stringsList of conflict IDs"conflict123" in task.analysis.conflictViolationsSpecific conflict detection

Entitlement object

The entitlement object can only be used in CEL expressions in policies. It does not work when writing CEL expressions to form ConductorOne groups.
FieldData typeDescriptionExample usageCommon use cases
entitlement.idstringEntitlement identifierentitlement.id == "entitlement123"Specific entitlement reference
entitlement.appIdstringApplication identifierentitlement.appId == "app123"Application-specific logic

IP address object

The IP address object is used for network-based access control and filtering.
FieldData typeDescriptionExample usageCommon use cases
ip.is4BooleanWhether IP is IPv4ip.is4IPv4-specific logic
ip.is6BooleanWhether IP is IPv6ip.is6IPv6-specific logic
ip.isPrivateBooleanWhether IP is privateip.isPrivateInternal network access
ip.isLoopbackBooleanWhether IP is loopbackip.isLoopbackLocalhost detection
ip.isGlobalUnicastBooleanWhether IP is global unicastip.isGlobalUnicastPublic IP detection
ip.isMulticastBooleanWhether IP is multicastip.isMulticastMulticast traffic detection
ip.isInterfaceLocalMulticastBooleanWhether IP is interface local multicastip.isInterfaceLocalMulticastLocal multicast detection
ip.isLinkLocalMulticastBooleanWhether IP is link local multicastip.isLinkLocalMulticastLink local multicast detection
ip.isUnspecifiedBooleanWhether IP is unspecifiedip.isUnspecifiedInvalid IP detection

IP CIDR object

The IP CIDR object is used for network range-based access control and filtering.
FunctionDescriptionExample usageCommon use cases
cidr('10.1.2.0/24').contains(ip('1.2.3.4'))Check if IP is within CIDR rangecidr('10.1.2.0/24').contains(ip('10.1.2.5'))Network-based access control
ip('10.1.2.5').within(cidr('10.1.2.0/24'))Check if IP is within CIDR rangeip('10.1.2.5').within(cidr('10.1.2.0/24'))Alternative syntax for range checking
cidr('10.1.2.0/24', '5.4.3.2/32')Create CIDR range with multiple networkscidr('10.1.2.0/24', '5.4.3.2/32')Multiple network range definition

Object usage reference

Understanding where each object can be used helps you write effective CEL expressions for different ConductorOne features.

Quick object reference table

ObjectPoliciesGroupsAutomationsCampaignsAccount provisioning
subject*
user*
task
task.analysis
entitlement
ctx
ip
ip CIDR
* In Automations, user data is accessed through the ctx object (e.g., ctx.trigger.newUser, ctx.trigger.oldUser)

Subject object

Available in: Policies Groups Automations Campaigns Account provisioning
Description: References the current user in the context
Usage: The most commonly used object across all CEL expression contexts
Context-specific examples: In Policies:
// Policy conditional - check if user is in Engineering department
subject.department == "Engineering"

// Policy expression - route to user's manager
c1.directory.users.v1.FindByEmail(subject.manager)
In Groups:
// Group membership - include all Engineering employees
subject.department == "Engineering" && subject.status == UserStatus.ENABLED
In Automations:
// Automation trigger - when user status changes
subject.status == UserStatus.DISABLED
In Campaigns:
// User selection - include contractors only
!subject.email.endsWith("@company.com")
In Account provisioning:
// Data mapping - use user's first name
subject.attributes.firstName

User object

Available in: Policies Groups Automations Campaigns ️ Account provisioning
Description: References other users in the system (not the current subject)
Usage: Used when you need to reference or compare against other users
Context-specific examples: In Policies:
// Policy expression - assign to specific user
c1.directory.users.v1.GetByID("user123")
In Groups:
// Group membership - include users by department
user.department == "Engineering"
In Automations:
// Automation step - modify specific user
user.status == UserStatus.DISABLED
In Campaigns:
// User selection - include users by status
user.status == UserStatus.ENABLED

Task object

Available in: Policies ️ Groups ️ Automations ️ Campaigns ️ Account provisioning
Description: References the current access request or task
Usage: Only available in policy expressions where there’s an active task context
Context-specific examples: In Policies:
// Policy conditional - check task origin
task.origin == TaskOrigin.SLACK

// Policy conditional - check for access conflicts
task.analysis.hasConflictViolations

// Policy conditional - time-based access
task.isGrantPermanent == false && task.grantDuration > duration("2h")

// Policy conditional - auto-approve self-service revocations
task.subjectUserId == task.requestorUserId

Task analysis object

Available in: Policies ️ Groups ️ Automations ️ Campaigns ️ Account provisioning
Description: Provides information about potential access conflicts and other analysis data
Usage: Only available in policy expressions for conflict detection and analysis
Context-specific examples: In Policies:
// Policy conditional - check for any conflicts
task.analysis.hasConflictViolations

// Policy conditional - check for specific conflict
"conflict123" in task.analysis.conflictViolations

Entitlement object

Available in: Policies ️ Groups ️ Automations ️ Campaigns ️ Account provisioning
Description: References the entitlement being requested
Usage: Only available in policy expressions where there’s an active entitlement request
Context-specific examples: In Policies:
// Policy conditional - check specific entitlement
entitlement.id == "entitlement123"

// Policy expression - assign to users with same entitlement
c1.directory.apps.v1.GetEntitlementMembers(entitlement.appId, entitlement.id)

IP address object

Available in: Policies ️ Groups Automations ️ Campaigns ️ Account provisioning
Description: Used for network-based access control and filtering
Usage: Available in policies and automations for network-based logic
Context-specific examples: In Policies:
// Policy conditional - check if IP is private
ip.isPrivate

// Policy conditional - check if IP is IPv4
ip.is4
In Automations:
// Automation trigger - when IP is from private network
ip.isPrivate

IP CIDR object

Available in: Policies ️ Groups Automations ️ Campaigns ️ Account provisioning
Description: Used for network range-based access control and filtering
Usage: Available in policies and automations for network range checking
Context-specific examples: In Policies:
// Policy conditional - check if IP is in range
cidr('10.1.2.0/24').contains(ip('10.1.2.5'))
In Automations:
// Automation trigger - when IP is in specific range
ip('10.1.2.5').within(cidr('10.1.2.0/24'))

Context object (ctx)

Available in: ️ Policies ️ Groups Automation Triggers Workflow Steps ️ Campaigns ️ Account provisioning
Description: Provides access to workflow execution context and trigger data
Usage: Used in automations to access trigger data and previous step outputs
Available fields: In Automation Triggers:
  • ctx.trigger.oldUser / ctx.trigger.old_user - User state before change
  • ctx.trigger.newUser / ctx.trigger.new_user - User state after change
  • ctx.trigger.oldAccount / ctx.trigger.old_account - Account state before change
  • ctx.trigger.newAccount / ctx.trigger.new_account - Account state after change
  • ctx.trigger.entitlement - Entitlement that triggered the workflow
In Workflow Steps:
  • ctx.trigger - Trigger output data (structure varies by trigger type)
  • ctx.[step_name] - Output from each completed workflow step
Context-specific examples: In automation triggers:
// Trigger when user changes departments
ctx.trigger.newUser.department != ctx.trigger.oldUser.department && 
ctx.trigger.newUser.department == "Engineering"

// Trigger when account status changes to disabled
ctx.trigger.newAccount.status.status == APP_USER_STATUS_DISABLED &&
ctx.trigger.oldAccount.status.status == APP_USER_STATUS_ENABLED
In automation steps:
// Access trigger data
ctx.trigger.user.email

// Template syntax for embedding in strings
"User {{ ctx.trigger.user.display_name }} changed departments"
Important:Automation triggers have limited function access. They do NOT have access to directory functions (c1.directory.*) or user entitlement checking functions (c1.user.v1.HasApp, etc.). Only basic user/account field access and status enums are available.

Important notes

Use camelCase

CEL expressions should be written in camelCase. ConductorOne is moving away from snake_case for consistency and readability. Existing expressions in snake_case will still work, but new ones should follow the camelCase convention.

Null safety with has()

Use the has() macro to check for existence of optional fields, especially in profile maps:
has(subject.profile.custom_field) ? subject.profile.custom_field == "value" : false

Performance considerations

Directory functions (c1.directory.*) perform I/O operations. Place them after simpler conditions when possible:
// Good: Check fast conditions first
subject.department == "Engineering" && c1.directory.users.v1.FindByEmail(subject.email)

// Less optimal: I/O operation runs for all evaluations
c1.directory.users.v1.FindByEmail(subject.email) && subject.department == "Engineering"

Function memoization

Directory function calls are automatically memoized within a single expression evaluation, so calling the same function multiple times is safe and efficient.

Template syntax

Only Workflow Steps support {{ expression }} template syntax for embedding CEL expressions in strings.