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.
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, GetEntitlementMembersAvailable as:subject, elements of appOwners, ctx.trigger.oldUser, ctx.trigger.newUser
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:ListAppUsersForUserAvailable as:ctx.trigger.oldAccount, ctx.trigger.newAccount (in account-change triggers)
Analysis data attached to a task, including access conflict information. See Task analysis object for all fields.Available as:task.analysis (in policy expressions)
Workflow execution context containing trigger data and step outputs. See Context object for all fields.Available as:ctx (in automation triggers and workflow steps)
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.
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:
Report incorrect code
Copy
Ask AI
// DANGER without fallback: step skipped if no managerc1.directory.users.v1.GetManagers(subject)// SAFE with fallback: uses app owners if no managersize(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.
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).
// 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) &×tamp(ctx.trigger.newAccount.updated_at) >= time.start_of(now(), "month")
Business hours checking:
Report incorrect code
Copy
Ask AI
// 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 hourstime.format(now(), "15", "America/New_York") >= "17" ||time.format(now(), "15", "America/New_York") < "09"
Day of week checking:
Report incorrect code
Copy
Ask AI
// Check if it's a weekendtime.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:
Report incorrect code
Copy
Ask AI
// Parse contract end date and check if within 30 dayshas(subject.profile.contract_end_date) &&time.parse(subject.profile.contract_end_date, TimeFormat.DATE) - now() < duration("720h")// Check if hire date is in the pasthas(subject.profile.hire_date) &&time.parse(subject.profile.hire_date, TimeFormat.DATE) < now()
Working with timezones:
Report incorrect code
Copy
Ask AI
// Start of day in company timezonetime.start_of(now(), "day", "America/New_York")// Format date in user's timezonetime.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.
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.
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.
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 contextsContext-specific examples:In Policies:
Report incorrect code
Copy
Ask AI
// Policy conditional - check if user is in Engineering departmentsubject.department == "Engineering"// Policy expression - route to user's managerc1.directory.users.v1.FindByEmail(subject.manager)
In Groups:
Report incorrect code
Copy
Ask AI
// Group membership - include all Engineering employeessubject.department == "Engineering" && subject.status == UserStatus.ENABLED
In Automations:
Report incorrect code
Copy
Ask AI
// Automation trigger - when user status changessubject.status == UserStatus.DISABLED
In Campaigns:
Report incorrect code
Copy
Ask AI
// User selection - include contractors only!subject.email.endsWith("@company.com")
In Account provisioning:
Report incorrect code
Copy
Ask AI
// Data mapping - use user's first namesubject.attributes.firstName
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 usersContext-specific examples:In Policies:
Report incorrect code
Copy
Ask AI
// Policy expression - assign to specific userc1.directory.users.v1.GetByID("user123")
In Groups:
Report incorrect code
Copy
Ask AI
// Group membership - include users by departmentuser.department == "Engineering"
In Automations:
Report incorrect code
Copy
Ask AI
// Automation step - modify specific useruser.status == UserStatus.DISABLED
In Campaigns:
Report incorrect code
Copy
Ask AI
// User selection - include users by statususer.status == UserStatus.ENABLED
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 contextContext-specific examples:In Policies:
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 analysisContext-specific examples:In Policies:
Report incorrect code
Copy
Ask AI
// Policy conditional - check for any conflictstask.analysis.hasConflictViolations// Policy conditional - check for specific conflict"conflict123" in task.analysis.conflictViolations
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 requestContext-specific examples:In Policies:
Report incorrect code
Copy
Ask AI
// Policy conditional - check specific entitlemententitlement.id == "entitlement123"// Policy expression - assign to users with same entitlementc1.directory.apps.v1.GetEntitlementMembers(entitlement.appId, entitlement.id)
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 logicContext-specific examples:In Policies:
Report incorrect code
Copy
Ask AI
// Policy conditional - check if IP is privateip.isPrivate// Policy conditional - check if IP is IPv4ip.is4
In Automations:
Report incorrect code
Copy
Ask AI
// Automation trigger - when IP is from private networkip.isPrivate
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 checkingContext-specific examples:In Policies:
Report incorrect code
Copy
Ask AI
// Policy conditional - check if IP is in rangecidr('10.1.2.0/24').contains(ip('10.1.2.5'))
In Automations:
Report incorrect code
Copy
Ask AI
// Automation trigger - when IP is in specific rangeip('10.1.2.5').within(cidr('10.1.2.0/24'))
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 outputsAvailable 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:
Report incorrect code
Copy
Ask AI
// Trigger when user changes departmentsctx.trigger.newUser.department != ctx.trigger.oldUser.department &&ctx.trigger.newUser.department == "Engineering"// Trigger when account status changes to disabledctx.trigger.newAccount.status.status == APP_USER_STATUS_DISABLED &&ctx.trigger.oldAccount.status.status == APP_USER_STATUS_ENABLED
In automation steps:
Report incorrect code
Copy
Ask AI
// Access trigger datactx.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.
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.
Directory function calls are automatically memoized within a single expression evaluation, so calling the same function multiple times is safe and efficient.